NSURLSessionDownloadTask switching from http to https - ios

I have an AFHTTPSessionManager created NSURLSessionDownloadTask being used to download a video within an app that sometimes when initialized with an http:80 url will convert it to https:443. This is happening before any connection attempt is being made (I added a custom HTTP protocol class via NSURLSessionConfiguration to the session in order to log when the connection is being made).
By the time the request makes it to the
-(NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
delegate method of my CustomHTTPProtocolDelegate class, the request has been changed to https.
App Transport Security is disabled (NSAllowsArbitraryLoads=true) and this behavior seems to be associated with a particular http-only server (other http-only server have no issue, and the connection is made as http on port 80).
Any idea of what could be going on? Anything else I could do to debug?
Here is how the download task is being created (including the debug custom protocol class):
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionConfiguration* config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
config.protocolClasses = #[[CustomHTTPProtocol class]];
AFHTTPSessionManager *session = [[AFHTTPSessionManager manager] initWithSessionConfiguration:config];
self.downloadTask = [session downloadTaskWithRequest:request progress:&progress destination:^NSURL *(NSURL *targetPath, NSURLResponse *response){
...
[UPDATE]
This issue is causing us a number of headaches, so to help facilitate troubleshooting, I created a small test project to help narrow in on the problem. My simple project does two things, loads a url into a UIWebView and downloads a file using NSURLSessionDownloadTask. The urls for these actions follow this pattern:
WebView URL: https://console.company.com/home.html
Download URL: http://data.company.com/file.txt
And those hostnames resolve to different IPs (different servers).
If I download the file before navigating the webview, then everything is fine, but if the webview loads its URL first, then the download URL will be switched to HTTPS automatically and the initial request for data will fail. One thought we had was that once iOS opens a TLS tunnel for the HTTPS connection that the webview is creating, that it tries to use that same tunnel for all subsequent *.company.com connections. Or at the very least, it assumes all *.company.com connections must also be TLS.

Figured it out. Both servers were sending a HSTS header for all subdomains. Because the networking layer under NSURLSession observes this header, the calls to the HTTP server were being upgraded to HTTPS prior to leaving the client.

Related

URLSession background upload task keeps resetting

I am experiencing some weird behaviour when using an uploadTask for a URLSessionConfiguration.background.
My custom delegate is implementing all of the delegate methods that belong to URLSessionDelegate, URLSessionTaskDelegate, and URLSessionDataDelegate. All of them has a print statement indicating that the method has been called.
I am trying to upload five images to a server, each of them has their own session with an id matching the image id.
The problem is that when uploading using a very slow connection "edge", the upload progress will reset before reaching 100% This happens whenever didFinishCollectingMetrics is called as you can see here: Data
This does not happen all the time when using a slow connection but only some of the time.
Anyone got any ideas as to what is happening here?
Edge networking is notoriously unreliable, and frequent upload failures are not atypical. The way you solve this is by replacing whole-file-based uploads with some form of chunked uploads so that you can continue the upload where you left off, but that requires server support.
Increase time-out of NSURLSession for request and resource:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
[configuration setTimeoutIntervalForRequest:120];
[configuration setTimeoutIntervalForResource:240];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
Use session to upload your image

NSURLConnection randomly fails until device reboot

I have been struggling with an issue where NSURLConnection calls instantly fail. The device needs to be rebooted entirely or Flight Mode needs to be toggled on/off to resolve the problem. Restarting the app (swipe up) alone does not help.
Some facts:
-All URLs are HTTPS, TLS 1.2 compatible with Forward Secrecy. There are no issues with ATS and iOS 9. The error has been present since iOS 7 and remains on 9.2.
-No third party frameworks are used by the app. I use only native NSURLConnection calls that always work, except for when this odd situation occurs.
-No infrastructure/network issues - other devices on same networks (same WiFi for instance) work in the same app at the same time. Going to/from 3G/Wifi makes no difference.
-I always implement willCacheResponse to return nil.
-The service is hosted on AWS Elastic Beanstalk, so some suggested that it might be a DNS caching issue in case of IP address changes - this seems unlikely to me and should trigger multiple errors at once on different devices, which I have never seen.
-The method called is didFailWithError, instantaneously, as if there were no Internet connection on the device at all - all other apps work, however.
-The website that hosts the API used by the app can be browsed with no problems at all times. The website actually makes the same requests to fetch data.
The error code returned is -1003, kCFURLErrorCannotFindHost. I've been following a thread on Git dealing with the same issue to no avail. https://github.com/AFNetworking/AFNetworking/issues/967
I tried using NSURLRequestReloadIgnoringCacheData for all my requests, but that did not help.
With this information, will anyone care to venture a guess what I might be doing wrong? I added the bounty because I have no idea how to approach this problem - especially because it's so inconsistent. And it is definitely not a legitimate error (that is, that the domain could not be found), as the service is operating fine while this happens on random clients.
I create my request with a static method that looks like this. It's been stripped of some non-public info, but basically it just performs a POST request with JSON data. [Controller getSQLHost] just returns a URL - the base domain.
+(NSURLConnection*)initiatePOSTwithJSONDictionary:(NSDictionary*)dictionary toURL:(NSString*)urllocation withDelegate:delegate {
NSMutableDictionary *connectionDictionary = [[NSMutableDictionary alloc] init];
if (dictionary) {
[connectionDictionary setObject:dictionary forKey:#"input"];
}
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:connectionDictionary options:kNilOptions error:nil];
NSURL *url = [NSURL URLWithString:[[Controller getSQLHost] stringByAppendingString:urllocation]];
NSString *postLength = [NSString stringWithFormat:#"%i", (int)[jsonData length]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30.0];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setHTTPBody:jsonData];
return [[NSURLConnection alloc] initWithRequest:request delegate:delegate];
}
Does you delegate implement connectionShouldUseCredentialStorage ? (or respond with YES)
I think the device's keychain is used when this method returns yes which may explain the persisting failure beyond the life time of the running application and why rebooting or otherwise resetting network connectivity "fixes" it. If an authentication failure has been recognized once, it may linger in the key chain for a little while which would then respond immediately without actually going to the server.
What would cause the authentication to register as a failure in the keychain in the first place may depend on a variety of factors. It could be as simple as a typo in the saved password or more convoluted such as some certificate expiration preventing the SSL layer from establishing a secure link.
You're creating NSURLConnections on the current runloop. How many are active at any one time? Have you considered using an NSOperationQueue so that you don't get bitten by load bugs?
Is your delegate thread-safe? If not, that could explain the sporadic-ness of the bug.
You say you don't see the problem often, but others do. Can you borrow their devices and maybe even them and their usage patterns and thus get to see the problem more often?

ios FTP upload using NSURLSession

I am trying to upload files via FTP to server. According to
Apple Documentation NSURLSession class supports FTP operations.
There is a famous Apple Developer blog which also supports that. But still its not clear whether NSURLSession API's supports ftp upload or not? (I tried with the way suggested and getting error).
With the conventional way of CFFTPStreamRef, ftp upload works fine but it's deprecated in 9.0. The header says: CF_DEPRECATED(10_3, 10_11, 2_0, 9_0 , "Use NSURLSessionAPI for ftp requests")
Any idea, example or link to get help with. I am trying something like this for now:
NSURL *url_upload = [NSURL URLWithString:#"ftp://username:password#thelink/myfolder/filename.zip"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url_upload];
[request setHTTPMethod:#"PUT"];
NSURL *docsDirURL = [NSURL fileURLWithPath:filePath];
NSURLProtectionSpace * protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:url_upload.host port:[url_upload.port integerValue] protocol:url_upload.scheme realm:nil authenticationMethod:nil];
NSURLCredential *cred = [NSURLCredential
credentialWithUser:userId
password:password
persistence:NSURLCredentialPersistenceForSession];
NSURLCredentialStorage * cred_storage ;
[cred_storage setCredential:cred forProtectionSpace:protectionSpace];
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.URLCredentialStorage = cred_storage;
sessionConfig.timeoutIntervalForRequest = 30.0;
sessionConfig.timeoutIntervalForResource = 60.0;
sessionConfig.allowsCellularAccess = YES;
sessionConfig.HTTPMaximumConnectionsPerHost = 1;
NSURLSession *upLoadSession = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
NSURLSessionUploadTask *uploadTask = [upLoadSession uploadTaskWithRequest:request fromFile:docsDirURL];
[uploadTask resume];
To the best of my recollection, NSURLSession (and NSURLConnection) support retrieving files via FTP, but do not support any other FTP commands, such as STOR (note that PUT is an HTTP method, not an FTP method).
For your purposes, your options are to either use the CFFTPStream API (which only barely works) or stop using FTP.
My strong recommendation would be to stop using FTP. The FTP protocol is hopelessly insecure, sending the username and password in cleartext form over the wire, which makes it really easy for people to sniff the credentials and masquerade as the user. So the only situation where an FTP upload would be even remotely acceptable these days is an anonymous FTP upload to a shared dropbox, and even then, it is somewhat dubious. That's why the functionality was never added to the NSURLConnection API, much less NSURLSession.
There are much better alternatives that are much more secure, such as WebDAV over HTTPS, POST request uploads over HTTPS, WebDAV or POST requests with digest authentication, etc. And those alternatives are actually supportable with NSURLSession and provide other advantages like the ability to resume downloads. Unless you have absolutely no way to change the server side, please use one of those alternatives instead.

AFNetworking lib not loading data in offline mode in iOS

I am developing a content reading app in which some data is displayed in a tale view and respective detail views.
I have already completed the app but there is a small bug.
I am using AFNetworking library for online data load and offline caching.
I have defined caching policy as described by following code:
Reachability *reach = [Reachability reachabilityWithHostname:#"google.com"];
if ([reach isReachable]) {
// Reachable
request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:60.0];
}
else{
request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReturnCacheDataDontLoad
timeoutInterval:60.0];
}
But, I am having a small problem that if I load some data online and then turn off the internet connection and close the app, and afterwords if I restart the app again in offline mode, the cached data should load but it is not happening.
I also have tried changing the caching policies as defined in this link.
app deployment target : iOS 6.0
devices : universal
xcode version : 6.1
AFNetworking lib version : 2.0
Replace all of the code you posted and just set the request cache policy to NSURLRequestUseProtocolCachePolicy.
As it stands, you're currently making a synchronous reachability query on every request. Either that or worse—it's returning immediately, but always returning before a real reachability state can be determined.

Switching from http to https. Invalid certificate

I have an app that connects to my home routers web interface. I want to convert this to use https instead of just http.
I was originally using ASIHttpRequest, but as it's no longer supported i'm switching over to AFNetworking.
The problem is, whenever I try to connect, I get this error message:
_block_invoke_0220 [Line 243] ERROR: Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “192.168.1.1” which could put your confidential information at risk." UserInfo=0x9792ad0 {NSErrorFailingURLStringKey=https://192.168.1.1/Info.live.htm, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSErrorFailingURLKey=https://192.168.1.1/Info.live.htm, NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to be “192.168.1.1” which could put your confidential information at risk., NSUnderlyingError=0xa6a3560 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “192.168.1.1” which could put your confidential information at risk.", NSURLErrorFailingURLPeerTrustErrorKey=< SecTrustRef:
If I navigate to the url i safari, I get a message that Safari can't verify the identity.... and I have to click continue to carry on.
How can I achieve this? I don't really know anything about ssl or https unfortunately.
Here is the code i'm currently using:
NSString *urlString = #"https://192.168.1.1/";
NSURL *url = [NSURL URLWithString:urlString];
// Set authorization
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
[httpClient setAuthorizationHeaderWithUsername:user password:pass];
NSURLRequest *request = [httpClient requestWithMethod:#"POST" path:#"Info.live.htm" parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString *responceString = [operation responseString];
// NSLog(#"%#",responceString);
if ([self parseInfoLive:responceString])
[[NSNotificationCenter defaultCenter] postNotificationName:#"downloadsComplete" object:nil];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"ERROR: %#",error.description);
}];
[operation start];
For getting around the validity check of the host certificate, add the following code.
First add an interface for the setter method that is already within the SDK but not exposed into public:
#interface NSURLRequest(Private)
+(void)setAllowsAnyHTTPSCertificate:(BOOL)inAllow forHost:(NSString *)inHost;
#end
Now, whenever you are rendering a new request, invoke that setter:
[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[inURL host]];
Warning
Do not use this code for production but only while developing your app in cases where the certificate is not yet approved/submitted/installed. Typical would be the use of a development server that does not have a trusted certificate installed.
The use of this code will get your App rejected from distribution via iTunes as it uses a private API method.
For making sure that things work smoothly in a production environment, you will have to get a trusted SSL certificate for your host. There are various authoritative companies providing such thing. To mention at least one (there are MANY more), you could use GoDaddy.
Update (31st May 2013)
AFNetworking got updated to support invalid certificates out of the box, without using any private API's. Kudos to Peter Steinberger!
For enabling that feature, the most convenient solution is to add the following to your prefix header (.pch):
#ifdef DEBUG
#define _AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_
#endif
Once again, I can not emphasize enough that you should refrain from enabling that feature in production code - you would pretty much invalidate the entire point of SSL connections and render them vulnerable.
This URL from Apple documentation might help Check this link
In the above document read Introduction section.
I am not familiar with AFNetworking, but there is a solution here that works around the error you are seeing. Till's answer is reputed to keep you from being able to submit your app to the app store.

Resources