プロが教える店舗&オフィスのセキュリティ対策術

perl5.8のエンコードで悩んでいます。
5.6環境下でjcodeなどを使用したいろいろな処理は普通にできるのですが、そろそろ5.8での文字処理もちゃんとできないとあぶないかなと思い、練習していましたが…なんとも挙動が解らないときがあります…。

よければご教授いただけると幸いです。

まず基本的な練習として、POSTされた文字データを受領するものを作ってみました。

<<送信元 post1.html>>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dt …
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>

<body>
<p>POST実験</p>
<form id="form1" method="get" action="post2.cgi">
<p>data1 <input name="data1" type="text" /> </p>
<p>data2 <input name="data2" type="text" /> </p>
<p> <input type="submit" name="button" value="送信" /> </p>
</form>
</body>
</html>

<<受領部 post2.cgi>>
#!/usr/bin/perl

#モジュール利用宣言
use CGI; #CGIモジュール
use strict; #表記の正規化を強制
use warnings; #警告表示
use CGI::Carp qw(fatalsToBrowser); #エラー報告有効

#エンコーディングの指定
#use utf8; #この部分を有効にすると入力内容が文字化け
#use encoding 'UTF-8';#これも同様に入力内容が文字化け

#データの受領と変数名整理
my $input_data = new CGI;
my $in_data1 = $input_data->param('data1');
my $in_data2 = $input_data->param('data2');

#入力チェック
my $message_data = ""; #変数初期化
if($in_data1 eq "" && $in_data2 eq ""){$message_data = "入力部に空欄があります";}
else{$message_data = 'Data1は'.$in_data1.'Data2は'.$in_data2.'です';}

#ヘッダ発行
print "Content-type: text/html\n\n";

#html表示
print <<END_OF_HTML;

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<p>サシスセソはSJISで化けるカタカナ。<br>入力内容は${message_data}</p>
</body></html>

END_OF_HTML

exit;

両ファイルともTeraPadを利用して、文字コードはUTF-8N、改行はLFにしています。

しかし、use utf8;と書くと、入力文字が化けてしまいます。
(コメントアウトすると普通に表示されます)

ちょっと漠然としていて申し訳ないのですが、なぜ化けるんでしょう…。
いろいろ調べてみたんですが、binmode STDOUT, ":encoding(utf-8)";とかをつけても特に変化がありません…。

しかし変わりすぎてキツい…でも今は5.8仕様の文字処理ができないとあぶないのかなぁ…。

A 回答 (3件)

> ↓はPOSTされ、CGI.pmで受領したデータを、現在のperlスクリプトが使用するutf8形式にデコードする。

と考えてよろしいでしょうか?
> my $in_data1 = Encode::decode('utf8', $input_data->param('data1')); #変更
意味合いとしては、utf8の文字列を内部形式(uft8)にデコードしてます。
しかし実際には、UTF-8フラグが付くだけです。

私も前に答えたときは確信がなかったので言いませんでしたが、
文字化けする原因は$in_data1や$in_data2にUTF-8フラグが付いていないことです。

たとえば、以下のコードのようにわざとUTF-8フラグを落とすと
最後のprintで出力されたテキストはutf8としても読めないものになってしまいます。
#-----------------------------------------------------------#
use strict;
use utf8;
use open ":utf8";
use open ":std"; #標準入出力にutf8を指定。

my $data="こんにちは"; #use utf8してるのでUTF-8フラグが付いている

utf8::encode($data); #わざとUTF8フラグを落とす
#↑は$data = Encode::encode('utf8',$data); と同じ。

print $data; #正しく出力されない
# UTF8フラグが落ちてるので、出力時に$dataの内容がLatin 1と見なされて
# 強引にUTF8に変換(upgrade)されてしまい文字化けする。

#-----------------------------------------------------------#

あなたが最初に提示しているコードの場合、
文字化けはUTF8フラグが立っている文字列と
UTF8フラグが立っていない文字列を連結したときに起こっています。
このときもUTF8フラグが立っていない文字列がupgradeされるので
文字化けしてしまいます。

