みてね Tech Blog
🐡

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: 日本語のパーセントエンコーディングはしてくれる
  • .urlQueryAllowedquery 全体 に対する文字集合であり、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 を定義するなどの対応が必要
みてね Tech Blog
みてね Tech Blog

Discussion