Why NSURLErrorDomain -1000 (bad URL) for specific NSURLs only? - objective-c

I looked through all the relevant questions I could find (search: "NSURLErrorDomain -1000"), and couldn't find a solution. Maybe somebody eagle-eyed could help me.
I'm making calls from iOS 6 to the Instagram API (but the parts that work, did so already in iOS 5.1). I'm using Koolistov's NSURL+PathParameters.h as a helper, and my understanding is that it does indeed escape everything that is necessary in the URL.
Some preliminaries:
NSDictionary *parameters = nil;
NSURL *URL = [NSURL URLWithString:#"https://api.instagram.com/"];
parameters = [NSDictionary dictionaryWithObjectsAndKeys:
INSTAGRAM_CLIENT_ID, #"client_id", nil];
URL = [URL URLByReplacingPathWithPath:#"/v1/tags/awesome/media/recent"];
URL = [URL URLByAppendingParameters:parameters];
(INSTAGRAM_CLIENT_ID is a macro that contains my app's client ID.)
This URL works as expected.
However, the URL constructed as follows gives me NSURLErrorDomain code = -1000 consistently (on device and on both iPhone 5.1 and 6.0 simulators):
double latitude = 48.858844;
double longitude = 2.294351;
parameters = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithDouble:latitude], #"lat",
[NSNumber numberWithDouble:longitude], #"lng",
[NSNumber numberWithInt:2000], #"distance",
INSTAGRAM_CLIENT_ID, #"client_id",
nil];
URL = [URL URLByReplacingPathWithPath:#"/v1/media/search"];
URL = [URL URLByAppendingParameters:parameters];
I then create an NSURLRequest:
NSURLRequest *req = [NSURLRequest requestWithURL:URL];
If I print out the URL from the NSURLRequest, it seems quite OK:
https://api.instagram.com/v1/media/search?client_id=REDACTED&distance=2000&lat=48.858844&lng=2.294351
I'm using MyDownloader from Matt Neuburg's Programming iOS 5, Chapter 37. I create an instance of MyDownloaderand pass it the NSURLRequest:
MyDownloader *downloader = [[MyDownloader alloc] initWithRequest:req];
Accoding to RFC 3986, underscores or dots don't need to be escaped, I don't see anything else to be escaped, and NSURL+PathParameters.h should handle escaping anyway. So what is going on here? What am I missing?
To add to the weirdness, also this even simpler way of constructing the URL gives the same error:
parameters = [NSDictionary dictionaryWithObjectsAndKeys:
INSTAGRAM_CLIENT_ID, #"client_id", nil];
URL = [URL URLByReplacingPathWithPath:#"/v1/media/popular"];
It also looks OK:
https://api.instagram.com/v1/media/popular?client_id=REDACTED
All of these URLs work in the Instagram API console and directly in Safari. These API calls don't need OAuth authentication, just the client ID.
I would be greatful if somebody could point out what the problem is.
EDIT: encoded the lat and lng values with:
[[[NSNumber numberWithDouble:latitude] stringValue]
stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
No change in URL or behavior. But when I replace the dots with literal %2E's, the path helper encodes the percent sign, resulting in %252E...

Solved. There was nothing wrong with the URLs shown above. The problem was with surrounding code. I'm answering my own question so that someone working with the Instagram API might benefit from the answer.
It turns out that the /v1/tags/tagname/media/recent endpoint of the API returns a pagination object in the JSON response, but the /v1/media/search and /v1/media/popular do not. From inside that object you can find the URL to the next batch of photos, in a value called next_url.
Because I hadn't read the documentation properly (and it was a little confusing in places), and because the different Instagram API endpoints work differently, my code expected to find the next_url value in all those cases. So the "bad URL" I was getting from NSURLConnection was referring to the second connection, and not the initial URL. Since I did not realize there is no pagination in the two other cases, I assumed that the problem must be in the initial URL.

Related

+ (instancetype) URLWithString returns nil when I try to put non-latin character

I know what's the error I've got however I don't know the way to decide it. It's okay to meet letters like Þþ, Ðð, Ææ, etc. within my Cocoa App.
By breakpoints I'd put I found that URLWithString returns nil every time when I put at least one non-latin character. Otherwise, returns some new URL only based on latin characters.
Some fragment of attempt:
NSString *baseURLString = #"https://hostdomain.com";
NSString *pathURLString = #"/restapi/someRequest?par1=arg1&par2=arg2&input=";
NSString *fullURLString = [NSString stringWithFormat:#"%#%#móðir", baseURLString, pathURLString];
NSURL *url = [NSURL URLWithString:fullURLString]; // here I get a nil while working with non-latin characters.
I'm still trying to find a solution but none of decisions here on stackoverflow didn't help me. Any idea would be appreciated! My idea is URLWithString works well only with ASCII symbols.. 🤔
URLWithString only works with valid URLs. Some of the characters you're passing are not valid for the query portion of an URL. See section 2 of RFC 3986. Since the URL is invalid, it returns nil.
If you have arbitrary characters coming into your URL, you shouldn't try to build it all as a single string, since each part of the URL requires different encoding. You need to use NSURLComponents. This will automatically escape each section correctly.
NSURLComponents *comp = [NSURLComponents new];
comp.scheme = #"https";
comp.host = #"hostdomain.com";
comp.path = #"/restapi/someRequest";
comp.query = #"par1=arg1&par2=arg2&input=óðir";
NSURL *url = comp.url;
// https://hostdomain.com/restapi/someRequest?par1=arg1&par2=arg2&input=%C3%B3%C3%B0ir
Or, since the base part of the URL is static and you know it's encoded correctly, you can do it this way:
NSURLComponents *comp = [NSURLComponents componentsWithString:#"https://hostdomain.com/restapi/someRequest"]
comp.query = #"par1=arg1&par2=arg2&input=óðir"
If you really want to build strings more directly, you can look at stringByAddingPercentEncodingWithAllowedCharacters:. Use [NSCharacterSet URLQueryAllowedCharacterSet] for the query part.

RestKit PUT not working

I'm trying to do a fairly basic HTTP PUT using RestKit. I don't want to put the entire object, since the API call was designed to accept a single query parameter and just update that field. I've tried two approaches so far, both unsuccessful.
URL to post to: https://myserver/api/users/{userId}
Query string parameter: verificationCode=
Example usage: PUT https://myserver/api/users/101?verificationCode=646133
Approach #1: Put the query parameter in a RKParams object and make the PUT call with those params.
NSString *putUrl = [NSString stringWithFormat:#"/api/users/%i", [APIUserInfo sharedAPIUserInfo].apiUserIdx];
NSLog(#"the PUT url is %#", putUrl);
// Send a PUT to a remote resource. The dictionary will be transparently
// converted into a URL encoded representation and sent along as the request body
NSDictionary* paramsDict = [NSDictionary dictionaryWithObject:[_verificationCode text] forKey:#"verificationCode"];
// Convert the NS Dictionary into Params
RKParams *params = [RKParams paramsWithDictionary:paramsDict];
[[RKClient sharedClient] put:putUrl params:params delegate:self];
Approach #2: Build the entire url and try a PUT with params set to nil.
NSString *putUrl = [NSString stringWithFormat:#"/api/users/%i?verificationCode=%#", [APIUserInfo sharedAPIUserInfo].apiUserIdx, [_verificationCode text]];
NSLog(#"the PUT url is %#", putUrl);
[[RKClient sharedClient] put:putUrl params:nil delegate:self];
Neither approach is working for me. The first fails saying "RestKit was asked to retransmit a new body stream for a request. Possible connection error or authentication challenge?" then runs for about 10 seconds and times out. The second approach fails saying HTTP Status 405 - Method Not Allowed.
Can anyone point out where I'm going wrong, or provide me with a simple PUT example using RestKit? Most of the examples I've found at there are putting the entire object which I don't want to do in this case.
UPDATE:
Approach #2 worked well once I got a few things sorted out on the server side. Final solution:
NSString *putUrl = [NSString stringWithFormat:#"/api/users/verify/%i?verificationCode=%#", [APIUserInfo sharedAPIUserInfo].apiUserIdx, [_verificationCode text]];
NSLog(#"the PUT url is %#", putUrl);
[[RKClient sharedClient] put:putUrl params:nil delegate:self];
the HTTP PUT method is disabled on your webserver. It is by default on all webserver for security reasons.
HTTP Status 405 - Method Not Allowed.

facebook post action url error

Im working on posting to facebook on an ios app. I had it working fine but somehow it has stopped working. I have the users auth at this point and all thats fine it just keeps giving me this error when i try to post to facebook "the post's action links must be valid urls"
below is my code for the post
currentAPICall = kDialogFeedUser;
SBJSON *jsonWriter = [[SBJSON new] autorelease];
// The action links to be shown with the post in the feed
NSArray* actionLinks = [NSArray arrayWithObjects:[NSDictionary dictionaryWithObjectsAndKeys:
#"someName",#"name",#"www.validurl.com",#"link", nil], nil];
NSString *actionLinksStr = [jsonWriter stringWithObject:actionLinks];
// Dialog parameters
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
shareInfo.facebookShareText, #"name",//name is heading with link to above
#"text text text", #"caption",// caption makes text grayed out
#"more wonderful text", #"description",// block text for the share
shareInfo.shareUrlShort, #"link",//actual link
shareInfo.facebookShareImage, #"picture",//url of picture to be displayed
actionLinksStr, #"actions",
nil];
//the post's action links must be valid urls
//make post to wall feed
[_facebook dialog:#"feed"
andParams:params
andDelegate:self];
the post works if i remove the actionLinksStr from the params as the error says that the link within it is not valid. This has been working for months but just stopped when i tried it today and i cant seem to get it back working again. Has anyone any idea why this error would suddenly start happening with no change to the actual sharing code? the url im passing into the action link is definately valid. any help would be much appreciated thanks
edit: just gonna flag this as a bug on facebooks dev site. see if i can get any feedback from there. still a mystery to me
Make sure you are using a valid URL. In your code example above, change www.validurl.com to a complete URL: http://www.validurl.com

Unable to retrieve certain pages using stringWithContentsOfURL

I am trying to get HTML files from the web, using stringWithContentsOfURL:. My problem is, sometimes it works but sometimes it doesn't. For example, I tried:
NSString *string = [NSString stringWithContentsOfURL:
[NSURL URLWithString:#"http://www.google.com/"]
encoding:encoding1
error:nil];
NSLog(#"html = %#",string);
This works fine, but when I replace the URL with #"http://www.youtube.com/" then I only get "NULL". Is there anyone that knows what's going on? Is it because of YouTube having some sort of protection?
Google's home page uses ISO-8859-1 encoding (aka "Latin-1", or NSISOLatin1StringEncoding). YouTube uses UTF-8 (NSUTF8StringEncoding), and the encoding you've specified with your encoding1 variable has to match the web page in question.
If you just want the web page and don't really care what encoding it's in, try this:
NSStringEncoding encoding;
NSError *error;
NSString *string = [NSString stringWithContentsOfURL:
[NSURL URLWithString:#"http://www.google.com/"]
usedEncoding:&encoding
error:&error];
NSLog(#"html = %#",string);
This method will tell you what the encoding was (by writing it to the encoding variable), but you can just throw that away and focus on the string.

How to make QTMovie play file from URL with forced (MP3) type?

I'm using QTKit to progressively download and play an MP3 from a URL. According to this documentation, this is the code I should use to accomplish that:
NSURL *mp3URL = [NSURL URLWithString:#"http://foo.com/bar.mp3"];
NSError *error = nil;
QTMovie *sound = [[QTMovie alloc] initWithURL:mp3URL error:&error];
[sound play];
This works, and does exactly what I want — the MP3 URL is lazily downloaded and starts playing immediately. However, if the URL does not have the ".mp3" path extension, it fails:
NSURL *mp3URL = [NSURL URLWithString:#"http://foo.com/bar"];
NSError *error = nil;
QTMovie *sound = [[QTMovie alloc] initWithURL:mp3URL error:&error];
[sound play];
No error is given, no exception is raised; the duration of the sound is just set to zero, and nothing plays.
The only way I have found to work around this is to force a type by loading the data manually and using a QTDataReference:
NSURL *mp3URL = [NSURL URLWithString:#"http://foo.com/bar"];
NSData *mp3Data = [NSData dataWithContentsOfURL:mp3URL];
QTDataReference *dataReference =
[QTDataReference dataReferenceWithReferenceToData:mp3Data
name:#"bar.mp3"
MIMEType:nil];
NSError *error = nil;
QTMovie *sound = [[QTMovie alloc] initWithDataReference:dataReference error:&error];
[sound play];
However, this forces me to completely download ALL of the MP3 synchronously before I can start playing it, which is obviously undesirable. Is there any way around this?
Thanks.
Edit
Actually, it seems that the path extension has nothing to do with it; the Content-Type is simply not being set in the HTTP header. Even so, the latter code works and the former does not. Anyone know of a way to fix this, without having access to the server?
Edit 2
Anyone? I can't find information about this anywhere, and Google frustratingly now shows this page as the top result for most of my queries...
Two ideas. (The first one being a bit hacky):
To work around the missing content type, you could embed a small Cocoa webserver that supplements the missing header field and route your NSURL over that "proxy".
Some Cocoa http server implementations:
http://code.google.com/p/cocoahttpserver/
http://cocoawithlove.com/2009/07/simple-extensible-http-server-in-cocoa.html
http://culturedcode.com/cocoa/
The second one would be, to switch to a lower level framework (From QTKit to AudioToolbox).
You'd need more code, but there are some very good resources out there on how to stream mp3 using AudioToolbox.
e.g.:
http://cocoawithlove.com/2008/09/streaming-and-playing-live-mp3-stream.html
Personally I'd go with the second option. AudioToolbox isn't as straightforward as QTKit but it offers a clean solution to your problem. It's also available on both - iOS and Mac OS - so you will find plenty of information.
Update:
Did you try to use another initializer? e.g.
+ (id)movieWithAttributes:(NSDictionary *)attributes error:(NSError **)errorPtr
You can insert your URL for the key QTMovieURLAttribute and maybe you can compensate the missing content type by providing other attributes in that dictionary.
This open source project has a QTMovie category that contains methods to accomplish similar things:
http://vidnik.googlecode.com/svn-history/r63/trunk/Source/Categories/QTMovie+Async.m
If you thought weichsel's first solution was hacky, you're going to love this one:
The culprit is the Content-Type header, as you have determined. Had QTKit.framework used Objective-C internally, this would be a trivial matter of overriding -[NSHTTPURLResponse allHeaderFields] with a category of your choosing. However, QTKit.framework (for better or worse) uses Core Foundation (and Core Services) internally. These are both C-based frameworks and there is no elegant way of overriding functions in C.
That said, there is a method, just not a pretty one. Function interposition is even documented by Apple, but seems to be a bit behind the times, compared to the remainder of their documentation.
In essence, you want something along the following lines:
typedef struct interpose_s {
void *new_func;
void *orig_func;
} interpose_t;
CFStringRef myCFHTTPMessageCopyHeaderFieldValue (
CFHTTPMessageRef message,
CFStringRef headerField
);
static const interpose_t interposers[] __attribute__ ((section("__DATA, __interpose"))) = {
{ (void *)myCFHTTPMessageCopyHeaderFieldValue, (void *)CFHTTPMessageCopyHeaderFieldValue }
};
CFStringRef myCFHTTPMessageCopyHeaderFieldValue (
CFHTTPMessageRef message,
CFStringRef headerField
) {
if (CFStringCompare(headerField, CFSTR("Content-Type"), 0) == kCFCompareEqualTo) {
return CFSTR("audio/x-mpeg");
} else {
return CFHTTPMessageCopyHeaderFieldValue(message, headerField);
}
}
You might want to add logic specific to your application in terms of handling the Content-Type field lest your application break in weird and wonderful ways when every HTTP request is determined to be an audio file.
Try replacing http:// with icy://.
Just create an instance like this...
QTMovie *aPlayer = [QTMovie movieWithAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
fileUrl, QTMovieURLAttribute,
[NSNumber numberWithBool:YES], QTMovieOpenForPlaybackAttribute,
/*[NSNumber numberWithBool:YES], QTMovieOpenAsyncOKAttribute,*/
nil] error:error];