この日記は https://yapud.hatenablog.com/ に引っ越し中
2008-12-25
_ [Software] URI::Encode の動きがワカラン?
WWW::Mechanize 1.52 を使っていて、フォームに値を入れてPOSTするコードを書いています。
文字コードは UTF-8 BOM無し で、意味もわからず use utf8;
としています。
サーバに送りたい文字列はShiftJISです。 試してみたコードは以下の通り。urlとフィールド名はとりあえず架空ので。
#!/usr/bin/perl use warnings; use strict; use utf8; use WWW::Mechanize; use Encode; my $mech = WWW::Mechanize->new(autocheck=>0); my $targetUrl = 'http://www.example.com/'; my $fieldName = 'name'; my $fieldData = 'あいうえお'; $mech->get($targetUrl); $mech->set_fields($fieldName=>Encode::encode('shiftjis',$fieldData)); $mech->click();
変なところ?
これでPOSTされたデータを眺めると、URLエンコード結果がオカシイんです。ShiftJISの'あいうえお'ですので、
before=%82%A0%82%A2%82%A4%82%A6%82%A8
となって欲しいんですけど、
before=%C2%82%C2%A0%C2%82%C2%A2%C2%82%C2%A4%C2%82%C2%A6%C2%82%C2%A8
となってしまいます。%C2
が1バイトごとに頭に付いています。なんじゃこれ?
WWW::Mechanize が悪いのか LWP が悪いのかわからないので、LWP だけでPOSTするコードを書いてみました。
#!/usr/bin/perl use warnings; use strict; use utf8; use LWP; use Encode; my $ua = LWP::UserAgent->new; my $targetUrl = 'http://www.example.com/'; my $fieldName = 'name'; my $fieldData = 'あいうえお'; $ua->post( $targetUrl, [$fieldName=>Encode::encode('shiftjis',$fieldData)]);
問題ない? 問題なくURLエンコードされています。じゃあ WWW::Mechanize が悪いのでしょうか。
双方の動きを追ってみたかったのですが、こちとら perl は素人でサッパリです。
perl -d filename.pl
で眺めてみたところ、URI の _query.pm 内での振る舞いが違うようです。
双方のコード共に、_query.pm を通るのですが、WWW::Mechanize を使用したモノは、なぜか utf8 処理用のコードを通過してるように見えます。ShiftJISなのに!
URI-1.37 の ChangeLog を見ると、1.36 から utf8 周りをいじってるようなので、変更される前のバージョンに戻してみました。
install GAAS/URI-1.35.tar.gz
これでOK。
しかし、同じバイト列(だと思う)が、同じコードを通ってるように見えるのに動作が違うのは何なのでしょう。追うの面倒なのでこれでオシマイ。
■追記:いちげん氏にいただいたコメント
こんにちは。
私も同じ問題でハマったので、コメントさせていただきます。
まずこれの直接の原因はURI/Escape.pmの
return join '', @URI::Escape::escapes{$_[0] =~ /(\C)/g};
という行にあるみたいです。
\Cっていうのが悪さをするようで、UTF-8フラグが立った文字列を食わせるとバイトごとに%C2にマッチしてしまうんですね。
ってことはUTF-8フラグを落とせば良いんですが、そもそもどこで立ったのか、WWW::Mechanize、HTML::Formとかが自動的にHTMLページをパースするときに、その「ページ」(※フォームの値ではない!)にUTF-8な文字列が入っていると、あとでリクエストボディを作るときに全体が影響を受けてしまう…、という感じです。
しかも訳のわからないことに、フォームにASCII文字しか入っていなくても、です…。
で、とりあえず回避策は、単にリクエストの直前に片っ端からフォームデータのUTF-8フラグを落とすだけでOKです。
ただこのとき要注意なのは、
●valueメソッドを使うとチェックなどされるので、問答無用で直接プロパティを書き換える
●valueだけじゃなくnameも書き換える
●selectタグとかcheckboxのような選択型の場合、valueを書き換えるとなぜか無効になってしまうので、選択中の選択肢のvalueを書き換える
というようにします。
こんな感じ。
これをリクエスト送信の直前に実行します。
$FORMはフォームオブジェクトに、$CHARSETは文字コードに置き換えてください。
require Encode; foreach my $input ($FORM->inputs) { $input->{name} = Encode::encode($CHARSET, $input->{name}); if (exists $input->{menu} && exists $input->{current}) { $input->{menu}[$input->{current}]{value} = Encode::encode($CHARSET, $input->{menu}[$input->{current}]{value}); } else { $input->{value} = Encode::encode($CHARSET, $input->{value}); } }
前 | 2008年 12月 |
次 | ||||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
1 | 2 | 3 | 4 | 5 | 6 | |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 |