Swift URL returns nil when the url contains an internationalized domain name (IDN) - swift

I was wondering why this piece of code always exit with 1:
import Foundation
// an idn domain:
let uLabel = "համընդհանուր-ընկալում-թեստ.հայ"
let urlStr = "https://" + uLabel
guard let url = URL(string: urlStr) else { exit(1) }
exit(0)
Since Apple's browser Safari does support well IDN domains, I was surprised their URL library does not... I tried to urlencode the string beforehand, but it is not helping.
======EDIT======
After fixing the piece above upon Matt's suggestion, I faced another problem during fetching the website data:
import Foundation
let uLabel = "համընդհանուր-ընկալում-թեստ.հայ"
let scheme = "https"
var comps = URLComponents()
comps.scheme = scheme
comps.host = uLabel
guard let url = comps.url else { exit(1) }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let rawContent = data else { exit(1) }
guard let content = String(data: rawContent, encoding: String.Encoding.utf8) else { exit(1) }
if content.contains("UASG Testbed Landing Page") {
// successfully fetch content of the page
exit(0)
} else {
// error during fetching
exit(1)
}
}
task.resume()
RunLoop.main.run()
The program still exits with 1. It seems the domain is not converted to an A-LABEL as it is on Safari, as the error suggests (the certificate is valid, the error is misleading):
NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to be “համընդհանուր-ընկալում-թեստ.հայ” which could put your confidential information at risk., NSErrorFailingURLKey=https://%d5%b0%d5%a1%d5%b4%d5%a8%d5%b6%d5%a4%d5%b0%d5%a1%d5%b6%d5%b8%d6%82%d6%80-%d5%a8%d5%b6%d5%af%d5%a1%d5%ac%d5%b8%d6%82%d5%b4-%d5%a9%d5%a5%d5%bd%d5%bf.%d5%b0%d5%a1%d5%b5/,

I don't know why you're having trouble, but rule number one is never never never call URL(string). Use URLComponents. That's what it's for.
let uLabel = "համընդհանուր-ընկալում-թեստ.հայ"
let scheme = "https"
var comps = URLComponents()
comps.scheme = scheme
comps.host = uLabel
let url = comps.url // works for me

Until, someone can find a way to tell the URL framework to correctly represent the URL "https://համընդհանուր-ընկալում-թեստ.հայ", it is possible to translate the domain part to an A-LABEL before passing it to the URL constructor and elude its wrong internal representation:
import Foundation
import IDNA
// an idn domain:
let uLabel = "համընդհանուր-ընկալում-թեստ.հայ"
guard let aLabel = uLabel.idnaEncoded else { exit(1) }
let supportedUrl = "https://" + aLabel
guard let url = URL(string: supportedUrl) else { exit(1) }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let rawContent = data else { exit(1) }
guard let content = String(data: rawContent, encoding: String.Encoding.utf8) else { exit(1) }
if content.contains("UASG Testbed Landing Page") {
// successfully fetch content of the page
exit(0)
} else {
// error during fetching
exit(1)
}
}
task.resume()
RunLoop.main.run()
This piece of code does exit 0. The IDNA library is there (ensure you take the master branch, because released versions are still on IDNA2003):
https://github.com/Wevah/IDNA-Cocoa

Related

Cannot write Data to file in Swift

I have an image that I want it's data to be saved in this fileUri generated by ("react-native-fs"). LibraryDirectoryPath/saved_images/{filename}:
/Users/macbookpro/Library/Developer/CoreSimulator/Devices/9CBD2F1E-7330-418D-81BE-108C064DEA7E/data/Containers/Data/Application/C26348CC-3463-43EF-9B26-B7E31641E2EA/Library/saved_images/6B3A6A3A-8DE3-488B-AF43-A54775545B38.jpg
And below is my implementation:
do {
let url = URL(string: fileUri)
let fileExisted = FileManager().fileExists(atPath: url!.path)
if (fileExisted) {
try decryptedData.write(to: url!)
} else {
let handle = try FileHandle(forWritingTo: url!)
handle.write(data) // data is type Data
handle.closeFile()
}
} catch {
reject("FileError", "Failed to write file", error)
}
I also tried let url = URL(fileURLWithPath: fileUri) with and without file:// prepending to fileUri
do {
let url = URL(fileURLWithPath: fileUri)
let fileExisted = FileManager().fileExists(atPath: url.path)
if (fileExisted) {
try decryptedData.write(to: url)
} else {
let handle = try FileHandle(forWritingTo: url)
handle.write(data)
handle.closeFile()
}
} catch {
reject("FileError", "Failed to write file " + error.localizedDescription, error)
}
it says:
You are using the wrong API.
let url = URL(string: fileUri)
is for strings representing a full – even encoded - URL starting with a scheme like file:// or https://.
On the other hand fileUri is actually a path without a scheme, so you have to use
let url = URL(fileURLWithPath: fileUri)
This returns a non optional URL by adding the file:// scheme.
fileUri should be renamed as filePath.

