日向夏特殊応援部隊

俺様向けメモ

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");

最後にかなり強引な置換してます。