Encode::decode('utf8', $data); はutf8をutf8(内部形式)に変換しており一見無意味なようですが、
この処理をしないとperlは$dataがutf8だと言うことが分かりません。
なのでちゃんと教えてあげないと、perlは不適切なupgradeをしてしまうのです。

[参考]
Perl 5.8 以降においての Unicode 文字列の扱い方 : NDO::Weblog
http://naoya.dyndns.org/~naoya/mt/archives/00061 …
Perl 5.8.x Unicode関連
http://www.rwds.net/kuroita/program/Perl_unicode …


> もし元の文字コードがわからなかったら
Encode::Guessなどで推測は可能ですが、
推測は失敗することもあります。
普通は送り側も自分で書きますから
送られてくる文字コードが不明と言うことは普通はないのではないかと思います。


> binmode(STDIN,":encoding(sjis)") ;
ブラウザはgetやpostでデータを送るときに
「%A4%B3%A4%F3%A4%CB%A4%C1%A4%CF」のようにエンコードしてから送るので、
たとえ漢字やひらがなを送ったとしても実際に送られてくるのはASCII文字だけです。
(CGI.pmが元に戻しているので気づいていないかもしれませんが。)
なのでエンコード指定は無意味なきがします。
あと、postの場合はSTDINからデータが得られますが、
getの場合は環境変数から取得するのでSTDINのモードを変更しても影響ないでしょう。
    • good
    • 0

問題があると言ったけど、


CGI.pmはデコードまで責任を持たないと考えれば
これはこれで良い気もしてきた。

もし、post1.htmlをEUC-JPで書いた場合は、
#--------------------------------------------------
my $in_data1 = Encode::decode('euc-jp', $input_data->param('data1'));
my $in_data2 = Encode::decode('euc-jp', $input_data->param('data2'));
#--------------------------------------------------
とする必要があるわけだし、
内部コードに変換するために必ずこれが必要になったと思えば良いのかな。
    • good
    • 0

どうやら、CGI.pmの実装に問題があるようですね。


(私も基本的にuse utf8してますが、
CGI.pm使わずにpostデータを解析してるのでこの問題は今初めて知りました。)

とりあえず以下のような変更をすればちゃんと表示されると思います。

#--------------------------------------------------
use utf8;
use Encode; #追加

my $input_data = new CGI;
my $in_data1 = Encode::decode('utf8', $input_data->param('data1')); #変更
my $in_data2 = Encode::decode('utf8', $input_data->param('data2')); #変更
#--------------------------------------------------

#参考
CGI.pmとUTF8 flag : blog.nomadscafe.jp
http://blog.nomadscafe.jp/archives/000491.html
    • good
    • 0
この回答へのお礼

ご指摘のように修正しましたら、ちゃんと動くようになりました。
バグではないとは言え、CGI.pmに起因する問題だったのですね。
ありがとうございます。

できましたら少しばかり追加質問なのですが…、
↓はPOSTされ、CGI.pmで受領したデータを、現在のperlスクリプトが使用するutf8形式にデコードする。と考えてよろしいでしょうか?
my $in_data1 = Encode::decode('utf8', $input_data->param('data1')); #変更
(…もし元の文字コードがわからなかったら…いやでも最近はhtml側でもキャラセット指定が入るし、大丈夫なのかな)

と言うことはつまり、perl5.8で「use utf8;」を使用するということは、スクリプトファイル自体の文字コードを宣言することと同時に、スクリプトが動作するときに、どのような文字コード体系で文字を扱うかを指定する…と考えても良いのかな…。
従来は特に指示しない場合、スクリプトファイル自体の文字コードで処理していたと思うのですが…しっかり指定しないといけないのか…。

ちなみに携帯とかではSJISを使うことになりますが、この場合は下のようになるのでしょうか?
use utf8;
binmode(STDIN,":encoding(sjis)") ;
binmode(STDOUT,":encoding(sjis)") ;
…でも、調べてみるとbinmodeはファイルの取り扱いについて指定するとあるので、POSTデータには作用しないのかな…。いやそもそもCGI.pmを使う以上、ご指摘いただいたように全データをデコード、エンコードし直す…ことになるのかな…。

ううむ…むずかしい…。ちょっと今はスクリプトに触れないのですが、後日自分でも試してみます。

お礼日時:2008/11/17 19:51

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!