cannot read data from Json hosted on GitHub

when try to read data from a json on my GitHub space, I get an error
nw_protocol_get_quic_image_block_invoke dlopen libquic failed
Invalid response from the server. Please try again.
am I using wrong url or there is an issue in the way I'm parsing data?
my repo url
https://github.com/stark226/stark226.github.io.git
where there is a simple json file at this url
https://github.com/stark226/stark226.github.io/blob/3b2bebb4a3d85524732c7e7ec302b24f8d3e66ae/testjson.json
in my viewDidload
getDataFromJsonOnGithub(completed: { [weak self] result in
guard let self = self else {return}
switch result {
case .success(let expected):
print(expected)
case .failure(let error):
print(error.rawValue)
}
})
my struct
struct RemoteData: Codable, Hashable {
var tokenDuration: Int
}
my func
func getDataFromJsonOnGithub(completed: #escaping (Result<RemoteData, ListOfErrors>) -> Void) {
let endpoint = "https://github.com/stark226/stark226.github.io/stark226/testjson.json"
guard let url = URL(string: endpoint) else {
completed(.failure(.invalidUsername))
return
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let _ = error {
completed(.failure(.unableToComplete))
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
completed(.failure(.invalidResponse))
return
}
guard let data = data else {
completed(.failure(.invalidData))
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let expectedRrsult = try decoder.decode(RemoteData.self, from: data)
completed(.success(expectedRrsult))
} catch {
completed(.failure(.invalidData))
}
}
task.resume()
}
enum ListOfErrors: String, Error {
case invalidUsername = "This username created an invalid request. Please try again."
case unableToComplete = "Unable to complete your request. Please check your internet connection"
case invalidResponse = "Invalid response from the server. Please try again."
case invalidData = "The data received from the server was invalid. Please try again."
case unableToFavorite = "There was an error favoriting this user. Please try again."
case alreadyInFavorites = "You've already favorited this user. You must REALLY like them!"
}
You have to access the raw file. The URL you are accessing renders an HTML page. Try the following url (click on "Raw" button in GitHub):
https://raw.githubusercontent.com/stark226/stark226.github.io/3b2bebb4a3d85524732c7e7ec302b24f8d3e66ae/testjson.json

Swift: How to use HTTPS proxy

I have a paid proxy (HTTP/HTTPS/SOCKS).
I can make requests with HTTP scheme, but can't with HTTPS.
I always get en error:
Error Domain=kCFErrorDomainCFNetwork Code=311 "(null)" UserInfo={_NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <69F444DF-8C0E-4F4B-B723-C7BCD72B6C02>.<1>, _kCFStreamErrorDomainKey=4, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask <69F444DF-8C0E-4F4B-B723-C7BCD72B6C02>.<1>"
), _kCFStreamErrorCodeKey=-2097}
I need to get and parse a few web pages from one concrete domain. I'm trying to use HTTP scheme, but it also fails (i guess due to redirect to HTTPS)
So, my goal is make HTTPS request via proxy server.
My code below:
Session configuration:
private func getProxySessionConfigration() -> URLSessionConfiguration {
let login = ConfigurationData.proxy.login
let password = ConfigurationData.proxy.password
let sessionConfiguration = URLSessionConfiguration.default
let userPasswordString = "\(login):\(password)"
let userPasswordData = userPasswordString.data(using: String.Encoding.utf8)
let base64EncodedCredential = userPasswordData!.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0))
let authString = "Basic \(base64EncodedCredential)"
sessionConfiguration.httpAdditionalHeaders = ["Authorization" : authString]
sessionConfiguration.connectionProxyDictionary = [
"HTTPEnable": true,
"HTTPPort": ConfigurationData.proxy.port,
"HTTPProxy": ConfigurationData.proxy.host,
"HTTPSEnable": true,
"HTTPSPort": ConfigurationData.proxy.port,
"HTTPSProxy": ConfigurationData.proxy.host
]
return sessionConfiguration
}
Request function:
public func getPage(_ url: URL, completion: #escaping (String?, RequestError?) -> Void) {
let session = URLSession(configuration: getProxySessionConfigration())
let task = session.dataTask(with: url) { (data, response, error) in
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
if let error = error {
completion(nil, .requestFailed(error))
} else {
completion(nil, .unknown(response))
}
return
}
if let data = data {
print(httpResponse)
if let urlContent = NSString(data: data, encoding: String.Encoding.utf8.self.rawValue) {
completion(urlContent as String, nil)
} else {
completion(nil, .dataDecodeFailed(data))
}
} else {
completion(nil, .noData)
}
}
task.resume()
}
Function call:
let requestAvito = AvitoProjectPlaygroud_Sources.RequestAvito()
let url = URL(string: "https://www.avito.ru/krasnodar/vakansii/voditel_v_yandeks_taksi_zarabotok_ot_1388074044")!
requestAvito.getPage(url) { (htmlString, error) in
if let error = error {
switch error {
case .noData: print("ERROR: no data")
case .unknown(let response):
print("ERROR: unknown error")
if let response = response { print(response) }
case .urlFailed: print("ERROR: url generating failed")
case .requestFailed(let returnedError): print("ERROR: \(returnedError)")
case .dataDecodeFailed(let data):
print("ERROR: data decoding failed")
print(data!)
}
return
}
if let htmlString = htmlString {
// print(htmlString)
print(String(htmlEncodedString: htmlString))
} else {
print("NO DATA")
}
}
For your issue first of all (Before ANYTHING else, make sure your proxy handle HTTPS proxying), if no then does not matter the code you put in.
To handle HTTPS Proxying there is 2 keys to use that it seems you are using it. One thing I can see is that you are using true as boolean, etc.
As per my experience I always used value such numeric or string lateral.
Make sure that data is passed as String / Int (true -> 1) etc. That could help.
Sometimes it can make some issues with those assumptions that true "will" be converted as 1.
Here below an example on how I use it:
let request = URLRequest(url: URL(string: url)!)
let config = URLSessionConfiguration.default
config.requestCachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
config.connectionProxyDictionary = [AnyHashable: Any]()
config.connectionProxyDictionary?[kCFNetworkProxiesHTTPEnable as String] = 1
config.connectionProxyDictionary?[kCFNetworkProxiesHTTPProxy as String] = "xxx.yyy.zzz.bbb"
config.connectionProxyDictionary?[kCFNetworkProxiesHTTPPort as String] = 8888
config.connectionProxyDictionary?[kCFStreamPropertyHTTPSProxyHost as String] = "xxx.yyy.zzz.bbb"
config.connectionProxyDictionary?[kCFStreamPropertyHTTPSProxyPort as String] = 8888
So in short :
Verify first your proxy (making sure https proxying is handled)
and then pass data as above
it may solve your issue hopefully.

