Perl CGI でリクエスト文字列をファイル書き出ししたら文字化けしたのを直した
POST 送信されたリクエスト文字列を取得し、それをファイルに書き出すという Perl CGI を書いていた。よくある掲示板システムみたいなモノだ。
昔の Shift-JIS を使っていた頃とは違って、今は表示する Web ページも UTF-8、Perl スクリプトファイルも UTF-8、AJAX 通信するので当然 UTF-8、全部 UTF-8 なら文字化けなんてないだろ…と思っていたら、ファイルに書き出した文字列が盛大に化けていた。
そこでよくよく調べてみると、何やら Perl では utf8::decode()
という関数を使う必要があることが分かった。
Perl における文字コード設定色々
Perl CGI における文字コード設定はいくつかの場所で出てくる。それぞれの効能を理解しておかないと、今回の文字化けは解消できなさそうだったので、色々調べてみた。
まずスクリプトの冒頭で、ファイル全体に対して適用したいものとして記述したのは、最終的に以下の3行になった。
use utf8;
use open ':encoding(UTF-8)';
binmode(STDOUT, ':encoding(UTF-8)');
use utf8;
- この CGI ファイルのエンコーディングを示す。
- コレにより、スクリプトが扱う全角文字データに UTF-8 フラグが付いているよ、という宣言になるようだ。
- 後で分かるが、今回の文字化けはこの UTF-8 フラグが付いていない全角文字を処理したことが原因だった。
use open ':encoding(UTF-8)';
- ファイル入出力で使用する
open()
関数のエンコーディングを示す。 open()
の第2引数で示すのと同じ内容。:utf8
と書くとエンコードチェックしないので、省略せず:encoding(UTF-8)
と書くのが良いようだ。
- ファイル入出力で使用する
binmode(STDOUT, ':encoding(UTF-8)');
- 標準出力
STDOUT
向けのエンコーディングを示す。 - コレがないと
Wide character in print
といったワーニングが出力される。
- 標準出力
utf8::decode()
関数
今回、リクエスト文字列を受け取って、それをファイルに書き込んでいたワケだが、リクエスト文字列は外部から拾ってきた文字列なので、Perl のスクリプト内部で使用している文字列と違って UTf-8 フラグというモノが付いていなかった。
# POST 送信されたリクエスト文字列を受け取る
my $queryString;
read(STDIN, $queryString, $ENV{'CONTENT_LENGTH'});
# ↑ この $queryString には UTF-8 フラグが付いていない
# ただの文字列をスクリプト内で宣言する
my $exampleStr = 'テスト';
# ↑ この $exampleStr は Perl スクリプト内で用意しているので自動的に UTF-8 フラグが付いている
変数 $queryString
は key=value
な文字列になっているので、分解して連想配列 (ハッシュ) にする関数を組んでいた。そこで、分解した value
に対して UTF-8 フラグを付けるため、utf8::decode()
関数を適用するようにした。
# クエリ文字列を連想配列に変換する (GET・POST 対応)
sub parseQueries {
# リクエスト情報をまとめる連想配列
my %formData;
# QueryString を取得する
my $queryString;
if($ENV{'REQUEST_METHOD'} eq 'POST') {
read(STDIN, $queryString, $ENV{'CONTENT_LENGTH'});
}
else {
$queryString = $ENV{'QUERY_STRING'};
}
# 'name=value' の配列に直す
my @queryPairs = split('&', $queryString);
foreach my $queryPair (@queryPairs) {
my ($name, $value) = split('=', $queryPair);
# パーセントエンコーディング文字列を戻す
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack('C', hex($1))/eg;
# UTF-8 フラグを付ける!
utf8::decode($value);
$formData{$name} = $value;
}
return %formData;
}
%formData
のキー文字列に使用する $name
の方は、ファイルへの書き出しをしなかったので特にデコードしなかった。試してはいないが、split()
する前に $queryString
ごとデコードしてしまっても良いのかも。
とりあえずコレでファイル書き出し時の文字化けは解消できた。
参考文献
- 参考 : PerlのCGIのutf-8改造で文字化けしたときの処方箋
- 一番分かりやすかった。
- 参考 : Perlで文字コードをいい感じに処理する方法 | Casual Developers Notes