Monday, February 25, 2008

Workaround for Workaround

Sometimes there are tiny little bugs in the API's that occasionally creep up in odd situations. These bugs generally only affect about a dozen people...ever. This is for the other 11 of you:

CFHTTPAuthenticationCreateFromResponse has a known bug. Basically if you create an empty response with CFHTTPMessageCreateEmpty, and then fill it with CFHTTPMessageAppendBytes, then attempting to create a CFHTTPAuthenticationRef from it causes a crash. The problem is explained in further detail here:
http://lists.apple.com/archives/macnetworkprog/2006/Oct/msg00028.html

Basically, the work around is the following code:



NSURL *requestURL = [(NSURL *)CFHTTPMessageCopyRequestURL(request) autorelease];
_CFHTTPMessageSetResponseURL(response, (CFURLRef)requestURL);
CFHTTPAuthenticationRef auth = CFHTTPAuthenticationCreateFromResponse(kCFAllocatorDefault, response);


I've used this code for a long time without ever a problem. But recently I had the "privilege" of writing a gateway server. It accepts requests from clients, and forwards the request to another server, then forwards the response from the server back to the client. Part of the purpose of this project was to automatically authenticate, and so I was using the above workaround.

...but it was crashing! Which confused me thoroughly because it works in other situations. Here's what I figured out:

If attempting to authenticate against a server using digest access authentication:
If you create both your request AND your response with CFHTTPMessageCreateEmpty and CFHTTPMessageAppendBytes, then you're unable to extract a full URL from your request. For example, CFHTTPMessageCopyRequestURL will only give you "/index.html". This seems to cause the problem, as the API wants the full "http://www.deusty.com/index.html". The workaround is to create a full URL, which can be done using the "Host" header as such:



NSURL *requestURL = [(NSURL *)CFHTTPMessageCopyRequestURL(request) autorelease];
if([requestURL host] == nil)
{
NSString *host = [(NSString *)CFHTTPMessageCopyHeaderFieldValue(request, CFSTR("Host")) autorelease];
NSURL *baseURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", host]];

requestURL = [NSURL URLWithString:[requestURL relativeString] relativeToURL:baseURL];
}
_CFHTTPMessageSetResponseURL(response, (CFURLRef)requestURL);
CFHTTPAuthenticationRef auth = CFHTTPAuthenticationCreateFromResponse(kCFAllocatorDefault, response);


For the other 1 or 2 people in the world that ever stumble across this bug, I hope this helps.

4 comments:

Anonymous said...

I spent yesterday evening trying to figure out the first bug, and you just saved me from the second. Hopefully the other person in the world encountering this will find your blog. Thanks!

Rod

Mike Abdullah said...

Thanks for this post. I'm not sure the second bug you describe is actually a bug as such, but pointing out the first bug was massively helpful to me as I spent a good hour wondering why my every attempt to create an CFHTTPAuthentication object failed.

Sascha said...

Hi Guys,

did anyone know if there is a third workaround for this fix?
I tried to submit an iPhone application to apple and they
refuse it cause of the use of private methods.

Thank in advance,
Sascha

Eelco said...

Thank you so much!

I'm apparently one of the dozen, rolling a custom HTTP client and creating my own CFHTTPMessageRefs from scratch :)

Currently (december 2011) it doesn't crash anymore, but I wish it still did, because I was utterly confused why CFHTTPAuthenticationCreateFromResponse didn't work. An exception would have been nice...