本当は怖いHPC

HPC屋の趣味&実益ブログ

URIに使ってよい文字の話 - RFC2396 と RFC3986

POSTするデータを、(プレビューのために)javascriptでGETアクセスするような処理を書いていてハマった話。

発端は、textareaに'(シングルクオートまたはアポストロフィー)が入ると、Railsがそこから先のパラメーターを無視しちゃうっていうこと。いろいろ調べた結果、以下のことがわかった(Railsのバージョンは2.0.2)。

URIを定義する2つのRFC

URIの構文はRFCで定義されている。これには2つあって、従来のRFC2396(1998年発行)と、RFC3986(2005年発行)だ。

RFC3986によれば、

This document obsoletes [RFC2396], which merged "Uniform Resource Locators" [RFC1738] and "Relative Uniform Resource Locators" [RFC1808] in order to define a single, generic syntax for all URIs.

ということなので、RFC3986が分散したURIの定義を全て統合することを狙っている様子。2008年にリリースされたRails2.0は、RFC3986にしたがってURIのパラメーターをパースするようだ(解析部分を確かめたわけではないんだけど、"3986"でgrepするとテストの部分が何箇所かヒットするので、意識しているのは間違いない)。

で、この2つには、URIに使ってよい文字のリストに違いがある。

URIに使ってよい文字の違い

まずは、RFC2396から、BNFによる定義の一部を抜粋してみる。

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          = "-" | "_" | "." | "!" | "~" | "*" | "'" |
                "(" | ")"

つまり、markに挙げられている文字はunreserved(非予約語)で、これは普通に使ってよいことになっている。現に、javascriptのencodeURIComponentでは、unreservedな文字は変換しない。

一方そのころRFC3986では、reservedとunreservedな文字のリストに変化がある。

RFC3986 Appendix A.  Collected ABNF for URI

URI           = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
query         = *( pchar / "/" / "?" )
pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
pct-encoded   = "%" HEXDIG HEXDIG
sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
                 / "*" / "+" / "," / ";" / "="

sub-delimsはreservedであるから、unreservedとされている記号はハイフン・ドット・アンダースコア・チルダだけだ。

つまり、「!'()*」の五文字が、以前はunreservedであったが現在はreservedになった、ということ。

encodeURIComponentはRFC2396なので

ところで、Javascript(ECMAScript)のencodeURIComponent関数はRFC2396に従ったエンコード処理をするので、「!'()*-._~」の記号はエンコードしない。だから、RFC3986準拠のURIを生成するには、encodeURIComponentの処理の上で、さらに「!'()*」は自前でエンコードしなくちゃいけない。

http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Functions:encodeURIComponent#Description

自前の関数を作らねば

というわけで、たいした処理ではないけど、自前関数。

function encodeURIComponentRFC3986(str) {
 return encodeURIComponent(str).
             replace(/[!*'()]/g, function(p){
                        return "%" + p.charCodeAt(0).toString(16);
                    });
}

まぁ、textareaをGETで送るっていう変則的な処理の場合だから必要になるケースは少ないけど、頭に入れておくと役に立つかも。

【広告】