NSURLSession treats http url as https, only on some devices - ios

I have an app running on a test server that doesn't support https. I added the ATS exceptions in the info.plist and it worked well on the devices I tested on.
Now some of my test flight users started complaining about the app not being able to connect to the server. One of the devices in my office started having this problem too, so I had the change to look into it.
I am making a server request like this:
let path = "http://myurl.com"
let request = NSMutableURLRequest(URL: NSURL(string: path)!)
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
let httpResponse = response as? NSHTTPURLResponse
print(httpResponse)
print(NSString.init(data: data!, encoding: NSUTF8StringEncoding)!)
})
task.resume()
Now when I look at the URL from the NSHTTPURLResponse it is printed as "https://myurl.com". Somehow it just added the 's' to it. This only happens on some devices, I can't seem to find a pattern.
When I open the https url in a browser I get redirected to some other project we're working on. When I look at the response data that's exactly what I see. So somehow on some devices when you enter a http url it just converts it to https or something...
So far it's only been on iOS9 devices, but that may very well be a coincidence. Does anyone have an idea why this happens and what to do about it?

Related

How to handle '€' in a password in a HTTP post request

I have an iOS app which sends a HTTP request for the login to our Webserver. The login basically works fine, but as soon as someone got a '€' in his password the login fails.
This bug only happens in the app. We also have a web application, which sends the same login request to the same webserver and I can perfectly log in when I do that in my browser, even if there is a '€' in my password.
Here's the function that generates the request:
func SignOn() {
var request = Helper.getURLRequest(str: str, method: "POST")
guard let httpBody = try? JSONEncoder().encode(Helper.Logon.init(domain: String(userDomain[0]), user: String(userDomain[1]), p: ""))else { return }
request.httpBody = httpBody
let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
urlSession.dataTask(with: request) { (data, response, error) in
do {
guard let data = data else { throw Helper.MyError.NoConnection }
Helper.isAuthenticated = try JSONDecoder().decode(Helper.Authentication.self, from: data)
task.leave()
} catch {
[...]
}
static func getURLRequest(str: String, method: String) -> URLRequest {
let url = URL(string: str)
var request = URLRequest(url: url!)
let loginString = "\(Helper.loggedOnUserWithDomain):\(Helper.loggedOnUserPassword)"
let loginData = loginString.data(using: String.Encoding.utf8)
let base64LoginString = loginData!.base64EncodedString()
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
request.httpMethod = method
request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
return request
}
SignOn() gets called as soon as the user presses the "login" button in the app. Username and password are stored in two variables in my Helper class.
SignOn() will then call a function that generates the request - also in my Helper class.
I double checked every step in getURLRequest(). loginString and loginData both keep the € and they are perfectly displaying the character when I let Xcode print the variables.
I then checked the base64 string. Let's say someone enters "t€stpassword". The encoded base64 string should be VOKCrHN0cGFzc3dvcmQ=, which the function got right. I then let the function decode the base64 string again and checked if "t€stpassword" was the result, which again was true.
Then I checked the request with HTTP interception, but it also had the '€' in his body.
I already tried to percent escape the '€' character but that does also not work. The '€' gets percent escaped correctly, but I think the web server can't handle it then, I don't really know tbh. I used this method: how to http post special chars in swift
I'm out of ideas what I'm doing wrong here. I'm pretty new to Swift so I don't want to rule out, that I'm missing something obvious. Could the web server be the issue? But as I said, the login is working when doing it in a browser, so the server cannot be the issue, right?
According "The 'Basic' HTTP Authentication Scheme" in RFC 7617, section 3:
3. Internationalization Consideration
User-ids or passwords containing characters outside the US-ASCII
character repertoire will cause interoperability issues, unless both
communication partners agree on what character encoding scheme is to
be used. Servers can use the new 'charset' parameter (Section 2.1)
to indicate a preference of "UTF-8", increasing the probability that
clients will switch to that encoding.
Furthermore,
For the user-id, recipients MUST support all characters defined in
the "UsernameCasePreserved" profile defined in Section 3.3 of
RFC7613, with the exception of the colon (":") character.
For the password, recipients MUST support all characters defined in
the "OpaqueString" profile defined in Section 4.2 of RFC7613.
The "recipient" here is the backend. The referenced RFCs in the cited paragraphs clearly describe how the backend should process the Unicode characters and how to perform the comparison operator. You might test the server against the specification to figure out whether the server behaves correctly.
The client however, should at least check for a semicolen in either the password or user-id which would be an invalid credential for Basic HTTP Authentication.
So, your code should work, unless the backend does not want to handle Unicode. If this is the case, only allow ASCII on the client side.
When the authentication fails, a server might message the expected charset in the response in the Authenticate header:
WWW-Authenticate: Basic realm="foo", charset="UTF-8"
However, specifying a charset parameter is "purely advisory". We can't rely on the server sending this.
Basic HTTP is what the name suggests: a basic authentication scheme. It has been deprecated for a while now.
If possible, use a more secure and a more resilient authentication scheme.

How to call REST API deployed in AWS on iOS app?

I am new to AWS and trying to implement a webservice API GET call on iOS app. So following is the postman I have and trying to implement the same in iOS app;
But I am confused in setting this header on URLSession requests. I am not seeing much documentations regarding this. Looking forward for some help in implementing this on iOS app.
Tried to implement the Swift code generated in Postman :
But this implementation returns a Forbidden error message , so i believe some different implementation is needed to connect AWS
Can you show us what you have tried with URLSession so far ?
As said by #burnsi, you show look at the raw headers that are working for you in Postman and try to replicate those.
Some headers should always be used for all requests (given a particular URLSession), so you should consider configuring your session using httpAdditionalHeaders:
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = [
"Accept-Encoding": "application/json",
"Content-Type": "application/json"
]
let session: URLSession = URLSession(configuration: configuration)
For headers that are request-specific or likely to change over time (like an authorization token), you should add them to the request itself using setValue(_:forHTTPHeaderField:):
var request: URLRequest = URLRequest(url: url)
request.setValue("XYZ", forHTTPHeaderField: "Authorization")
Then you should perform the request using:
session.dataTask(with: request, completionHandler: { (data, response, error) in
print("Data: \(data?.debugDescription)\nResponse: \(response?.debugDescription)\nError: \(error?.debugDescription)")
})
Let us know what this code prints out for you and I'll try to help more!

Timeout when offline iOS (using waitsForConnectivity)

Whilst implementing several HTTP calls, I noticed some strange behaviour on the URLSession. As far as I know, URLSession data tasks used to immediately throw an error when the network connection was unavailable (error -1009 The Internet connection appears to be offline).
I'm now performing network requests too, and while executing a request in flight mode, the request runs for as long as the timeout, before failing. I tried creating a custom configuration that should prevent this, but this does not work. How can I re-enable or configure the behaviour where a request will immediately crash when there is no connection?
let url = URL(string: "https://www.google.com/")!
let config = URLSessionConfiguration.default
config.waitsForConnectivity = false
config.timeoutIntervalForRequest = 2
config.timeoutIntervalForResource = 0
URLSession(configuration: config).dataTask(with: url) { d, r, e in
print(d?.count, (r as? HTTPURLResponse)?.statusCode, e?.localizedDescription)
}.resume()
The configuration documentation appears to be inconsistent or incorrect.

Alamofire request fails with nil response

I am getting numerous failed requests with Alamofire 5.3, where the response object itself is nil, or the error is "cannot parse response". I can see from the server logs that all of those requests are returning valid.
Here is my setup:
API manager class:
let config = Alamofire.Session.default.session.configuration
self.session = Alamofire.Session(configuration: config, interceptor: AccessTokenInterceptor())
AccessTokenInterceptor:
class AccessTokenInterceptor: RequestInterceptor {
func adapt(_ urlRequest: URLRequest, for session: Alamofire.Session, completion: #escaping (AdapterResult<URLRequest>) -> Void) {
var adaptedRequest = urlRequest
adaptedRequest.setValue("application/json", forHTTPHeaderField: "Accept")
adaptedRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
if let token = SettingsManager.shared.userToken {
adaptedRequest.setValue("Bearer " + token, forHTTPHeaderField: "Authorization")
}
completion(.success(adaptedRequest))
}
}
This interceptor inserts my auth token from SettingsManager
I am also using the standard router for URLRequestConvertible where encoding is done by JSON serialization (dictionary) or Codable protocol (objects)
case .login(let body):
request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])
case .register(let object):
request.httpBody = try JSONEncoder().encode(object)
What is strange is that I don't think I'm doing anything different from the many other times I've used Alamofire and now the first request I make fails but the following one succeeds. If I remove the interceptor, there is no change.
If I inspect the outgoing headers or body content, it all seems normal, but the response from Alamofire is nil.
UPDATE: By using OS_ACTIVITY_MODE and iOS 13 I was able to see that it was complaining about the request headers and protocol. The server is on Elastic Beanstalk so I've been trying to mess with the SSL policy but still the first request fails every time.
This turned into quite the rabbit hole, so in the interest of community improvement, here is what I found.
After searching through the activity log errors, I noticed that iOS was complaining about an invalid header type -- upgrade. Searching for that value I found this question about removing the header. I learned that Apache acts as a proxy on Elastic Beanstalk but there is a mix up for HTTP/2 headers in the request, and iOS does not like that.
To get away from the header value, I ended up switching to Nginx proxy. Since my application uses Laravel, I then needed to deal with correcting the pretty URLs. To do that I found this answer. Now my web and mobile application both seem to be getting along nicely.

