URIとURI::Escapeのurlencodeについて (2)
d:id:ZIGOROu:20070824:1187943710の続き。
ちなみにそのブクマコメントで、個人情報もネタにするハム一さん事、id:nipotanさんが答えを書いてました。
調べちゃったお!!!><
URIモジュールの場合
httpと言うschemeの場合は、あれこれ経てURI::httpオブジェクトになります。
URI::httpって、こんな継承ツリーになってます。
+-----+ +-------------+ | URI | | URI::_query | +-----+ +-------------+ \'-------v---\' +---------------+ | URI::_generic | +---------------+ | +--------------+ | URI::_server | +--------------+ | +-----------+ | URI::http | +-----------+
でquery()とかquery_form()はURI::_queryのメソッドです。
URI::_query::query_form()
URI-1.35より抜粋。
sub query_form { my $self = shift; my $old = $self->query; if (@_) { # Try to set query string my @new = @_; if (@new == 1) { my $n = $new[0]; if (ref($n) eq "ARRAY") { @new = @$n; } elsif (ref($n) eq "HASH") { @new = %$n; } } my @query; while (my($key,$vals) = splice(@new, 0, 2)) { $key = '' unless defined $key; $key =~ s/([;\/?:@&=+,\$\[\]%])/$URI::Escape::escapes{$1}/g; $key =~ s/ /+/g; $vals = [ref($vals) eq "ARRAY" ? @$vals : $vals]; for my $val (@$vals) { $val = '' unless defined $val; $val =~ s/([;\/?:@&=+,\$\[\]%])/$URI::Escape::escapes{$1}/g; $val =~ s/ /+/g; push(@query, "$key=$val"); } } $self->query(@query ? join('&', @query) : undef); } return if !defined($old) || !length($old) || !defined(wantarray); return unless $old =~ /=/; # not a form map { s/\+/ /g; uri_unescape($_) } map { /=/ ? split(/=/, $_, 2) : ($_ => '')} split(/&/, $old); }
まぁ新規に値突っ込んだ時のescape処理はと言うと、
my @query; while (my($key,$vals) = splice(@new, 0, 2)) { $key = '' unless defined $key; $key =~ s/([;\/?:@&=+,\$\[\]%])/$URI::Escape::escapes{$1}/g; $key =~ s/ /+/g; $vals = [ref($vals) eq "ARRAY" ? @$vals : $vals]; for my $val (@$vals) { $val = '' unless defined $val; $val =~ s/([;\/?:@&=+,\$\[\]%])/$URI::Escape::escapes{$1}/g; $val =~ s/ /+/g; push(@query, "$key=$val"); } }
まぁスペ−スは見事に+に変換されてる訳でした。
URI::Escapeの場合
uri_escape()のコード抜粋。
# Build a char->hex map for (0..255) { $escapes{chr($_)} = sprintf("%%%02X", $_); } my %subst; # compiled patternes sub uri_escape { my($text, $patn) = @_; return undef unless defined $text; if (defined $patn){ unless (exists $subst{$patn}) { # Because we can't compile the regex we fake it with a cached sub (my $tmp = $patn) =~ s,/,\\/,g; eval "\$subst{\$patn} = sub {\$_[0] =~ s/([$tmp])/\$escapes{\$1} || _fail_hi(\$1)/ge; }"; Carp::croak("uri_escape: $@") if $@; } &{$subst{$patn}}($text); } else { # Default unsafe characters. RFC 2732 ^(uric - reserved) $text =~ s/([^A-Za-z0-9\-_.!~*'()])/$escapes{$1} || _fail_hi($1)/ge; } $text; }
特に変換パターンを指定しない場合はelse句で、最後のs///geで見事に%20のように変換されとる次第な訳でございます。
んー、これ絶対おかしいよねー。
URI::_queryの方を直すべきだと思う訳です。
追記 (2007-08-24T19:46:41+09:00)
http://b.hatena.ne.jp/miyagawa/20070824#bookmark-5670717
id:miyagawaさんのコメントより。
queryの場合のスペースはsearch-wordの区切りで特殊扱いされるからでは? rfc2396, rfc3875
あー、なるほど。
ざっとRFC見た限りではreservedって事しか良く分からなかった体たらくぶりですがw
番外編 Text::Tree
あの継承ツリーっぽぃテキストの出力スクリプトはこんな感じ。
#!/usr/bin/perl use strict; use warnings; use Text::Tree; my $tree = Text::Tree->new( " URI::http ", [ " URI::_server ", [ " URI::_generic ", [" URI "], [" URI::_query "] ] ] ); print map { s/\^/v/g; s/\./\\'/g; $_ } reverse $tree->layout("centered in boxes");
最後にかなり強引な置換してます。