Well, that’s interesting. I tried again to reproduce the problem with DuckDuckGo without any success. Here’s what I did specifically:
In Xcode 8.2, I created a new test project from the iOS > Single View Application template.
I changed
ViewController.m
to the code pasted in below.In the
Info.plist
, I enabled NSAllowsArbitraryLoads
.I ran it on a device running iOS 10.2.1.
Here’s what got logged:
2017-02-23 09:05:05.543511 xxoi[12702:7784093] redirect from http://duckduckgo.com/ to https://duckduckgo.com/
2017-02-23 09:05:05.650945 xxoi[12702:7784097] challenge NSURLAuthenticationMethodServerTrust
2017-02-23 09:05:05.742295 xxoi[12702:7784097] response
2017-02-23 09:05:05.743510 xxoi[12702:7784093] done
However, I then retried with
http://github.com/
and that
does reproduce the problem:
2017-02-23 09:10:26.187070 xxoi[12724:7787629] challenge NSURLAuthenticationMethodServerTrust
2017-02-23 09:10:26.430530 xxoi[12724:7787633] response https://github.com/
2017-02-23 09:10:26.431890 xxoi[12724:7787632] done
Curiously, in that case I can then remove
NSAllowsArbitraryLoads
and it also works.
A quick check of the CFNetwork diagnostics log shows that the HTTP request never makes it to the ‘wire’. Somehow it’s getting rewritten to HTTPS before it’s seen by the core HTTP engine.
These two factoids (site-specific behaviour, never hitting the wire) got me thinking about HSTS. And some quick spelunking in the debugger reveals that this is, indeed, the culprit. GitHub has a built-in HSTS entry, so the HTTP request always gets rewritten as HTTPS. DuckDuckGo, OTOH, does not have a built-in HSTS entry, so the HTTP request hits the ‘wire’, triggers an HTTP redirect, which results in a
willPerformHTTPRedirection
delegate callback.
Finally, I suspect that you are seeing the problem with DuckDuckGo because your app has previously cached an HSTS requirement from that site. My app, OTOH, is brand new, so it’s relying on the built in HSTS requirements.
Interesting. It’s not obvious whether the HSTS rewrite should result in an
willPerformHTTPRedirection
delegate callback. I can see why you’d want it to, but it’d be a bit weird, architecturally, to synthesise an HTTP response in this case.
If you were the only client of these NSURLSession tasks then it would be easy to get around this problem by noticing the HTTPS URL in the response passed to the
didReceiveResponse
delegate callback. However, in your case UIWebView is the main client, and that complicates things.
I suspect the only way around this would be to have your NSURLProtocol subclass detect the HSTS rewrite and synthesise a redirect response to pass up to the web view.
Regardless of what else you do, you should file a bug about this oddity. In fact, it may make sense to file a suite of bugs, including:
Please post any bug numbers, just for the record.
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"
#import "ViewController.h"
@interface ViewController () <NSURLSessionDataDelegate>
@property (nonatomic, strong, readwrite) NSURLSession * session;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSURLSessionConfiguration * config = [NSURLSessionConfiguration defaultSessionConfiguration];
self.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
NSURL * url = [NSURL URLWithString:@"http://duckduckgo.com/"];
[[self.session dataTaskWithURL:url] resume];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler {
NSLog(@"redirect from %@ to %@", response.URL, request.URL);
completionHandler(request);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
NSLog(@"response");
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
NSLog(@"challenge %@", challenge.protectionSpace.authenticationMethod);
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
NSLog(@"done");
}
@end