Why is Xcode complaining when I wrap this let in an if statement?

I have the following working code in my app:
func downloadJSON(completed: #escaping ([JsonFile.JsonBonuses]?) -> ()) {
let url = URL(string: "http://example.com/ExampleData.json")!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error == nil, let data = data {
do {
let posts = try JSONDecoder().decode(JsonFile.self, from: data)
completed(posts.bonuses)
self.defaults.set(posts.meta.version, forKey: "jsonVersion")
print("URLSession did not fail")
print("JSON Version Set to \(posts.meta.version)")
} catch {
print("Can't decode JSON: \(error)")
}
} else {
print("downloadJSON completed")
completed(nil)
}
}.resume()
}
I am wanting to change that URL to a different one based on a UserDefaults setting. So I wrapped the let url in an if statement like this:
if devModeStatus == true {
let url = URL(string: "https://otherexample.com/Example2Data.json")!
} else if devModeStatus == false {
let url = URL(string: "http://example.com/ExampleData.json")!
} else {
print("Invalid Dev Status encountered!")
return
}
However when I do that, Xcode complains about "Use of unresolved identifier 'url'; did you mean 'erfl'?" on the line that says URLSession.shared.dataTask(with: url) { (data, response, error) in
I'm not sure why it is complaining about this change. I use that same if/else logic else where to print a status message at first load of this view, so I know the variable is correct.
Your url declaration dies within those if, else-if scopes. You need to declare your url first then modify it. Also, since devModeStatus is a boolean value, the else case will never be reached, so no need for third path. Update your code as following:
let url: URL
if devModeStatus {
url = URL(string: "https://otherexample.com/Example2Data.json")!
} else {
url = URL(string: "http://example.com/ExampleData.json")!
}

swift NSURL gets back an "unable to read data" message even with https://

I am trying to learn iOS following a course and they ask to do the following:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//var string1 = "http://www.google.com"
//var string1 = "https://www.weather-forecast.com/locations/San-Antonio/forecasts/latest"
//var url = NSURL(string: string1)
var url = NSURL(string: "https://google.com")
print(url)
if url != nil {
let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (data, response, error) -> Void in
var urlError = false
if error == nil {
var urlContent = NSString(data: data!, encoding: NSUTF8StringEncoding)
print(urlContent)
} else {
urlError = true
}
if urlError == true {
self.showError()
}
})
task.resume()
} else {
showError()
}
}
the app doesn't show any web page content and when debugging I find that the object for the url says that it is "unable to read data"
I have tried with http and https. I have tried with different web sites.
I have tried the address in the safari of the simulator and it loads.
Can someone tell me why is this not working
Thanks in advance.
gariva
You're using wrong encoding. The webpage you're trying to fetch (http://www.google.com/) uses ISO-8859-1.
I was able to reproduce your issue. Fetch worked when I changed encoding. Try this:
var urlContent = NSString(data: data!, encoding: NSISOLatin1StringEncoding)
For display web page you should use UIWebView element. Something like this:
let url = NSURL(string: "https://google.com")
let webView = UIWebView(frame: self.view.frame)
self.view.addSubview(webView)
webView.loadRequest(NSURLRequest(URL: url!))