NSString's stringWithContentsOfURL uses browser cookies? - objective-c

I'm working on an objective-c project that downloads webpages from a community website and parses the results. The download code looks like this:
NSError* error = nil;
NSString* text = [NSString stringWithContentsOfURL:fileUrl encoding:NSASCIIStringEncoding error:&error];
if(text) {
return text;
}
else {
NSLog(#"Error = %#", error);
return nil;
}
The odd thing is that when I download from the site I see resulting content that I would only see if logged into the site (which, in my browser, I am).
Does that method (NSString stringWithContentsOfURL:encoding:error) use browser cookies when executing the request? If so, is it Safari specifically that it's integrated with? The default browser? I can't seem to find documentation describing the behavior that I'm seeing. I'm ok with the behavior (in fact, it's preferable), but I only want to depend on it if I fully understand what's going on.
Thanks for your time.

Cookies are automatically handled and stored in an app's NSHTTPCookieStorage shared instance. Call the cookies method and check to see if your cookie is there. If it is, then that confirms your suspicion.
EDIT: I highly suspect you are using a UIWebView in your app and logging in from there. In that case, then yes, cookies are stored in your app's NSHTTPCookieStorage shared instance and will be used with further URL requests.

Related

IOS Facebook SDK can't get User info after log in

Alright, I am having a devil of a time with getting some basic user info from the Facebook IOS SDK (3.1.1). Having done FB development with other platforms I am pretty convinced my issue has to do with the FB app's set up than with my code. BTW, I am in iTunes and have set an iTunes ID for iPhone. I have also carefully checked and quadruple checked the bundle ID in the App set up against my bundle ID.
Right now I can authenticate with a test user and get all the detail, etc. It works great. When I log in as a real user like myself (and I am a developer on the project) I get an Auth token, but calls to get the user fail, and I'm not sure what I have done wrong (if it works for a test user it should work for everyone).
All I need to get from the user, BTW is their First name, Last name, FB User ID, and email address (optional, but requested).
Currently after authenticating using the I'm using the games tutorial. Once I am logged in I do the following (although I have done a bunch of other things with the same result):
- (void)fbDidLogin
{
// removed the setup for the class level Facebook var that I am currently not using
// get information about the currently logged in user
NSString *fql = #"select uid, first_name, last_name, email from user where uid = me()";
NSDictionary *queryParam = [NSDictionary dictionaryWithObjectsAndKeys:fql, #"q", nil];
[FBRequestConnection startWithGraphPath:#"/fql"
parameters:queryParam
HTTPMethod: #"GET"
completionHandler:^(FBRequestConnection *connection,
id result,
NSError *error) {
[self meRequestResult:result WithError:error];
}];
}
The meRequestResult routine starts like this:
- (void)meRequestResult:(id)result WithError:(NSError *)error
{
if ([result isKindOfClass:[NSDictionary class]])
{
NSDictionary *dictionary;
if([result objectForKey:#"data"])
dictionary = (NSDictionary *)[(NSArray *)[result objectForKey:#"data"] objectAtIndex:0];
else
dictionary = (NSDictionary *)result;
[fbUserData release];
fbUserData = [dictionary retain];
NSString *facebookId = [dictionary objectForKey:#"id"];
if(!facebookId)
facebookId = [dictionary objectForKey:#"uid"];
// and more follows that is not pertinent
When I turn on request logging in the FB IOS SDK. I see the same things and can not find any description anywhere what these mean.
Here's the last 10 or so lines from the log:
2012-12-03 11:52:12.394 mTender[6572:1cd03] Reachability Flag Status: -R -----l- networkStatusForFlags
2012-12-03 11:52:12.395 mTender[6572:1cd03] The internet is working via WIFI.
2012-12-03 11:52:12.396 mTender[6572:1cd03] Reachability Flag Status: -R ------- networkStatusForFlags
2012-12-03 11:52:12.396 mTender[6572:1cd03] A gateway to the host server is working via WIFI.
2012-12-03 11:52:35.093 mTender[6572:1cd03] FBSDKLog: Request <#1111>:
URL: https://graph.facebook.com//fql?sdk=ios&access_token=ACCESS_TOKEN_REMOVED&q=select%20uid%2C%20first_name%2C%20last_name%2C%20email%20%20from%20user%20where%20uid%20%3D%20me%28%29&migration_bundle=fbsdk%3A20121003&format=json
Method: GET
UserAgent: FBiOSSDK.3.1.1
MIME: multipart/form-data; boundary=3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f
2012-12-03 11:52:37.194 mTender[6572:1cd03] Error: HTTP status code: 500
2012-12-03 11:52:37.196 mTender[6572:1cd03] FBSDKLog: Response <#1111> <Error>:
The operation couldn’t be completed. (com.facebook.sdk error 5.)
Hopefully someone can figure this out. It's the last step of this project..
I am doing something very similar to you. I am using facebook for login so that I don't have to worry about storing passwords and what not.
I followed this set of instructions.. The facebook site seems to be horrid the way its layed out with out of date stuff all over the place.
You can use native facebook or web based user authentication (the SDK will manage all that) so it will work on older versions of iOS and when not signed in to native facebook.
You will set a handler which will give you a "NSDictionary *user" with all the stuff that you need. No need to use the graph api on the iphone client.
i think you have to see alternate way because the REST API has been deprecated. To make FQL queries you have to use the Graph API call.
http://developers.facebook.com/docs/reference/rest/fql.query/
OK, I'm going to close this out.
I reverted to an SDK back in November of 2011. It didn't seem to be working at first, and then it just started working. I suspect a bug on Facebook's end was causing this. I haven't updated to the latest again to try, but this is my only guess as to what happened.
If you've done any lengthy Facebook development you know that sometimes their bugs really make things difficult. That said, no idea if this is the actual answer since I really didn't make any coding changes from the original code.
I know that I purposefully left the bundle ID out of the FB App Setup..

ASIHTTPRequest seems to cache JSON data always

I'm using ASIHTTPRequest API to get some JSON data from a web side. I'm using an asynchronous request without changing default cache properties. Code is as follows:
__unsafe_unretained ASIHTTPRequest * request = [ASIHTTPRequest requestWithURL:url];
request.timeOutSeconds = 30;
[request setCompletionBlock:^(void)
{
NSString *str = [request responseString];
/*
*
*
*/
}];
[request setFailedBlock:^(void)
{
NSLog(#"status: %#",[request responseStatusMessage]);
NSLog(#"%# : %#",[request url],[[request error] debugDescription]);
}];
[request startAsynchronous];
However, obtained JSON data remains as the old one although content of JSON data in server changes.
I checked data using web browser, both on laptop and on iPhone safari. When i request url after a change in JSON, it first returns old data, but if i refresh the page, it returns updated data. But in the app, ASIHTTPRequest always returns the old data.
I also try to debug ASIHTTPRequest code in order to see whether any cached data used. But it seems like it never uses download cache because it has not been set. It never enters [useDataFromCache] method.
What could be the problem? How can i force ASIHTTPRequest to check whether there is an updated data on server, and make it get the true updated JSON?
EDIT
I used Cache-Control header, and now i get the correct updated JSON data. Code is as follows:
[request addRequestHeader:#"Cache-Control" value:#"no-cache"];
However, i think from now on request will always try to retrieve JSON even if it is not modified, which will decrease performance.
How can i make it first check the server whether data is modified, and retrieve if it is modified? Currently i get JSON data as a dynamic response from a php url, so there is no file which i can check up to dateness of the data.
What could be the solution?
Regards,
Given everything you've said, it seems unlikely that ASIHTTPRequest is cacheing the response.
So, something else must be - it seems like you have a cacheing proxy server inbetween you and the server, and that's why setting Cache-Control makes a difference.
It could be a proxy server on your local network, or it could be at your ISP, or it could be in front of the web server you're using.
According to ASIHTTPRequest's documentation, calling the method
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICachePermanentlyCacheStoragePolicy];
will clear the cache. Call this method before you send the request and it should give you the updated JSON data.

Clear cookies for a given iOS App

My App connects to a server and based on a cookie the server will issue a different response.
Is it no possible to programmatically clear the cookie store, so that the server will not recognize my App when it contacts the server the next time.
I gathered that clearing the Cookies in the Settings.app does only apply for cookies within Safari.
Thanks very much for your comment.
Okay... following up on my earlier comment (and hoping this is the solution you are looking for), you probably want to utilize:
[[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:]
for each of the cookies for your site.
If you want your changes to the NSHTTPCookieStorage to be retained, you'll also want to call off to
[[NSUserDefaults standardUserDefaults] synchronize];
To prevent this from slowing down your app, you may also want to call this on a background thread like so:
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(backgroundQueue, ^{
//TODO: Cookie deletion logic here
});
EDIT:
If you just need to disregard cookies altogether for a given NSURLRequest, you can do so with:
[request setHTTPShouldHandleCookies:NO];
Where request is your instance of NSURLRequest.
As mentioned by #Niralp it isn't possible to delete all cookies by passing nil to deleteCookie: on an instance of NSHTTPStorage. However, since iOS 8 there has been a removeFromDate: method that can be utilised to the same effect.
In Swift 4 this would be:
HTTPCookieStorage.shared.removeCookies(since: Date(timeIntervalSince1970: 0))
That would remove all cookies in the app since the 1970 epoch which is likely suitable for most needs.

Can't reauthenticate with different NSURLCredentials (old deleted ones are used)

i've been searching stackoverflow, google, apple and other places. The tips provided look promising, i implemented them but alltogether don't seem to work or get enforced.
Problem: I have an NSURLConnection with specific credentials. I then have a logout where I clear the credentials, the protectionspace, i remove all cached responses and delete all cookies in the sharedHTTPCookieStorage but when calling my authenticated request again a few seconds later even with wrong credentials I still am using the old (deleted) credentials
Here are some code extracts, where credentials are removed
NSDictionary *credentialsDict = [[NSURLCredentialStorage sharedCredentialStorage] allCredentials];
if ([credentialsDict count] > 0) {
// the credentialsDict has NSURLProtectionSpace objs as keys and dicts of userName => NSURLCredential
NSEnumerator *protectionSpaceEnumerator = [credentialsDict keyEnumerator];
id urlProtectionSpace;
// iterate over all NSURLProtectionSpaces
while (urlProtectionSpace = [protectionSpaceEnumerator nextObject]) {
NSEnumerator *userNameEnumerator = [[credentialsDict objectForKey:urlProtectionSpace] keyEnumerator];
id userName;
// iterate over all usernames for this protectionspace, which are the keys for the actual NSURLCredentials
while (userName = [userNameEnumerator nextObject]) {
NSURLCredential *cred = [[credentialsDict objectForKey:urlProtectionSpace] objectForKey:userName];
WriteLog(#"Method: switchView removing credential %#",[cred user]);
[[NSURLCredentialStorage sharedCredentialStorage] removeCredential:cred forProtectionSpace:urlProtectionSpace];
}
}
}
I then remove all cached responses
NSURLCache *sharedCache = [NSURLCache sharedURLCache];
[sharedCache removeAllCachedResponses];
I then delete all cookies
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSArray *cookies = [cookieStorage cookies];
for (NSHTTPCookie *cookie in cookies) {
[cookieStorage deleteCookie:cookie];
NSLog(#"deleted cookie");
}
I also tried using no cookies and other policies
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:theURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.0];
[request setHTTPShouldHandleCookies:NO];
if(self.currentCookies != nil){
[request setAllHTTPHeaderFields:
[NSHTTPCookie requestHeaderFieldsWithCookies:nil]];
}
theConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
I also tried this hint here on specifically storing the cookies and passing them again. http://www.hanspinckaers.com/multiple-nsurlrequests-with-different-cookies. There's another blog on the web suggesting to add a "#" to each URL in order to enforce reauthentication, which works but just does not solve the issue because I need to count on session's credentials and the ability to use totally different credentials.
Is this a bug or known issue and how do I really solve this...
Put bluntly: What am I exactly doing wrong here?
This is really bugging me and keeping me from continuing my work.
I would greatly appreciate any input!
Thanks alot!
Unfortunately, there does not seem to be a solution to this problem.
You can use NSURLCredentialPersistenceNone or the # trick or you can define the 'connectionShouldUseCredentialStorage' delegate method to return NO. If you do that every time and your app never persists the credentials for a session, that will force the challenge to occur on every request.
For apps that are only performing minimal requests or that end up using a session cookie for authentication, that might work OK.
For apps that are sending a large number of requests, these solutions all result in a 401 response for every single request and that extra challenge-response can add up in terms of data and performance.
It would be nice if you could persist the credential storage for the session until you needed to log out and then switch to one of the work-arounds, but that is not possible.
As soon as you store the credentials once for a session, they get cached for the entire TLS session. That results in a need to wait about 10 minutes until that session goes away.
You can read more about this issue at: http://developer.apple.com/library/ios/qa/qa1727/_index.html
That document mentions a limited work-around that involves appending a '.' to the end of the server name. I have been unable to get that working, however.
Other than that, these are the solutions I can think of:
1) Always use the NSURLCredentialPersistenceNone & connectionShouldUseCredentialStorage workaround that should generate the 401s. Add the Basic authentication header to the request, yourself. This should prevent the extra 401's while also bypassing the credential storage. The code for adding that authorization looks something like this:
NSString *s ;
NSString *authStr ;
s = [NSString stringWithFormat:#"%#:%#",user,password] ;
s = [YourBase64Encoder base64EncodingForData:[NSData dataWithBytes:[s UTF8String] length:strlen([s UTF8String])]];
authStr = [NSString stringWithFormat:#"Basic %#",s] ;
[request setValue:authStr forHTTPHeaderField:#"Authorization"] ;
I don't know how this would be implemented for other authentication methods, but I presume it is possible.
2) Inform the user of the issue and ask them to restart the app
3) Implement your own low-level sockets based http retrieval mechanism that bypasses CFNetwork completely. Good luck with that :>)
I just ran into this issue with AFNetworking. I'm working with a backend that requires Authorization to be passed in the header. However, when the user logs out of the app and attempts to log back in (even with different creds) I was getting an error from the server. My solution was to clear out my apps cookies when clearing the authheader in logout.
- (void)clearAuthorizationHeader {
[self.manager.requestSerializer clearAuthorizationHeader];
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in [storage cookies]) {
[storage deleteCookie:cookie];
}
}
I've run into this issue too. Clearing NSURLCredentialStorage seems to partially work, but it seems like I have to wait a few seconds after this for it to take effect. Doing another HTTP request without waiting results in the old Authorization header being used.
I was able to fix it by passing NSURLCredentialPersistenceNone while initializing my NSURLCredential:
NSURLCredential* credentials = [[NSURLCredential alloc] initWithUser:username password:password persistence:NSURLCredentialPersistenceNone];
note: this will cause 401 Challenges on every HTTP request you make with this NSURLCredential. However this isn't an issue if you get back some cookies that keep you authenticated.
For what it's worth, I'm having the same problem.
I think it's a timing issue. When using the simulator, if I wait 5-10 seconds before trying to log in again, login fails as expected. Also, when I use an actual phone, I can rarely get the problem to occur - which might be a function of the phone being slower, or might be a bug in the simulator.
I know it's an old topic. However the only thing, that works for me was to use different urls for different certificates.
It worked in my application, since I have only 2 certificates (one general in application resources and one custom downloaded from internet after the user verification process). In my case there are no cookies, and no credentials to clear so none of the solutions I found on stackoverflow worked.
I was facing the same problem, now it works.
Using NSURLConnection this issue can be fixed easily by adding a random number to the end of the URL:
So, search for your URLRequest and append the random number to URLRequest
NSInteger randomNumber = arc4random() % 999;
NSString *requestURL = [NSString stringWithFormat:#"%#?cache=%ld",yourURL,(long)randomNumber];
NSURL *URLRequest = [NSURL URLWithString:requestURL];
And make sure you have a random number at the end of all URLs you are calling.

Objective-C: Check Firewall status in OSX?

My objective-c app needs to be aware if the firewall in OSX is running, so it can tell the user to turn it off or create a new rule.
Also, is it possible to create rules directly from my app so users never need to handle networking issues?
John
I am writing a function that will provide you the status of OSX firewall :)
-(BOOL)getFirewallStatus{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSSystemDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
path = [NSString stringWithFormat:#"%#/%#",path,#"Preferences/com.apple.alf.plist"];
path = [path stringByReplacingOccurrencesOfString:#"/System"
withString:#""];
NSDictionary* _dictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
// firewall status
int status = [[_dictionary valueForKey:#"globalstate"] integerValue];
if (status == 0)
{
return NO;
}
return YES;
}
If your application is being run by the user (i.e., double-clicked in the Finder), any attempt by your application to create a socket listener will prompt the user to allow/deny that listener - and subsequently adjust the firewall settings accordingly - without any programmatic intervention on the part of your application.
If the firewall in question is your router (a problem I recently had to deal with), you have a few options. The best supported option is Bonjour/mDNSResponder (as long as you don't want to support a double-nat'ed situation). Apple provides an Objective-C wrapper application around the rather obtuse dns_sd.h:
http://developer.apple.com/library/mac/#samplecode/PortMapper/Introduction/Intro.html#//apple_ref/doc/uid/DTS40007879-Intro-DontLinkElementID_2
Going the 3rd party route, take a look at TCM Port Mapper. It uses some deprecated features and it'll take a bit of effort to get it running with ARC support (if that's important to you).
http://code.google.com/p/tcmportmapper/
Both support UPnP and NAT-PMP.
Finally, if your application is running as a daemon (without a user interface), you're going to have to become acquainted with ipfw. Brace yourself. Google for "ipfw os x". StackOverflow is preventing me from posting more than two links. Brilliant.
Hope this helps....