Issue with encoding "&" in URLs - ios

I've got to send a https GET request to a web service in my iPhone app which is developing in Swift 1.2.
I am trying to construct query string parameters but got to encode them before send to server.
All good but not working when password contains '&' charcter. Expect to encode '&' character to '%26' but NOT working...
Just done a test when having '%'. Works as expected with '%' providing '%25'. But NOT convert '&' sign....
Tried following ways:
var testPassword1: String = "mypassword&1"
var testPassword2: String = "mypassword%1"
// Try to encode 'testPassword1'
testPassword1.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
testPassword1.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())!
// Try to encode 'testPassword2'
testPassword2.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
testPassword2.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())!
I've done the above tests and following are the response
Would like to know the correct way to do this. Thanks.

You should use NSURLComponents for your task.
Given a URL string, create a url-components:
let urlString = "http://example.com"
let urlComponents = NSURLComponents(string: urlString)!
Given a query parameter container (possibly a dictionary, or an array of (String, String?) tuple), create an array of NSURLQueryItems:
let queryParameters: [String: String?] = ["param": "az09-._~!$&'()*+,;=:#/?", "reserved": ":/?#[]#!$&'()*+,;="]
var queryItems = queryParameters.map { NSURLQueryItem(name: $0.0, value: $0.1) }
Append the query-component to the url-components:
urlComponents.queryItems = queryItems.count > 0 ? queryItems : nil
print(urlComponents.string!)
prints:
http://example.com?reserved=:/?%23%5B%5D#!$%26'()*+,;%3D&param=az09-._~!$%26'()*+,;%3D:#/?

