🐡
SwiftのURLパラメータ値のパーセントエンコーディング
はじめに
- 多くの言語では URL の クエリパラメータ値 を正しくパーセントエンコードする機能が標準で備わっているが、Swift の標準 SDK には Query Value 用のエンコードを行う専用 API が存在しない。
- Swift にも String#addingPercentEncoding(withAllowedCharacters:) は存在するが、あくまで「許可文字集合を自分で指定する」ための低レベル API であり、Ruby や Kotlin のような query value を安全にエンコードするプリセット関数 は用意されていない。
- 例えば
custom-scheme://example?return_to=%2Fhoge%2Ffuga%3Fparam1%3Dvalue1のように、URL パラメータ値にパスやクエリを含むケースで問題になる。
Swiftでの対応例
Swift で URL パラメータ値を正しくエンコードするには、RFC 3986 の Unreserved Characters に基づく CharacterSet を自前で定義する必要がある。
RFC 3986 では、Query Value において非エンコードでよい文字は次の通りとされている。
ALPHA / DIGIT / "-" / "." / "_" / "~"
Swiftでの実装例
import Foundation
extension CharacterSet {
/// URL の Query Value で利用できる文字集合
/// 具体的には RFC 3986 の Unreserved Characters (ALPHA / DIGIT / "-" / "." / "_" / "~")
static let urlQueryValueAllowed: CharacterSet = {
var allowed = CharacterSet.alphanumerics
allowed.insert(charactersIn: "-._~")
return allowed
}()
}
"/hoge/fuga?param1=value1".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)!
// => "%2Fhoge%2Ffuga%3Fparam1%3Dvalue1"
各言語の標準SDKのURLパラメータ値のパーセントエンコーディング
Ruby, Kotlin(Android), Swiftを比較
Ruby
require 'uri'
URI.encode_www_form_component("/hoge/fuga?param1=value1")
# => "%2Fhoge%2Ffuga%3Fparam1%3Dvalue1"
# OK
Kotlin
import android.net.Uri
Uri.encode("/hoge/fuga?param1=value1")
// => "%2Fhoge%2Ffuga%3Fparam1%3Dvalue1"
// OK
Swift
- Ruby, Kotlinのようなパーセントエンコーディングする機能が標準SDKには存在していない。
- 以下で紹介するパーセントエンコーディングしてくれそうなモジュールでもURLパラメータ値用にパーセントエンコーディングする機能はない。
String#addingPercentEncoding(withAllowedCharacters:)
-
String#addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)がやってくれそうだがURLパラメータ値に合わせたパーセントエンコーディングはしてくれない。
import Foundation
"/hoge/fuga?param1=value1".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
// => "/hoge/fuga?param1=value1"
// NG: パーセントエンコーディングされない
"return_to=あああ".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
// => "return_to=%E3%81%82%E3%81%82%E3%81%82"
// OK: 日本語のパーセントエンコーディングはしてくれる
-
.urlQueryAllowed は query 全体 に対する文字集合であり、query value のためのものではない。
- そのため
?や=がエンコードされないのは仕様通りであり、/や?が encode されないのもそのため。
- そのため
- Swift には
.urlQueryValueAllowedのような Query Value 専用の CharacterSet プリセットは存在しない ため、自分で定義する必要がある。
URLComponents
- URLComponentsでは少しやってくれるが期待通りではない
import Foundation
var component = URLComponents()
component.scheme = "custom-scheme"
component.host = "example"
component.queryItems = [
.init(name: "return_to", value: "/hoge/fuga?param1=value1")
]
component.url!.absoluteString
// => "custom-scheme://example?return_to=/hoge/fuga?param1%3Dvalue1"
// NG: 期待通りではないが `=` だけパーセントエンコーディングされる
まとめ
- Swift には query value を RFC 3986 に沿って percent-encode する標準 API は存在しない
-
.urlQueryAllowedは query 全体 のための CharacterSet であり、value 用ではない - URLComponents も query value を完全にはエンコードしない
- Swiftでの実装例のように自前の CharacterSet を定義するなどの対応が必要
Discussion