user1413558
user1413558

Reputation: 3919

NSJSONSerialization crashes when pulling data from specific url

I'm trying to pull data from this url, but I am running into some problems. Why does this crash on the NSJSONSerialization line? Is there a better way to download info from this website?

EDIT: I changed jsonArray from a NSArray to a NSDictionary, but it still crashes in the same spot. Is there a different way to download this data?

NSString *url=@"https://api.p04.simcity.com/simcity/rest/users/search/J3d1.json";

NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];

NSURLResponse *resp = nil;
NSError *err = nil;

NSData *response = [NSURLConnection sendSynchronousRequest: theRequest returningResponse: &resp error: &err];

NSDictionary *jsonArray = [NSJSONSerialization JSONObjectWithData: response options: NSJSONReadingMutableContainers error: &err];

NSLog(@"%@",jsonArray);

For reference, the JSON is:

{
    "users": [
        {
            "uri": "/rest/user/20624",
            "tutorialState": 0,
            "nucleusId": 20624,
            "id": 20624,
            "screenName": "R3DEYEJ3D1",
            "lastLogin": 1362666027000,
            "isOnline": "true",
            "avatarImage": "https://api.p04.simcity.com/simcity/rest/user/20624/avatar",
            "cities_count": 0,
            "canChat": "true"
        },
        {
            "uri": "/rest/user/46326",
            "tutorialState": 0,
            "nucleusId": 46326,
            "id": 46326,
            "screenName": "J3D1_WARR10R",
            "lastLogin": 1363336534000,
            "isOnline": "false",
            "avatarImage": "https://api.p04.simcity.com/simcity/rest/user/46326/avatar",
            "cities_count": 0,
            "canChat": "true"
        }
    ]
}

Upvotes: 0

Views: 902

Answers (3)

Carl Veazey
Carl Veazey

Reputation: 18363

Your server is presenting a certificate that isn't trusted by the OS - I had to use the -k flag to curl your JSON so should have thought of this earlier.

In order to get around this, you'll need to switch to using an asynchronous NSURLConnection and implement its delegate methods. See this answer from this stack overflow question to implement a working solution to your parsing issue. I implemented it inside the app delegate, but you can for example put this in an asynchronous NSOperation and use it there.

WARNING: This code will connect to any server, regardless of trust. This is asking for a man in the middle attack. Do not send sensitive information to a server when this type of trust is enabled. You MUST implement code to verify your server's identity, and replace the if (YES) in - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge with a test against the result of that check.

My current understanding is that this should be done by including your certificate's public key in the app bundle as a DER file, and using SecCertificateCreateWithData to create a SecCertficiateRef that will be used as the input to SecTrustSetAnchorCertificates. Then, SecTrustEvaluate should be used to verify the identity. I base this understanding on my reading of the Certificate, Key, and Trust Services Reference and sample code found in this blog post on how to trust your own certificate.

@interface AppDelegate () <NSURLConnectionDelegate, NSURLConnectionDataDelegate>

//  This is needed to collect the response as it comes back from the server
@property (nonatomic, strong) NSMutableData *mutableResponseData;

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Xcode template code for setting up window, ignore...
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        self.viewController = [[ViewController alloc] initWithNibName:@"ViewController_iPhone" bundle:nil];
    } else {
        self.viewController = [[ViewController alloc] initWithNibName:@"ViewController_iPad" bundle:nil];
    }
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];

    //  Instantiate our connection 
    NSString *urlString = @"https://api.p04.simcity.com/simcity/rest/users/search/J3d1.json";
    NSURL *url = [NSURL URLWithString:urlString];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    if(![NSURLConnection connectionWithRequest:request delegate:self]) {
        NSLog(@"Handle an error case here.");
    }

    return YES;
}


- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    //  Prepare to recevie data from the connection
    NSLog(@"did receive response: %@", response);
    self.mutableResponseData = [NSMutableData data];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    //  Handle an error case here
    NSLog(@"did fail with error: %@", error);
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    //  Build up the response data
    [self.mutableResponseData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSError *error = nil;
    id JSONObject = [NSJSONSerialization JSONObjectWithData:self.mutableResponseData options: NSJSONReadingMutableContainers error:&error];
    if (!JSONObject) {
        //  Handle error here
        NSLog(@"error with JSON object");
    }
    else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
        //we're in business
        NSDictionary *dict = JSONObject;
        NSLog(@"dict is %@", dict);
    }
    else {
        //  Handle case of other root JSON object class...
    }
}


//  The following two methods allow any credential to be used. THIS IS VULNERABLE TO MAN IN THE MIDDLE ATTACK IN ITS CURRENT FORM
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
    return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        // WE ARE POTENTIALLY TRUSTING ANY CERTIFICATE HERE. Replace YES with verification of YOUR server's identity to avoid man in the middle attack.
        //  FOR ALL THAT'S GOOD DON'T SHIP THIS CODE
#warning Seriously, don't ship this.
        if (YES) {
            [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
        }
    }

    [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];   
}

@end

Upvotes: 3

Hossam Ghareeb
Hossam Ghareeb

Reputation: 7113

Try put the url in the browser to make sure that gives you a valid JSON, also make sure the parsed data is Array or NSDictionary

Upvotes: 0

ChrisH
ChrisH

Reputation: 4558

NSJSONSerialization will always crash if the data is nil. You need to put a conditional in to see if that URL contains any data.

Upvotes: 0

Related Questions