String.addingPercentEncoding(withAllowedCharacters:) が何をエンコードするか気になったので調べてみました。
ついでに URLComponents の queryItems の value 部分と、JavaScriptCore の encodeURI および encodeURIComponent とも比較してみました。悩ましい。
ちなみに、記号は全部エンコードしちゃおうと思って .alphanumerics を使うと、アクセント符号付きのアルファベット (é など) やいわゆる全角英数字がエンコードされないという罠があります。
|CharacterSet| |!|"|#|$|%|&|'|(|)|*|+|,|-|.|/|:|;|<|=|>|?|@|[||]|^|_|`|{|||}|~|
|:--|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|urlFragmentAllowed|✅||✅|✅||✅|||||||||||||✅||✅|||✅|✅|✅|✅||✅|✅|✅|✅||
|urlHostAllowed|✅||✅|✅||✅||||||||||✅|||✅||✅|✅|✅||✅||✅||✅|✅|✅|✅||
|urlPasswordAllowed|✅||✅|✅||✅||||||||||✅|✅||✅||✅|✅|✅|✅|✅|✅|✅||✅|✅|✅|✅||
|urlPathAllowed|✅||✅|✅||✅||||||||||||✅|✅||✅|✅||✅|✅|✅|✅||✅|✅|✅|✅||
|urlQueryAllowed|✅||✅|✅||✅|||||||||||||✅||✅|||✅|✅|✅|✅||✅|✅|✅|✅||
|urlUserAllowed|✅||✅|✅||✅||||||||||✅|✅||✅||✅|✅|✅|✅|✅|✅|✅||✅|✅|✅|✅||
|URLComponents|✅||✅|✅||✅|✅||||||||||||✅|✅|✅|||✅|✅|✅|✅||✅|✅|✅|✅||
|encodeURI|✅||✅|||✅|||||||||||||✅||✅|||✅|✅|✅|✅||✅|✅|✅|✅||
|encodeURIComponent|✅||✅|✅|✅|✅|✅|||||✅|✅|||✅|✅|✅|✅|✅|✅|✅|✅|✅|✅|✅|✅||✅|✅|✅|✅||
import Foundation
import JavaScriptCore
let asciiSymbols = (0x20...0x7e)
.map { Unicode.Scalar($0) }
.filter { !CharacterSet.alphanumerics.contains($0) }
let allowdChars = { (str: String) -> String in
let regexp = try! NSRegularExpression(pattern: "%[0-9A-F][0-9A-F]")
let range = NSRange(str.startIndex..., in: str)
return regexp.stringByReplacingMatches(in: str, range: range, withTemplate: "")
}
let urlComponents = { () -> String in
var comp = URLComponents(string: "https://example.com/")!
comp.queryItems = [URLQueryItem(name: "q", value: asciiSymbols.map { String($0) }.joined())]
return allowdChars(comp.url!.absoluteString.components(separatedBy: "=")[1])
}()
let context = JSContext()!
context.evaluateScript("var ascii = ''; for (var i = 0x20; i < 0x7f; i++) ascii += String.fromCharCode(i)")
context.evaluateScript("var result = encodeURI(ascii)")
let encodeURI = allowdChars(context.objectForKeyedSubscript("result")!.toString()!)
context.evaluateScript("var result = encodeURIComponent(ascii)")
let encodeURIComponent = allowdChars(context.objectForKeyedSubscript("result")!.toString()!)
let charsets: [(String, CharacterSet)] = [
("urlFragmentAllowed", .urlFragmentAllowed),
("urlHostAllowed", .urlHostAllowed),
("urlPasswordAllowed", .urlPasswordAllowed),
("urlPathAllowed", .urlPathAllowed),
("urlQueryAllowed", .urlQueryAllowed),
("urlUserAllowed", .urlUserAllowed),
("URLComponents", CharacterSet(charactersIn: urlComponents)),
("encodeURI", CharacterSet(charactersIn: encodeURI)),
("encodeURIComponent", CharacterSet(charactersIn: encodeURIComponent)),
]
print("|CharacterSet|" + asciiSymbols.map { String($0) }.map { ($0 == "|" ? "|" : $0) + "|" }.joined())
print("|:--|" + asciiSymbols.map { _ in ":-:|" }.joined())
for cs in charsets {
print("|\(cs.0)|" + asciiSymbols.map { (cs.1.contains($0) ? "" : "✅") + "|" }.joined())
}