perlのCGIプログラムで文字コードを扱う場合

理解したと思っても何度も何度も忘れてしまうので備忘録。
今のperlにはEncodeモジュールのおかげで文字コードを扱えるわけだが、ここでUTF-8を扱う場合には、UTF-8フラグとPerlIOレイヤを意識すること。
perl内部ではUTF-8フラグをオンにしてマルチバイトとして扱わせること。プログラムへの入出力はなにも宣言しないと単なるバイト列でくるので、なにかしら変換が必要であり、そのための変換メソッドだったり、入出力のデフォルトエンコーディングの指定だったりする。

#!/usr/bin/perl

use strict;
use warnings;
use utf8;               # このファイルはUTF-8で記述
#use open ':utf8';      # 入出力はUTF-8で行う
#use open ':std';       # 標準入力、標準出力、標準エラー出力は上記と同じにする
binmode STDOUT, ':utf8';# 標準出力はUTF-8
use Encode;
use CGI qw/-debug/;

sub sanitize() {
        my $s = shift;
        $s =~ s/&/&/g;
        $s =~ s/</&lt;/g;
        $s =~ s/>/&gt;/g;
        $s =~ s/"/&quot;/g;

        return $s;
}

sub html() {
        my $q = shift;
        my $html = join('', <DATA>);

        my @ex = qw/テスト0 テスト1/;

        if (defined($q->param('examples'))) {
                my $in = &sanitize(
                        Encode::decode('utf8', $q->param('examples')));
                chomp($in);
                @ex = split(/\r?\n/, $in);
        }
        my $examples = join("\n", @ex);

        $html =~ s/__##SELF##__/$0/g;
        $html =~ s/__##EXAMPLES##__/$examples/g;
        return $html;
}

print "Content-type: text/html;\n\n" . &html(new CGI);

__END__
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<title>テスト</title>
</head>
<body>
<h1>テスト</h1>
<form action="__##SELF##__" method="post">
        <textarea name="examples" cols="32" rows="8">__##EXAMPLES##__</textarea>
        <input type="submit" value="Test" />
</form>
</body>
</html>

ハマったのは、 binmode STDOUT, ':utf8'; の部分。
というのは、デバッグの際は、 use open ':utf8'; と use open ':std'; で問題なく動くのだが、実際にブラウザから行うと入力文字が文字化けするのだ。
デバッグコマンドラインから行っており、標準入力を使っていたからうまく動いていただけのこと。
つまり、コマンドラインと実際のブラウザ両方でうまく動かすには、

  • 標準出力だけはUTF-8にする(binmode STDOUT, ':utf8';を使用することで出力時、いちいちEncode::encode()する必要がない)。これで標準出力にUTF-8フラグなしで出力。
  • 入力はEncode::decodeでUTF-8フラグ付きに変換してあげる(Encode::decode('utf8', $q->param('examples'))の部分)。

ということが必要。

また、use, binmodeもスコープが大事なようで、ファイルを読み書きする場合にグローバルで設定した文字コードと違うコードで読み書きする場合は同じスコープ内で宣言してあげないとうまく動かないみたい。

http://blog.livedoor.jp/dankogai/archives/51224106.html
http://blog.livedoor.jp/dankogai/archives/51693618.html
http://www.rwds.net/kuroita/program/Perl_unicode.html