...pudding - diary

この日記は 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
Twitter : @moriya_jp