GET and POST requests fail when going to my custom server

I have a linux based node.js server running on BlueHost that I have configured to respond to basic GET and POST requests. I have tested these requests extensively in Postman and they all seem to work great. I figured the transition to doing these requests from an app would not be too difficult but I am currently unable to get either of them to work.
I am using boilerplate code based on this repository. As an example here is the code I am using for the get request
guard let url = URL(string: "publicIP/test") else { print("Invalid url"); return }
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
if let res = response {
print(res)
}
if let d = data {
print(d)
do {
let json = try JSONSerialization.jsonObject(with: d, options: [])
print(json)
} catch {
print(error)
print("ERRORED")
}
}
}.resume()
This code works great for the https://jsonplaceholder.typicode.com/users endpoint however whenever I try substituting it with my public ip (just like in Postman) and I keep getting the following error.
2018-02-18 01:14:35.633518-0800 Application[1248:321560] Task <2E64810D-C10E-4D45-82F5-9C9E37A5FE54>.<1> finished with error - code: -1002
Keep in mind this information is not passed throughout the error parameter as "ERRORED" is not printed.
Can you add this to your info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
That error should not be related to using HTTP instead of HTTPS. App Transport Security failures return error code -1022.
The error code -1002 indicates an invalid URL. Perhaps your HTTP file contains a structurally invalid URL (e.g. missing scheme, a scheme other than http/https, etc.)?
For additional debugging, set this environment variable
CFNETWORK_DIAGNOSTICS=1
in your Xcode project and re-run the app. Once you know what URL is failing, the problem will likely become more obvious.
If it isn't, file a bug.
You can check the list of error code on below link
Apple Documentation NSURLErrorDomain Error Codes List
The issue was not any of the example code but merely a misunderstanding of what a url string should look like. I figured that I would need to match the url of the webpage as shown in the address bar of my web-browser but that is not exactly true because http does not show even if it is a part of the address in chrome.
The solution was simply to prepend http:// to the front of my url.

Resources