I used such an utility method to URL-encode values in GET-requests:
#interface NSString (Ext)
#property (nonatomic, readonly) NSString *urlEncoded;
#end
#implementation NSString (Ext)
- (NSString *)urlEncoded {
NSMutableCharacterSet *const allowedCharacterSet = [NSCharacterSet URLQueryAllowedCharacterSet].mutableCopy;
// See https://en.wikipedia.org/wiki/Percent-encoding
[allowedCharacterSet removeCharactersInString:#"!*'();:#&=+$,/?#[]"]; // RFC 3986 section 2.2 Reserved Characters (January 2005)
NSString *const urlEncoded = [self stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
return urlEncoded;
}
#end

If you need to encode the & character, you can use the following:
var testPassword1: String = "mypassword&1"
testPassword1.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
testPassword1.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet(charactersInString: "&").invertedSet)!

Related

Swift: Add prefix (string) into a string

I'm looking for a String function that adds prefix string into an existing string.
The problem I've is: Sometimes, I get a URL string from web service response without keyword http:.
The general form of URL (URL string) should be: http://www.testhost.com/pathToImage/testimage.png
But sometimes I get //www.testhost.com/pathToImage/testimage.png from web service.
Now, I know that I can check, whether prefix http: is there in a string or not, but if there isn't then I need to add prefix into an existing URL string.
Is there any String (or substring or string manipulation) function that adds prefix into my URL string?
I tried into Apple document: String but couldn't find any help.
An alternate way I have is a concatenation of string.
Here is my code:
var imageURLString = "//www.testhost.com/pathToImage/testimage.png"
if !imageURLString.hasPrefix("http:") {
imageURLString = "http:\(imageURLString)" // or "http:"+ imageURLString
}
print(imageURLString)
But is there any standard way or iOS String default function that I can use here?
An alternative is URLComponents. This works with or without http
var urlComponents = URLComponents(string: "//www.testhost.com/pathToImage/testimage.png")!
if urlComponents.scheme == nil { urlComponents.scheme = "http" }
let imageURLString = urlComponents.url!.absoluteString
If "http:" + "example.com" doesn't suit you, you could write your own extension that does this:
extension String {
mutating func add(prefix: String) {
self = prefix + self
}
}
...or make it test the string before adding the prefix, to add it only if it doesn't exist yet:
extension String {
/**
Adds a given prefix to self, if the prefix itself, or another required prefix does not yet exist in self.
Omit `requiredPrefix` to check for the prefix itself.
*/
mutating func addPrefixIfNeeded(_ prefix: String, requiredPrefix: String? = nil) {
guard !self.hasPrefix(requiredPrefix ?? prefix) else { return }
self = prefix + self
}
}
Usage:
// method 1
url.add(prefix: "http:")
// method 2: adds 'http:', if 'http:' is not a prefix
url.addPrefixIfNeeded("http:")
// method 2.2: adds 'http:', if 'http' is not a prefix (note the missing colon which includes to detection of 'https:'
url.addPrefixIfNeeded("http:", requiredPrefix: "http")
There is nothing built in but you could do this in one line with a conditional assignment. See the following:
imageURLString = imageURLString.hasPrefix("http:") ? imageURLString : ("http:" + imageURLString)
I feel that this thread should be retitled to dealing more with URL String Manipulation. To return to prefixing Strings, you don't have to do this using an extension, but to use a Higher Order function (for collections)
Collections of Strings
let prefix = "Mr."
self.dataSource = myMaleFriends.map({ (friend) -> String in
return prefix + " " + friend
})
For prefixing a single word
var name = "Anderson"
name = name.withMutableCharacters({ (name) -> String in
return "Mr. " + name
})

How to encode URL string such that it encodes & and spaces [duplicate]

I've got to send a https GET request to a web service in my iPhone app which is developing in Swift 1.2.
I am trying to construct query string parameters but got to encode them before send to server.
All good but not working when password contains '&' charcter. Expect to encode '&' character to '%26' but NOT working...
Just done a test when having '%'. Works as expected with '%' providing '%25'. But NOT convert '&' sign....
Tried following ways:
var testPassword1: String = "mypassword&1"
var testPassword2: String = "mypassword%1"
// Try to encode 'testPassword1'
testPassword1.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
testPassword1.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())!
// Try to encode 'testPassword2'
testPassword2.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
testPassword2.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())!
I've done the above tests and following are the response
Would like to know the correct way to do this. Thanks.
You should use NSURLComponents for your task.
Given a URL string, create a url-components:
let urlString = "http://example.com"
let urlComponents = NSURLComponents(string: urlString)!
Given a query parameter container (possibly a dictionary, or an array of (String, String?) tuple), create an array of NSURLQueryItems:
let queryParameters: [String: String?] = ["param": "az09-._~!$&'()*+,;=:#/?", "reserved": ":/?#[]#!$&'()*+,;="]
var queryItems = queryParameters.map { NSURLQueryItem(name: $0.0, value: $0.1) }
Append the query-component to the url-components:
urlComponents.queryItems = queryItems.count > 0 ? queryItems : nil
print(urlComponents.string!)
prints:
http://example.com?reserved=:/?%23%5B%5D#!$%26'()*+,;%3D&param=az09-._~!$%26'()*+,;%3D:#/?
I used such an utility method to URL-encode values in GET-requests:
#interface NSString (Ext)
#property (nonatomic, readonly) NSString *urlEncoded;
#end
#implementation NSString (Ext)
- (NSString *)urlEncoded {
NSMutableCharacterSet *const allowedCharacterSet = [NSCharacterSet URLQueryAllowedCharacterSet].mutableCopy;
// See https://en.wikipedia.org/wiki/Percent-encoding
[allowedCharacterSet removeCharactersInString:#"!*'();:#&=+$,/?#[]"]; // RFC 3986 section 2.2 Reserved Characters (January 2005)
NSString *const urlEncoded = [self stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
return urlEncoded;
}
#end
If you need to encode the & character, you can use the following:
var testPassword1: String = "mypassword&1"
testPassword1.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
testPassword1.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet(charactersInString: "&").invertedSet)!

Cyrillic symbols in URL

App crashes with following url:
let jsonUrl = "http://api.com/алматы/events"
let session = NSURLSession.sharedSession()
let shotsUrl = NSURL(string: jsonUrl)
let task = session.dataTaskWithURL(shotsUrl!)
Log:
fatal error: unexpectedly found nil while unwrapping an Optional value
It's because of cyrillic symbols in url. How can I solve this issue. Thanks for your help!
Swift 4
Using String Extension
Create a swift file named String+Extension.swift and paste this code
import UIKit
extension String{
var encodeUrl : String
{
return self.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)!
}
var decodeUrl : String
{
return self.removingPercentEncoding!
}
}
and Use it like so: (sample according to question):
"http://api.com/алматы/events".encodeUrl
Try this:
let encodedUrl = jsonUrl.stringByAddingPercentEncodingWithAllowedCharacters(URLQueryAllowedCharacterSet)
Something like this:
let apiHost = "http://api.com/"
let apiPath = "алматы/events"
let escapedPath = apiPath.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
let url = NSURL(string: "\(apiHost)\(escapedPath!)")
Obviously you should do something smarter than just force unwrap escapedPath.
Using the Wikipedia page for Swift as an example:
https://ru.wikipedia.org/wiki/Swift_(язык_программирования)
Becomes:
https://ru.wikipedia.org/wiki/Swift_(%D1%8F%D0%B7%D1%8B%D0%BA_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)
Which when pasted into the browser takes you to the right page (and most browsers will conveniently render the UFT-8 characters for you).
Non-ASCII characters (and many special characters) need to be escaped in a URL. Chrome and other browser do it automatically. And they unescape the URLs in the address bar for a nicer display.
So if you have a static URL, just paste it into the adressbar, press enter, selected the URL again, copy and paste it to your app:
So instead of:
let jsonUrl = "http://api.com/алматы/events"
You'll get:
let jsonUrl = "http://api.com/%D0%B0%D0%BB%D0%BC%D0%B0%D1%82%D1%8B/events"
Try stringByAddingPercentEncodingWithAllowedCharacters: defined on NSString. You may see people suggesting stringByAddingPercentEscapesUsingEncoding:, but that method is deprecated in iOS 9.
There are also a few predefined NSCharacterSets in Foundation, such as URLHostAllowedCharacterSet and URLPathAllowedCharacterSet. Therefore, if you really have to parse the unescaped URL in code (using preprocessed URLs, mentioned in the accepted answer, is usually a much better idea), you can write a helper method like this:
import Foundation
func url(scheme scheme: String, host: String, path: String) -> NSURL? {
let components = NSURLComponents()
components.scheme = scheme
components.host = host.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
components.path = path.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLPathAllowedCharacterSet())
return components.URL
}
// evaluates to http://api.com/%25D0%25B0%25D0%25BB%25D0%25BC%25D0%25B0%25D1%2582%25D1%258B/events
url(scheme: "http", host: "api.com", path: "/алматы/events")
Note that the above documentation mentions that
This method is intended to percent-encode an URL component or subcomponent string, NOT an entire URL string.
That's because according RFC 3986, not all parts of an URL can be percent-encoded (e.g. scheme - http/https/etc.)
in xamarin:
var uri = new Uri (url);
var nsurl = new NSUrl (uri.GetComponents (UriComponents.HttpRequestUrl, UriFormat.UriEscaped));
UIApplication.SharedApplication.OpenUrl (nsurl);
URLs cannot contain Cyrillic characters. There are standards how to translate Cyrillic characters into valid URLs - you might find something if you search for "Punicode" (the P is intentional).

