受け取ったデータ処理のためのプログラム
前回で、データの送信がどのように行われるか理解できたと思いますが、送信されたデータをプログラム内で処理できるように整理する必要があります。下のソースは、「項目名1=値1&項目名2=値2&項目名3=値3」といったデータを$bufferという変数に取り込むための処理です。
POSTとGETの場合わけ - 分岐処理
この場合、先ほども書きましたが、POSTで送信された場合とGETで送信された場合で処理が違う(格納される変数が違う)ため、場合分けを行っています。
01: if ($ENV{'REQUEST_METHOD'} eq "POST") {
02: read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
03: } else {
04: $buffer = $ENV{'QUERY_STRING'};
05: }
【簡易解説】
01: もしも $ENV{'REQUEST_METHOD'} が POST なら
02: STDINという変数から $ENV{'CONTENT_LENGTH'}の長さだけデータを読み出して
$bufferに入れる
03: それ以外の場合(GET)は下の処理
04: $ENV{'QUERY_STRING'}からデータを読み出して$bufferに入れる
05: 以上
まず、「$ENV{'REQUEST_METHOD'}」という特別な変数を参照して、要求が「POST」か「GET」かのチェックを行います。この「$ENV{'REQUEST_METHOD'}」は環境変数と言われ、要求等に関する様々な情報を知ることができます。これ以外にも様々なものがありますので、順次紹介したいと思います。
POSTで送信された場合
POST の場合は、標準入力(STDIN)として入力されるので、上記のように $buffer に対して、$ENV{CONTENT_LENGTH} の長さのデータを入力する、というようにして処理します。(上記2行目。$ENV{CONTENT_LENGTH}は入力されたデータの長さをチェックする環境変数です。)
GETで送信された場合
GETの場合は、URLとして送信されるので、そのデータの長さに限界があります。 また、送信されたデータは標準入力ではなく、$ENV{QUERY_STRING} に格納されるので、その中のデータをそのまま $buffer に入力しています。(上記4行目)
ちなみに、ここでは$bufferという名前の変数を使いましたが、どんな名前のものでもOKです。これで、プログラム内にデータを取り込むことができました。ただ、この状態では
$buffer = 「項目名1=値1&項目名2=値2&項目名3=値3」
というつながった一つのデータに過ぎませんので、これを扱えるようにバラバラにする必要があります。
まとまったデータを分離する処理
下記の処理では、$bufferに格納されたデータを「&」を区切り文字として分離する処理です。これを、&で区切れることができなくなるまで繰り返します。「foreach」とはPerlで利用できるそのような繰返し処理です。
foreach (split(/&/, $buffer)) {
処理(ここはあとで解説)
}
ですが、&で切り取られたデータはどこにいってしまったのでしょうか? これは、Perlの書き方の問題なのですが、Perlはソースの表記をかなり省略できます。この場合、上記の太字の部分で切り取られたデータは、「$_」という汎用的な変数に格納されて、変数を使うことを明示しなくても使えるという非常に便利(あいまい)な機能です。この「$_」は明示して使うこともできますし、明示せずに省略して関数を使うことで、自動的に引数として渡すこともできます。
ですから、foreach(split(/&/,$buffer)) は正確に記述すると、
foreach $_ (split(/&/,$buffer))
ということになります。
この切り取られたデータは
$buffer=「項目名1=値1&項目名2=値2&項目名3=値3」
↓
項目名1=値1
項目名2=値2
項目名3=値3
上記のように分解されますが、まだ項目と値が=でつながっているのでさらにこれらを分離する必要があります。よって、「foreach(){}」の繰り返し処理の中で、この分解されたそれぞれを、さらに「=」で分離して、
($keyword, $value) = split(/=/);
↓
$keyword
$value
*ちなみに、上の式は次の式の省略形です。
($keyword, $value) = split(/=/, $_);
というように変数にそれぞれ格納します。
これを、$keywordと$valueという関連づいた変数で扱うことができるように、ハッシュに格納します。(Perlでの常套手段です。)
$FORM{$keyword} = $value;
↓
$FORM{'項目名1'}=値1
$FORM{'項目名2'}=値2
$FORM{'項目名3'}=値3
エンコード処理 - 日本語として読めるように
これで入力されたデータをプログラム中で変数として扱うことができるようになります。 基本的にはこれでOKなのですが、ちょっと困る場合もあります。先ほど上でも書きましたが、「GET」でも「POST」でも送信するときに「%82%A0%82%A2%82%DC%82%A2...」という文字に変換されてしまいます。(エンコードという)
英数だけなら問題ないのですが、それ以外の文字や日本語の文字はこのように通常では読めない形式に変換されてしまうので、これを読めるように戻してやる必要があります。(デコードという)
そこで、$value に格納されたデータの中に、「%**」という形式で書かれたデータを見つけた場合、下記のように%だけと取り除いて、通常の16進数のコードを取り出す必要があるのです。(これは16進数だったのですね!)
そこで、次のような式が必要になります。
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
これは、色々な処理を効率よく一行にしてしまっているので分かりにくいのですが、簡単に書くと下のような処理を順番にしています。
$value =~ s/(データ1)/(データ2)/eg;
これは、$value の文字列に「データ1」に該当する部分があれば「データ2」に置換するという処理です。
%([a-fA-F0-9][a-fA-F0-9])
この場合、「%」で始まるアルファベットまたは数字で構成される二桁の文字列(例、%82%A0%82%等)を見つけたら変換しなさい、
という命令となっています。
pack("C", hex($1)
変換の仕方ですが、%だけ抜き出して変換された16進数を10進数に変換して、
それを文字(Char)として更に変換したものに置き換えなさいという処理を行っています。
最後に、その内容を $value に上書きして終了。
つまり、「%82%A0%82%A2%82%DC%82%A2...」のような読めない文字が読める文字に変換されます。
入力を受付けるデータが英数文字だけならこれで問題ないのですが、日本語を使いたい場合もあります。このような場合は、文字コードに従った処理をする必要があるので、(WindowsはShift-JIS, UnixはEUCなど、、、)jcode.pl というモジュールにそのあたりの処理を任せます。
# S-JISコード変換
&jcode'convert(*value, "sjis", "", "z");
この部分は、説明が難しくなるので、日本語を使う場合には、これが必要だ、、、と覚えておいてもらえばOKだと思います。
不要なデータの除去 - エスケープ処理
あと、これは後ほどセキュリティのところで別途とりあげようと思うのですが、入力されたデータを処理に移す前に加工する必要があります。プログラム中に不特定多数からデータ入力を許すわけですから、そのデータをチェックする必要があるのです。データの中には、改行コード等が入っていることもあり(カット&ペーストで入力した場合など)、それが入力されると、処理がうまくいかないこともありえます。よって、ここでは、「\r」といった改行コードを無効にする処理をしています。また、空白は「+」とエンコードされてしまうので、これも通常のスペースとなるように戻す処理をしています。
$value =~ s/\r//g;
$value =~ s/\n//g;
$value =~ tr/+/ /;
それ以外にも入力を避けたほうがいい文字はたくさんあります。これを「エスケープする」というのですが、その理由等についてはかなり奥が深く、ここで簡単に説明するには重要すぎるトピックなので、別途説明したいと思います。 (ちなみに「掲示板作成:データのサニタイズ」でも取り上げています。)
以上の処理をまとめたソースは次のようになります。
01: if ($ENV{'REQUEST_METHOD'} eq "POST") {
02: read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
03: } else {
04: $buffer = $ENV{'QUERY_STRING'};
05: }
06:
07:
08: foreach (split(/&/, $buffer)) {
09: ($keyword, $value) = split(/=/);
10: $value =~ tr/+/ /;
11: $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
12:
13: # S-JISコード変換
14: &jcode'convert(*value, "sjis", "", "z");
15:
16: $value =~ s/\r//g;
17: $value =~ s/\n//g;
18:
19: $FORM{$keyword} = $value;
20: }
これで、入力されたデータに関する処理は以上なのですが、これだけ見ても何がどうなっているか理解できないかもしれませんので、次回で実際に簡単なプログラムを使って解説してみたいと思います。





