CPANモジュールをアップデートしたらuse utf8してるスクリプトのNet::Twitter経由でのpostが文字化けるようになった。
な... 何を言ってるのか わからねーと思うが (以下略
まぁ、結論から言うと、 戦犯は Net::Twitter だったわけですが。
use strict;
use warnings;
use utf8;
use Encode;
use URI;
use URI::Escape;
# user code
my %param = ( status => 'てすと' ); # (1)
# Net::Twitter::API::_authorized_request (v3.04003)
utf8::upgrade($_), $_ = encode('utf8', $_) for values %param; # (2)
# URI::_query::query_form
my @query;
while ( my ($k,$v)=each %param) {
push @query, "$k=$v"; # (3)
}
my $query = join('&', @query);
my $uri = URI->new('http:');
$uri->query($query);
# what I've got
print $uri->query, "\n";
# what I want to get
$uri = URI->new('http:');
$uri->query(join('=', status => 'てすと'));
print $uri->query, "\n";
これは今回の問題を簡単に説明するためのコードで、主要なコードを各モジュールから拾ってきたものです。
utf8で保存して実行すると、
status=%C3%A3%C2%81%C2%A6%C3%A3%C2%81%C2%99%C3%A3%C2%81%C2%A8
status=%E3%81%A6%E3%81%99%E3%81%A8
と表示されるはず。
上段が、 use utf8 下で Net::Twitter::update を呼び出した結果サーバに送られるデータと同等のもの(何かよくわからないデータがURI-Encodeされてて文字化けする)。下段が本来送られるべきデータ(UTF-8文字列'てすと'がURI-Encodeされてる)。
なんでこんな変なことになるか
- (1) の時点では、 hashのkey, value とも utf8-flagged 文字列
- (2) で、 valueだけ utf8フラグを落とされ、utf8 octet stream になる
- (3) の文字列
"$k=$v"は utf8-flagged. ただし、 $v は octet stream のまま展開される。この時点で "=" 以降は 'てすと' ではない何か変な文字列になっている。
WORKAROUND
- (1) の時点で key を
encode_utf8しておく → (3) の"$k=$v"は$k . '=' . $vと等価で、utf8-flag が先頭の$kに合わせられ(?)、全体としてutf8-flagはつかず utf8 octet streamになる。 - (2) の時点での
encode_utf8をやめる。→ (3)での文字列結合の結果は utf8-flagged文字列になる use utf8をやめ、(1)のvalue をdecode_utf8する。 → (3)での文字列結合の結果は utf8 octet stream
まぁ、よく考えたら keyがutf8-flagged でvalue が utf8 octet stream とかいうちぐはぐなことを Net::Twitterがやってるのがよろしくないわけで。そこのところもそっと考えて修正していただきたいところです。





コメントする