読者です 読者をやめる 読者になる 読者になる

北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

WWW::Mechanize-1.66と文字コード

最近、LWPやWWW::Mechanizeを2007年頃のバージョンから最新のバージョンにしたら、文字コードで色々ハマったのでメモっておきます。

結論から言えば、最新のLWPやWWW::MechanizeはUnicode文字列を適切に扱えますので、以下を心得て使うとハマりどころが少ないかなと思います。

LWP-5.837

  • $ua->decoded_contentはUnicode文字列*1
    • Content-Typeでの指定やMETAタグの指定から推測してくれる
  • $ua->content はバイト列
    • ただし、$ua->contentはContent-Encoding(俗にいうgzip圧縮)の展開をしないので、decoded_contentが無難

WWW-Mechanize-1.66

  • $mech->content は $ua->decoded_content
  • $mech->field, tick, valueとかには、Unicode文字列を渡す
  • $mech->click とすると、その<form>にaccept-charset属性があればその値でencodeされて送信される
    • そうでなければ、全て"UTF-8"でencodeされる


さて、このclickの動作には一つ問題があって、WWW::Mechanize-1.66では「<form>が表示されているドキュメントのcharsetを、送信するクエリのcharsetとする」という大多数のブラウザでの動作をエミュレートできていません。具体的には、以下のテストが通りません。サーバに届くのは「%E5%A3%B1=%E3%81%82」となります。

$Plack::Test::Impl = 'Server';
my $mech = WWW::Mechanize->new;

test_psgi
    ua  => $mech,
    app => sub {
        my $env = shift;
        if ($env->{PATH_INFO} eq '/post') {
            return [
                200, [
                    'Content-Type' => 'text/html; charset=euc-jp',
                    'X-QStr' => $env->{QUERY_STRING},
                ], [ "done\n" ]
            ];
        } else {
            return [ 
                200, [
                    'Content-Type' => 'text/html; charset=euc-jp'
                ], [ encode 'euc-jp', do { local $/; <DATA> } ]
            ];
        }
    },
    client => sub {
        my $cb = shift;
        my $res_form = $cb->(GET '/');

        $mech->tick('壱' => 'あ');
        my $res_posted = $mech->click;
        is scalar $res_posted->header('X-QStr'), '%B0%ED=%A4%A2';

        done_testing;
    };

__DATA__
<form action="/post" method="GET">
<input type="checkbox" name="壱" value="あ">
</form>

こちらはパッチを送ってみましたが、適用されるかはわかりません。

*1:厳密にいえばLatin-1文字列が返ることもあるので、UTF-8フラグが立つとは限らない → UTF8 フラグあれこれ