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)');

utf8::decode() 関数

今回、リクエスト文字列を受け取って、それをファイルに書き込んでいたワケだが、リクエスト文字列は外部から拾ってきた文字列なので、Perl のスクリプト内部で使用している文字列と違って UTf-8 フラグというモノが付いていなかった。

# POST 送信されたリクエスト文字列を受け取る
my $queryString;
read(STDIN, $queryString, $ENV{'CONTENT_LENGTH'});
# ↑ この $queryString には UTF-8 フラグが付いていない

# ただの文字列をスクリプト内で宣言する
my $exampleStr = 'テスト';
# ↑ この $exampleStr は Perl スクリプト内で用意しているので自動的に UTF-8 フラグが付いている

変数 $queryStringkey=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 ごとデコードしてしまっても良いのかも。

とりあえずコレでファイル書き出し時の文字化けは解消できた。

参考文献