What is the iOS proper URL encoding of "+" character?

Is there seriously not a way natively to URL Encode the value of a query string parameter that has a "+" character in it?
e.g.
me+blah#domain.net
to
me%2Bblah%40#domain.net?
I tried solutions like posted in these other questions, but those do not properly encode that email.
Swift - encode URL
How do I URL encode a string
A swift solution would be preferred as that is what I am working in at the moment, but I am capable of translating Objective-C Code to swift code for the most part.
Specifically I am trying to x-www-form-urlencoded encode query string values in the body of POST request.
let email = "me+blah#domain.net"
let output = CFURLCreateStringByAddingPercentEscapes(nil, email as NSString, nil, ":/?#!$&'()*+,;=" as NSString, CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding))
// output = "me%2Bblah%40domain.net"
CFURLCreateStringByAddingPercentEscapes doesn't escape + or # by default, but you can specify it (as I did along with other characters, in the ":/?#!$&'()*+,;=" string).
Edit: If you want output to be a Swift string:
let output = (CFURLCreateStringByAddingPercentEscapes(nil, email as NSString, nil, ":/?#!$&'()*+,;=" as NSString, CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)) as NSString) as String
println(("me+blah#domain.net" as NSString)
.stringByAddingPercentEncodingWithAllowedCharacters(
NSCharacterSet.alphanumericCharacterSet()))
Output:
Optional("me%2Bblah%40domain%2Enet")
In Objective-C:
NSString *encodedString =
["me+blah#domain.net" stringByAddingPercentEncodingWithAllowedCharacters:
[NSCharacterSet alphanumericCharacterSet]];

base64EncodedStringWithOptions in Swift fails with compile error

let dataStr = data.base64EncodedStringWithOptions(options: Encoding64CharacterLineLength)
Doesn't compile with "Use of unresolved identifier 'Encoding64CharacterLineLength'"
When I just change the param to zero with
let dataStr = data.base64EncodedStringWithOptions(options: 0)
It gives even stranger error: "Cannot convert the expression of type 'String!' to type 'String!'" I found a way to init NSString with NSData (however, I still can't get the difference between String and NSString), but I'm really curious why these two lines of code don't work.
Unless explicitly given an external name, first argument of a method in Swift is not a named argument. Therefore you should be doing: data.base64EncodedStringWithOptions(x) without the options: part.
If you actually look at the argument type, NSDataBase64EncodingOptions, you'll notice that it is a struct conforming to RawOptionSet with static variables for option constants. Therefore to use them you should do: NSDataBase64EncodingOptions.Encoding64CharacterLineLength
The NSDataBase64EncodingOptions struct (or RawOptionSet in general) is also not convertible from integer literals (like 0). But it does conform to NilLiteralConvertible so if you don't want any options you can pass nil.
Putting it together:
let dataStr = data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
or
let dataStr = data.base64EncodedStringWithOptions(nil)
Swift3.0
let dataStr = data.base64EncodedString(options: [])
For Swift 2.x use an array for options:
let dataStr = data.base64EncodedStringWithOptions([.Encoding64CharacterLineLength])
let dataStr = data.base64EncodedStringWithOptions([])
For swift 3.0+ use this ,
var dataStr = data.base64EncodedString(options: .lineLength64Characters)
Swift 3.x
let fileStream = fileData?.base64EncodedString(options: NSData.Base64EncodingOptions.init(rawValue: 0))
You don't have to put in the "options:" identifier in the argument. You DO have to specify that Encoding64CharacterLineLength is a static member of NSDataBase64EncodingOptions, like so:
var dataStr = data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
let dataStr = data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.allZeros)
Since the default parameter value is set be an empty array…
/// Returns a Base-64 encoded string.
///
/// - parameter options: The options to use for the encoding. Default value is `[]`.
/// - returns: The Base-64 encoded string.
#inlinable public func base64EncodedString(options: Data.Base64EncodingOptions = []) -> String
you just need to call
let dataStr = data.base64EncodedString()

Resources