36
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

Organization

URLのクエリーパラメータでエスケープしなくていい文字はどれか?

http://example.com/path?k1=v1&k2=v2
においてk1, v1, k2, v2の部分に使える文字について調べてみた。

まとめ

  • *-._の4文字と英数字はそのまま
  • スペースは+に置換する
  • それ以外はパーセントエンコードする

根拠:HTML5のw3cの仕様がそう言っている。

  • JSのencodeURIComponentはこの4文字に加えて!'()~もエスケープから除外している。encodeURIComponentはRFC 2396準拠であり、古い。よってencodeURIComponentが常に正かというとそういうわけではない。
  • 現実には、スペースを+でなく%20に置換してしまっても問題ない事が多い
  • それどころか、*-._ の4文字もパーセントエンコーディングしてしまって問題ない事が多い

調査記録

  • RFCを追っていくと、パーセントエンコードしなくていいのは-._~の4文字と解釈できそうだった。これはHTML5の仕様と微妙に違う。でもおそらくHTML5の仕様を信用した方がいいと思う。
    • クエリーパラメータの仕様を定めているのはRFC 1866 x-www-form-urlencodedである。
    • RFC 1866は「スペースを+に変換し、URLで予約された文字をパーセントエンコードする」と定義している。
    • RFC 3986によれば、unreservedと定義されているのは-._~の4文字。
  • 現実には、スペースは%20+どちらになる場合もある。
    • x-www-form-urlencodedの仕様的には+の方が正しいのかもしれないが、現実的には%20にした方が安全と思う。
      • %20なら受け取る側はまず間違いなくスペースと解釈するだろうから。
      • curlも--data-urlencode 'hoge= 'では%20にする。
    • JSのencodeURIComponent%20にするし、decodeURIComponent+をスペースに置換しない。
      • encodeURLComponentが準拠するのはRFC 3986でなくRFC 2396である。つまり古い。RFC 1866は考慮していないと言える。
  • 関連RFCは
    • RFC 2396 "Uniform Resource Identifiers (URI): Generic Syntax" (1998年発行)
    • RFC 3986 "Uniform Resource Identifiers (URI): Generic Syntax" (2005年発行)
      • 2396は「Obsoleted by: 3986」なので現在は3986だけを参照すればよい
    • RFC 1866 "Hypertext Markup Language - 2.0" 8.2.1. The form-urlencoded Media Typeの部分
  • encodeURI と decodeURI は URI 全体に適用するもので、encodeURIComponent と decodeURIComponent は URI の要素に適用するものと意図されている。
  • Chromeのアドレスバーで「python ruby」と入れて検索するとURLは「python+ruby」になる

RFC 1866

8.2.1. form-urlencoded メディアタイプ(The form-urlencoded Media Type)

すべてのフォームのデフォルトエンコーディングは`application/x-www-form-urlencoded'です。このメディアタイプではフォームデータセットは以下のように表わされます。

1.フォームフィールドの名称と値は次のようにエスケープされます:空白文字は'+'に置き換えられ、予約された文字記号は[URL]に従ってエスケープされます。すなわち、英数字以外は'%HH'――パーセント記号と記号のASCIIコードを16進数で表わしたもの――に置き換えられます。複数行のテキストフィールドにおいて、改行はCRとLFの組み合わせ、すなわち'%0D%0A'で表わされます。

2.フィールドは文書に現われる順番に、'='により値と名称を区別し、'&'によりほかの組み合わせと区別して並べられます。値のないフィールドは無視されるでしょう。特に選択されていないラジオボタンやチェックボックスは符号化されたデータには現われませんが、VALUE属性を持つ隠しフィールドは現われます。

注意‐問い合わせフォーム提出からのURIは通常のハイパーリンクのアンカー形式として使用できます。不運なことに、'&'記号をフィールドの区切りに使用することは、SGML属性値において実体参照区切り子としてその記号を使うことと互いに影響を与えあいます。例えば、http://host/?x=1&y=2は、<a href="http://host/?x=1&#38;y=2">あるいは<a href="http://host/?x=1&amp;y=2">と書くべきです。

HTTPサーバ、特にCGIはユーザーをトラブルから守るために、'&'の代わりに';'の使用をサポートすることを薦めます。

RFC 2396

RFC2396 Appendix A. Collected BNF for URI (抜粋)

URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
absoluteURI   = scheme ":" ( hier_part | opaque_part )
hier_part     = ( net_path | abs_path ) [ "?" query ]
query         = *uric
uric          = reserved | unreserved | escaped
reserved      = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
                "$" | ","
unreserved    = alphanum | mark
mark          = "-" | "_" | "." | "!" | "~" | "*" | "'" |
                "(" | ")"
escaped       = "%" hex hex

reserved(予約文字)はURIにおいて特殊な意味を持つ文字。よってこれらをパラメータで送りたい場合は?q=&でなくq=%26のようにURLエンコードしなければならない。

RFC 3986

   unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
   reserved      = gen-delims / sub-delims
   gen-delims    = ":" / "/" / "?" / "#" / "[" / "]" / "@"
   sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
                 / "*" / "+" / "," / ";" / "="

RFC 2396にはあったreservedという文言がなくなっているが、sub-delims、":"、"@"がreservedに相当する。よって!'()*の五文字が、以前はunreservedであったが現在はreservedになった、ということ。

JavaScriptのencodeURIComponent

RFC 2396の通り、9個の文字がエスケープされずに残っている。

        %20
!       !       equal
"       %22
#       %23
$       %24
%       %25
&       %26
'       '       equal
(       (       equal
)       )       equal
*       *       equal
+       %2B
,       %2C
-       -       equal
.       .       equal
/       %2F
:       %3A
;       %3B
<       %3C
=       %3D
>       %3E
?       %3F
@       %40
[       %5B
\       %5C
]       %5D
^       %5E
_       _       equal
`       %60
{       %7B
|       %7C
}       %7D
~       ~       equal

上記結果出力用のスクリプト

urlencode.js
function encodeString(str) {
    for (var j = 0; j < str.length; j++) {
        var c = str[j];
        console.log(c + "\t" + encodeURIComponent(c) + "\t" + (c === encodeURIComponent(c) ? "equal" : ""));
    }
}

for (var i = 0x20; i < 0x7F; i++) {
    var s = String.fromCharCode(i);
    if (!s.match(/[A-Za-z0-9]/)) {
        encodeString(s);
    }
}

RubyのURI.encode_www_form_componentとencode_www_form

HTML5のw3cの仕様と同じく*-._の4文字をそのまま残している。

    +   
!   %21 
"   %22 
#   %23 
$   %24 
%   %25 
&   %26 
'   %27 
(   %28 
)   %29 
*   *   equal
+   %2B 
,   %2C 
-   -   equal
.   .   equal
/   %2F 
:   %3A 
;   %3B 
<   %3C 
=   %3D 
>   %3E 
?   %3F 
@   %40 
[   %5B 
\   %5C 
]   %5D 
^   %5E 
_   _   equal
`   %60 
{   %7B 
|   %7C 
}   %7D 
~   %7E 
require "uri"

(0x20...0x7F).each do |c|
  s = c.chr
  unless s =~ /[A-Za-z0-9]/
    encoded = URI.encode_www_form_component(s)
    puts "#{s}\t#{encoded}\t#{s == encoded ? 'equal' : ''}"
  end
end

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
36
Help us understand the problem. What are the problem?