#navi_header|技術| 仕事中、FormやCookieのkey名に"="を含めたらどうなるのだろう?という話題が出た。 や、 Set-Cookie: foo=bar=baz の時、どんな挙動がみられるのか? ということで、FormとCookieのそれぞれについて、最初にRFC上での"="の扱いについて調べ、続いて実際に動作を確認してみたメモ。 #more|| #outline|| ---- * Form、つまりURLのqueryやx-www-form-urlencodedの場合 URLのquery部分やx-www-form-urlencodedなど、Formからのsubmitなどの場合について調べてみる。 ** RFC2396, RFC2616, RFC3986 の調査 まずRFC上に何か規定がないか調べてみる。 HTTPで使われているURLについてはRFC2616で次のように記されている。(see: http://www.studyinghttp.net/cgi-bin/rfc.cgi?2616#Sec3.2.2 ) http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]] "host"や"query"にどのような文字を使えるのかについては、RFC2396のAppendix.A に記されている。 (see: http://tools.ietf.org/html/rfc2396 ) A. Collected BNF for URI URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] absoluteURI = scheme ":" ( hier_part | opaque_part ) relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ] ... query = *uric ... uric = reserved | unreserved | escaped reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," unreserved = alphanum | mark mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" escaped = "%" hex hex hex = digit | "A" | "B" | "C" | "D" | "E" | "F" | "a" | "b" | "c" | "d" | "e" | "f" ここで"="が"reserved"に含まれていることが確認できる。では"reserved", "unreserved"の違いは何か?これについてもRFC2396の"2.2. Reserved Characters"以下に記されている。 2.2. Reserved Characters Many URI include components consisting of or delimited by, certain special characters. These characters are called "reserved", since their usage within the URI component is limited to their reserved purpose. If the data for a URI component would conflict with the reserved purpose, then the conflicting data must be escaped before forming the URI. ... 2.3. Unreserved Characters Data characters that are allowed in a URI but do not have a reserved purpose are called unreserved. These include upper and lower case letters, decimal digits, and a limited set of punctuation marks and symbols. ざっくりまとめれば、"reserved"に指定している文字セットはURLを構成する各パートでの区切り文字などに使われているため、データとして使うのであれば"escape"せよ、と書いてある。"escape"というのはいわゆる"%"+16進数で表現する「パーセントエンコーディング」となり、詳細は同RFCの"2.4. Escape Sequences"を参照のこと。 URLについてはURNと共にRFC3986にてURIに包含された。RFC3986での"query"について見てみる。 (see : http://tools.ietf.org/html/rfc3986#section-3.4 ) 3.4. Query The query component contains non-hierarchical data that, along with data in the path component (Section 3.3), serves to identify a resource within the scope of the URI's scheme and naming authority (if any). The query component is indicated by the first question mark ("?") character and terminated by a number sign ("#") character or by the end of the URI. query = *( pchar / "/" / "?" ) The characters slash ("/") and question mark ("?") may represent data within the query component. Beware that some older, erroneous implementations may not handle such data correctly when it is used as the base URI for relative references (Section 5.1), apparently because they fail to distinguish query data from path data when looking for hierarchical separators. However, as query components are often used to carry identifying information in the form of "key=value" pairs and one frequently used value is a reference to another URI, it is sometimes better for usability to avoid percent- encoding those characters. 具体的な文字セットについては"Appendix A. Collected ABNF for URI"参照: Appendix A. Collected ABNF for URI URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] ... pchar = unreserved / pct-encoded / sub-delims / ":" / "@" query = *( pchar / "/" / "?" ) ... pct-encoded = "%" HEXDIG HEXDIG unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" ... sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" RFC2396では"reserved"で一括りになっていたのが、やや細分化され"sub-delims"と":"と"@"になっている。とはいえここでもやはり"="は"sub-delims"扱い、つまりデータとして使うのであればパーセントエンコーディングが必要とされていることが読み取れる。 とはいえ、ここまでのRFC2616, RFC2396, RFC3986で記されているのは "=" が query における区切り文字であること迄であり、一体何を区切るのか、つまり query 自体の詳細には踏み込んでいない。 ** CGI, W3C HTML 4.01, RFC1866の調査 Webの世界ではqueryはCGIなど動的処理に使われる。そこでCGIのQUERY_STRINGの定義に何かヒントが無いか見てみる。(see : http://www.studyinghttp.net/cgi#QUERY_STRING ) >検索文字列のための URL 構文は RFC 2396 [2] の section 3 にて記述される。 QUERY_STRING 値は、大文字・小文字を区別する。 > QUERY_STRING = query-string > query-string = *uric > uric = reserved | unreserved | escaped >問い合わせ文字列を解析し、デコードする際、その解析の詳細や予約文字、非 US-ASCII 文字についてのサポートは、状況に依存する。 例えば、HTML 文書からのフォーム提出 [18] は、application/x-www-form-urlencoded 符号化を使用し、その場合は文字 "+", "&", "=" が予約されており、非 US-ASCII 文字については ISO 8859-1 エンコーディングが使用されているであろう。 ここでもquery内部の詳しいフォーマットまでは定義されていない。では、HTML文書からのフォーム提出ではどのように記されているのか見てみる。上記CGIの"HTML 文書からのフォーム提出 [18] "はW3Cの"HTML 4.01 Specification"を参照しているので、まずはHTML4.01でのフォームの仕様を調べてみる。 すると "17.13.4 Form content types" でようやく求めたいた記述が見つかった。 http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4 >application/x-www-form-urlencoded > >This is the default content type. Forms submitted with this content type must be encoded as follows: >1. Control names and values are escaped. Space characters are replaced by `+', and then reserved characters are escaped as described in [RFC1738], section 2.2: Non-alphanumeric characters are replaced by `%HH', a percent sign and two hexadecimal digits representing the ASCII code of the character. Line breaks are represented as "CR LF" pairs (i.e., `%0D%0A'). >2. The control names/values are listed in the order they appear in the document. The name is separated from the value by `=' and name/value pairs are separated from each other by `&'. ''Control names and values are escaped.'' この一文により、フォームのname属性はエスケープされることが規定される。 同ページの"17.4 The INPUT element"ではinputタグのname属性がCDATAとなっており、データの自由度は高い。 "application/x-www-form-urlencoded"についてはRFC1866(HTML 2.0)が初出・・・らしい。 - http://tools.ietf.org/html/rfc1866 -- 8.2. Form Submission 参照 ** 実験 簡単なFormを作成し、name属性に改行や空白、"="などを混ぜてみる。
このようなフォームをChrome, IE9, Firefox5でsubmitしてみる。 Chrome, Firefox5.0: ...?+%3Fabc%0D%0Adef+%3D%0D%0Aghi+%2F%0D%0A+=foo IE9: ...?+%3Fabc%250D%250Adef+%3D%250D%250Aghi+%2F%250D%250A+=foo IE9だけ改行文字でパーセントエンコーディングが二重に処理されている・・・。 PHPではどのようにデコードされるか? 'foo', ) IE9: array ( '?abc%0D%0Adef_=%0D%0Aghi_/%0D%0A_' => 'foo', ) key名の先頭のスペースは除去され、それ以外の空白は"_"に置換されている。改行についてはChrome, Firefoxの場合はそのままデコードされ、IEの場合は二重のパーセントエンコーディングが一回デコードされた段階になっている。 PHPの場合、ドットやスペースは"_"に置換される。これはregister_globalsにより自動的に変数に展開されていた時代、ドットが変数名を表す文字セットに含まれていなかったことが影響していると思われる。 http://jp.php.net/manual/ja/language.variables.external.php 注意: 変数名のドットやスペースはアンダースコアに変換されます。 たとえば は $_REQUEST["a_b"] となります。 * Cookieの"NAME=VALUE"の場合 Cookieの"NAME=VALUE"にエンコードされていない"="が含まれるとどうなるか? ** Netscape仕様, RFC6265の調査 まずNetscapeの仕様を見てみる。 http://www.futomi.com/lecture/cookie/specification.html >NAME=VALUE > >ここには、セミコロン、カンマ、スペースを排除した文字列が入ります。セミコロン、カンマ、スペースが含まれるようなデータを設定する必要がある場合には、URLエンコードのような何かしらのエンコードが推奨されます。ただし、エンコード自体は、まったく定義されているわけではありませんし、要求されるものではありません。(日本語を扱う場合には、URLエンコードをする必要があります。) この文章からは、 NA=M%3DE=VA%3DLU=E のような極端な例に対してどう処理すれば良いのかは読み取れない。 念のためweb.archive.orgに残されている原文を確認してみる。 http://web.archive.org/web/20020803110822/http://wp.netscape.com/newsref/std/cookie_spec.html >NAME=VALUE >This string is a sequence of characters excluding semi-colon, comma and white space. If there is a need to place such data in the name or value, some encoding method such as URL style %XX encoding is recommended, though no encoding is defined or required. 日本語と同様である。セミコロン、カンマ、ホワイトスペースが予約語扱いでパーセントエンコーディングが必要、というのは理解できる。 "="は予約語ではないのか? 最新のCookieはRFC6265となり、こちらを調べてみる。 http://www.ietf.org/rfc/rfc6265.txt #pre||> 4.1. Set-Cookie The Set-Cookie HTTP response header is used to send cookies from the server to the user agent. 4.1.1. Syntax Informally, the Set-Cookie response header contains the header name "Set-Cookie" followed by a ":" and a cookie. Each cookie begins with a name-value-pair, followed by zero or more attribute-value pairs. Servers SHOULD NOT send Set-Cookie headers that fail to conform to the following grammar: set-cookie-header = "Set-Cookie:" SP set-cookie-string set-cookie-string = cookie-pair *( ";" SP cookie-av ) cookie-pair = cookie-name "=" cookie-value cookie-name = token cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E ; US-ASCII characters excluding CTLs, ; whitespace DQUOTE, comma, semicolon, ; and backslash token =