I coding with Swift 2.0.
I got my URL string like this:
let urlString = "http://example.com/api/getfile/?filepath=C:\\1.txt"
When I convert it into NSURL, it returns nil.
let OrginUrl = NSURL(string: urlString)
Anyone know how to do this?
There are two issues that need to be addressed:
In order to include a \ it must be escaped because it is itself the escape character.
'\' characters are not allowed in URLs so they need to be URL encoded
let urlString = "http://example.com/api/getfile/?filepath=C:\\\\1.txt"
print("urlString: \(urlString)")
var escapedString = urlString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
print("escapedString!: \(escapedString!)")
let orginUrl = NSURL(string: escapedString!)
print("orginUrl: \(orginUrl!)")
urlString:
http://example.com/api/getfile/?filepath=C:\\1.txt
escapedString!:
http%3A%2F%2Fexample.com%2Fapi%2Fgetfile%2F%3Ffilepath=C%3A%5C%5C1.txt
orginUrl:
http%3A%2F%2Fexample.com%2Fapi%2Fgetfile%2F%3Ffilepath=C%3A%5C%5C1.txt
You should use unicode instead of backslash twice.
stringByAddingPercentEscapesUsingEncoding is deprecated: Use stringByAddingPercentEncodingWithAllowedCharacters(_:) instead, which always uses the recommended UTF-8 encoding, and which encodes for a specific URL components or subcomponents since each URL component or subcomponent has different rules for what characters are valid.
Documentation Reference
Below is the example code:
let myLink = "http://example.com/api/getfile/?filepath=C:\u{005C}\u{005C}1.txt"
var newLink = ""
if let queryIndex = myLink.characters.indexOf("?"){
newLink += myLink.substringToIndex(queryIndex.successor())
if let filePathIndex = myLink.characters.indexOf("=")?.successor() {
newLink += myLink.substringWithRange(queryIndex.successor()...filePathIndex.predecessor())
let filePath = myLink.substringFromIndex(filePathIndex)
if let pathEscaped = filePath.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLPathAllowedCharacterSet()) {
newLink += pathEscaped
}
}
}
if let newURL = NSURL(string: newLink) {
print(newURL, separator: "", terminator: "")
} else {
print("invalid")
}
And result you will get:
"http://example.com/api/getfile/?filepath=C%3A%5C%5C1.txt"
Related
I am trying to add a suggestion/feedback section in my app. It's supposed to open a mail app with pre-populated text for the subject, body, and email address. It's working fine except the subject and body show the percent-encoding for space. I have searched a lot and it seems like an iOS 15 issue but I am not sure. This is my code:
private func createEmailUrl(to: String, subject: String, body: String) -> URL? {
let subjectEncoded = subject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let bodyEncoded = body.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let gmailUrl = URL(string: "googlegmail://co?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
let outlookUrl = URL(string: "ms-outlook://compose?to=\(to)&subject=\(subjectEncoded)")
let yahooMail = URL(string: "ymail://mail/compose?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
let sparkUrl = URL(string: "readdle-spark://compose?recipient=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
let defaultUrl = URL(string: "mailto:\(to)?subject=\(subjectEncoded)&body=\(bodyEncoded)")
if let gmailUrl = gmailUrl, UIApplication.shared.canOpenURL(gmailUrl) {
return gmailUrl
} else if let outlookUrl = outlookUrl, UIApplication.shared.canOpenURL(outlookUrl) {
return outlookUrl
} else if let yahooMail = yahooMail, UIApplication.shared.canOpenURL(yahooMail) {
return yahooMail
} else if let sparkUrl = sparkUrl, UIApplication.shared.canOpenURL(sparkUrl) {
return sparkUrl
}
return defaultUrl
}
And this is how I am calling the API
UIApplication.shared.open(url)
The Gmail app shows the subject as:
Reg:%2520Suggestions/Feedback%2520About%2520iOS%2520App
It wasn't an issue at all. I was sending an already encoded string and it was encoding % to %25. Hence all my encoded special characters were converted into %25__.
My bad. I must read the code properly before copy-pasting.
As I don't have enough rep points for comment for this question URL Encoding swift IOS, I created a new question. I would like to receive feedback on making it more swifty for use cases like this one.
My understanding of OP question is inside API response or from data, there is multiple links of URLs for image, with possibility of different host domains.
In such cases, the host URL could be first retrieved like in this answer. In similar way, the path can also be retrieved separately.
The path can be encoded like as below and concat back to host string later.
addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
This is what I initially tried out in playground.
import Foundation
extension String {
var encoded: String? {
return self.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)
}
}
let urlStrings:[String] = ["https://example.com/test.jpg"
"https://example.cc/test2.jpg",
"https://example.dev.cc/test3.jpg"]
var encodedUrlStrings: [String] = []
urlStrings.forEach { urlString in
print(urlString)
guard let url = URL(string: urlString),
let domain = url.host,
let encodedPath = url.path.encoded else { return }
let encodedUrlString = domain + encodedPath
encodedUrlStrings.append(encodedUrlString)
}
encodedUrlStrings.count
encodedUrlStrings.forEach { print($0) }
But the weird thing I notice from OP's question is the url itself should not be already including character like space or any unicode character.
https://ftximages.apk.in/test Fretailer_Point-6685.jpg
For such string, converting into URL will always return nil with URL(String:) method. Same result for using URLComponents as well as the characters in path are still not usable in URL. So this need to make more agreements with backedn dev or anyone who's sending such URL string without encoded characters.
Just in case the backend dev is making a fuss, following codes will gives example of encoded URL strings.
String extension from #Claudio's answer.
import Foundation
extension String {
var encoded: String? {
return self.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)
}
}
let urlStrings:[String] = ["https://example.com.cc/folder/test.jpg",
"https://example.com/test.jpg",
"https://Ftximages.apk.in/test Fretailer_Point-6685.jpg",
"https://example.com/원 더 우먼.jpg",
"https://example.dev.cc/刺客聶隱娘-6685.jpg",
"https://example.cc/बाजीराव मस्तानी.jpg"]
var encodedUrlStrings: [String] = []
func encodeURL(scheme: String, host: String, path: String) -> String? {
var components = URLComponents()
components.scheme = scheme
components.host = host
components.path = path
return components.url?.absoluteString ?? URL(string: "\(scheme)://\(host)/\(path)")?.absoluteString
}
func encodeWeirdUrlString(_ urlString: String) -> String? {
var urlSplitStrings = urlString.components(separatedBy: ["/"])
urlSplitStrings.removeAll(where: { $0.isEmpty })
guard urlSplitStrings.indices.contains(0), urlSplitStrings.indices.contains(1) else { return nil }
let scheme = String(urlSplitStrings[0].dropLast())
let host = urlSplitStrings[1]
urlSplitStrings.remove(at: 1)
urlSplitStrings.remove(at: 0)
guard let path = urlSplitStrings.joined(separator: "/").encoded else { return nil}
let encodedUrlString = encodeURL(scheme: scheme, host: host, path: path)
return encodedUrlString
}
urlStrings.forEach { urlString in
print(urlString)
guard let url = URL(string: urlString),
let scheme = url.scheme,
let host = url.host,
let encodedPath = url.path.encoded,
let encodedUrlString = encodeURL(scheme: scheme, host: host, path: encodedPath) else {
if let encodedUrlString = encodeWeirdUrlString(urlString) {
encodedUrlStrings.append(encodedUrlString)
}
return
}
encodedUrlStrings.append(encodedUrlString)
}
encodedUrlStrings.count
encodedUrlStrings.forEach { print($0) }
Are there any improvements to make this solution more swifty?
When passing the valid url of the string with the init the URL(string: urlString) returns nil. How to fix? When I enter a value from one word (for example, Moscow) everything works, when a city from two words is entered, I separate the request using split(separator: " ") and connect using .joined(separator: "%20") and get the city divided by %20 and return nil. How can this problem be solved?
enter image description here
enter image description here
You need to do addingPercentEncoding see my extension for that
extension String {
var url: URL? {
guard !isEmpty else { return nil }
if let url = URL(string: self) {
return url
} else if let urlEscapedString = addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let escapedURL = URL(string: urlEscapedString) {
return escapedURL
}
return nil
}
}
Use the apiKey after encoding it:
let encodedKey = key.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
let testurl = "http://akns-
images.eonline.com/eol_images/Entire_Site/2018029/rs_1024x759-
180129200032-1024.lupita-nyongo-angela-bassett-black-panther-
premiere.ct.012918.jpg?fit=inside|900:auto"
if let url = URL(string: testurl){
print("valid")
}else {
print("invalid")
}
This prints as an invalid URL. But shows the image in web browser. I have seen lot of methods but it throws Apple warning to use stringByAddingPercentEncodingWithAllowedCharacters and this doesn't work always.
I would prefer a solution to clean-up the url without encoding the complete urlstring. Encoding it at initial step can be a friction when passing to external libraries which will re-encode the url and make it invalid.
Use URLComponents, it will handle escape characters automatically and encodes the url correct. no need to use addingPercentEncoding
func computeURL() {
let testurlStr = "http://akns-images.eonline.com/eol_images/Entire_Site/2018029/rs_1024x759-180129200032-1024.lupita-nyongo-angela-bassett-black-panther-premiere.ct.012918.jpg?fit=inside|900:auto"
let components = transformURLString(testurlStr)
if let url = components?.url {
print("valid")
} else {
print("invalid")
}
}
func transformURLString(_ string: String) -> URLComponents? {
guard let urlPath = string.components(separatedBy: "?").first else {
return nil
}
var components = URLComponents(string: urlPath)
if let queryString = string.components(separatedBy: "?").last {
components?.queryItems = []
let queryItems = queryString.components(separatedBy: "&")
for queryItem in queryItems {
guard let itemName = queryItem.components(separatedBy: "=").first,
let itemValue = queryItem.components(separatedBy: "=").last else {
continue
}
components?.queryItems?.append(URLQueryItem(name: itemName, value: itemValue))
}
}
return components!
}
URL encoding is compulsory for building a URL from a string in case it contains special characters.
Why we need URL encoding
From this answer:
A URI is represented as a sequence of characters, not as a sequence of octets. That is because URI might be "transported" by means that are not through a computer network, e.g., printed on paper, read over the radio, etc.
And
For original character sequences that contain non-ASCII characters, however, the situation is more difficult. Internet protocols that transmit octet sequences intended to represent character sequences are expected to provide some way of identifying the charset used, if there might be more than one [RFC2277]. However, there is currently no provision within the generic URI syntax to accomplish this identification. An individual URI scheme may require a single charset, define a default charset, or provide a way to indicate the charset used.
In the IOS SDK we have the following:
User: URLUserAllowedCharacterSet
Password: URLPasswordAllowedCharacterSet
Host: URLHostAllowedCharacterSet
Path: URLPathAllowedCharacterSet
Fragment: URLFragmentAllowedCharacterSet
Query: URLQueryAllowedCharacterSet
You can use addingPercentEncoding(withAllowedCharacters:) to encode a string correctly.
One important note according to the apple docs for that method:
Entire URL strings cannot be percent-encoded, because each URL component specifies a different set of allowed characters. For example, the query component of a URL allows the “#” character, but that character must be percent-encoded in the password component.
UTF-8 encoding is used to determine the correct percent-encoded characters. Any characters in allowedCharacters outside of the 7-bit ASCII range are ignored.
An example:
let encodedURL = testurl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
if let url = URL(string: finalUrl) {
print("valid url")
} else {
print("invalid url")
}
I hope this is helpful.
I think I have found my answer. Still will be checking for any side effects, comments are welcome:
let urlst = "http://akns-
images.eonline.com/eol_images/Entire_Site/2018029/rs_1024x759-
180129200032-1024.lupita-nyongo-angela-bassett-black-panther-
premiere.ct.012918.jpg?fit=inside|900:auto"
extension String {
func getCleanedURL() -> URL? {
guard self.isEmpty == false else {
return nil
}
if let url = URL(string: self) {
return url
} else {
if let urlEscapedString = self.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) , let escapedURL = URL(string: urlEscapedString){
return escapedURL
}
}
return nil
}
}
This does not encode the complete URL but encodes only the invalid query characters (in eg url its '|'). Thus it can still be passed around as a string or a valid URL.
Based on your own answer, I would even simplify it further by making it an extension of URL instead of String, that way we can still use an initialiser which is similar to URL(string:) with an identical output:
extension URL {
init?(_ string: String) {
guard string.isEmpty == false else {
return nil
}
if let url = URL(string: string) {
self = url
} else if let urlEscapedString = string.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed),
let escapedURL = URL(string: urlEscapedString) {
self = escapedURL
} else {
return nil
}
}
}
Please replace your line with:
let finalUrl = testurl.addingPercentEncoding( withAllowedCharacters: .urlUserAllowed)
if let url = URL(string: finalUrl){
print("valid")
} else {
print("invalid")
}
I have the following code, but NSURL does not like the curly braces. It crashes. If I put an # symbol before the string after "format:" it does nothing. If I try to use \ to escape the braces, it doesn't work. How do I make this work?
func getUrlWithUpdateText(updateText: String!) -> NSURL {
let escapedUpdateText = updateText.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
let urlString = String(format: "http://localhost:3000/api/Tests/update?where={ \"name\": %# }", escapedUpdateText)
let url = NSURL(string: urlString)
return url!
}
I realize there's another similar thread, but it does not translate to this situation, for one thing this is Swift, not Objective-C.
Escape everything once it's constructed:
func getUrlWithUpdateText(updateText: String) -> NSURL? {
let toEscape = "http://localhost:3000/api/Tests/update?where={ \"name\": \(updateText) }"
if let escapedUpdateText = toEscape.stringByAddingPercentEncodingWithAllowedCharacters(
NSCharacterSet.URLHostAllowedCharacterSet()),
url = NSURL(string: escapedUpdateText) {
return url
}
return nil
}
Usage:
if let res = getUrlWithUpdateText("some text #%$") {
print(res)
} else {
// oops, something went wrong with the URL
}
As the braces are in a URL, you may be able to handle this by treating the braces as an HTML entity. See How do I decode HTML entities in swift? for more information.