sub decode
sub decode
目次
はじめに
ここではSOSIIがCGIとして動作するための要となるsub decodeについて解説します。
sub decodeでは主に
- URLのデコード
- 文字コードのコンバート
- HTMLタグの無効化
- スプリッターとして使用する文字列のエスケープ
を行っています。
ソースコードの解説
# Sub Decode # sub decode {
if ($ENV{'REQUEST_METHOD'} eq "POST") {
まず環境変数からファイルメソッドPOSTまたはGETを評価します。
$post = 1;
POSTメソッドであった場合、$postという変数に1を代入しフラグを立てます。
Ver.1.10の場合、$post変数が真でなければ多くのサブルーチンが実行できません。
これは不正行為への対策と思われます。
read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
STDINで入力されたフォームデータをファイルハンドルによりCONTENT_LENGTHの長さ(ようするに全て)読み込み、$bufferに代入します。
@pairs = split(/&/, $buffer);
$bufferに代入されたフォームデータをsplit関数で、&をスプリッターとして分解し@pairsという配列に格納します。
例としてフォームデータが「id=0000&ps=PASS&nm=NAME」であった場合
- @pairs = (id=0000,ps=PASS,nm=NAME);
となります。
} else { @pairs = split(/&/, $ENV{'QUERY_STRING'}); }
GETであった場合、上と同じくQUERY_STRINGを@pairsという配列に格納します。
QUERY_STRINGはフォームデータ自体が格納されていますので、ファイルハンドルによる操作は不要になります。
foreach $pair (@pairs) {
foreach構文によるループ構文で@pairs配列を展開します。
$pairには@pairsの要素が順に代入されてゆきます。
ループは@pairsの要素の数だけ繰り返して行われます。
($name, $value) = split(/=/, $pair);
$pairに代入された変数をsplit関数で、今度は=をスプリッターとして分解します。
分解されたものは、それぞれ$name、$valueという変数に代入されます。
例として$pairが「id=0000」であった場合
- $name = id;
- $value = 0000;
となります。
$value =~ tr/+/ /;
tr///演算子を使いパターンマッチによる置き換えを行っています。
+という文字を (半角スペース)に変換しています。
URLエンコードのさい、送られてくるデータの半角スペースが+に置き換えられてCGIに渡されるためこのような変換が必要になります。
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
s///演算子を使いパターンマッチによる置き換えを行っています。
日本語の文字列はURLエンコードによって%xxという形でフォームに送られます。
これをpack関数でもとの文字列に置き換えています。
$1には左側の一番目の括弧([a-fA-F0-9][a-fA-F0-9])にマッチした内容が入ります。
オプションeは右側の評価を行います。
上の式の場合、置き換えを行う前にpack("C", hex($1))を実行し、その評価結果を置き換えの対象としています。
オプションgは繰り返し置き換えを行います。
上の式の場合、%xxという形式の文字列をすべて置き換えるまで実行しています。
&jcode'convert(*value,'sjis');
$valueの変数を日本語コード(Shift-JIS)に変換しています。
- &jcode'convert(*スカラ,"文字コード");
というのはjcode.plというライブラリに入っている命令文です。
スカラを文字コードのタイプへと変換します。
$value =~ s/</</g; $value =~ s/>/>/g; $value =~ s/"/"/g; $value =~ s/\,/,/g; $value =~ s/△/▲/g; $value =~ s/\r\n/<br>/g; $value =~ s/\r/<br>/g; $value =~ s/\n/<br>/g;
$valueに代入された一意の文字を置き換えています。
HTMLタグや改行コード、システム上スプリッターとして使用している文字等が該当しています。
$Fm{$name} = $value;
フォームで送信された値を最終的な形($Fm{'hoge'})として代入しています。
- $Fm{'id'};
という形は%Fmという連想配列(ハッシュ)の参照です。
- id=0000(<input type=hidden name=id value=0000>)
であるならば
- $Fm{'id'} = 0000;
となります。これは%Fmというハッシュのキー値idに対してペア値0000を代入している式になります。
この部分がSOSIIの汎用性の高さを物語っていると、勝手に思っています。
} }
実際の動き
<input type=hidden name=nm value=なまえ> <input type=hidden name=ps value=ぱす>
上記のようなフォームであった場合、これをスクリプトへ送信すると、クエリーは「nm=なまえ&ps=ぱす」となります。
この時、URLエンコードが行われ、実際の文字列は「nm=%82%C8%82%DC%82%A6&ps=%82%CF%82%B7」と変換されます。
この文字列がファイルハンドルにより$bufferに代入されます。
$buffer = 'nm=%82%C8%82%DC%82%A6&ps=%82%CF%82%B7';
$bufferは分割され@pairsに格納されます。
@pairs = (nm=%82%C8%82%DC%82%A6,ps=%82%CF%82%B7);
ループにより@pairsの要素を順に評価していきます。
$pairへ代入された要素をさらに分割し、$nameと$valueに代入されます。
$name = 'nm'; $value = '%82%C8%82%DC%82%A6';
$valueの置き換えを行います。
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
に当てはめた場合、gオプションによって
$value =~ s/%82/pack("C", hex(82))/e; $value =~ s/%C8/pack("C", hex(C8))/e; $value =~ s/%82/pack("C", hex(82))/e; $value =~ s/%DC/pack("C", hex(DC))/e; $value =~ s/%82/pack("C", hex(82))/e; $value =~ s/%A6/pack("C", hex(A6))/e;
このようになるはずです。
しかし、実際には
$value =~ s/%82%C8/pack("C2", hex(82), hex(C8))/e; # pack("C2", hex(82), hex(C8)) = な $value =~ s/%82%DC/pack("C2", hex(82), hex(DC))/e; # pack("C2", hex(82), hex(DC)) = ま $value =~ s/%82%A6/pack("C2", hex(82), hex(A6))/e; # pack("C2", hex(82), hex(A6)) = え
と同じ動作をしているようです。
eオプションで右側式の評価結果に置き換えられますので、最終的には
$value = 'なまえ';
となります。
ここまでで(URLエンコードに対する)URLデコードが終了します。
これがデコードの必要性です。
さらに$valueはコンピューターで扱うための文字、Shift-JISにコンバートされます。
$valueに一意の置き換えを行った後、%Fmというハッシュに格納されます。
ループ構文を繰り返し最終的にすべての$pairをハッシュに格納します。
%Fm = ('nm','なまえ','ps','ぱす');
このハッシュを各々のルーチン内でフォームで送信された情報として参照します。
%Fm{'nm'} = 'なまえ'; %Fm{'ps'} = 'ぱす';
キーワード解説
$ENV{'hoge'} は環境変数を表します。
環境変数とはクライアント(プレイヤー)側の情報で、%ENVという連想配列に格納されます。
REQUEST_METHOD
スクリプト(CGI)へのデータの受け渡し方法(GETまたはPOST)を格納しています。
CONTENT_LENGTH
POSTによるフォームデータの長さ(バイト数)を格納しています。
QUERY_STRINGS
GETによるフォームデータそのものを格納しています。
read関数(read FILEHANDLE, SCALAR, LENGTH, [OFFSET])
ファイルハンドルからデータを読み込む関数です。
FILEHANDLEからLENGTH分の長さのデータを読み取りSCALARに代入します。
OFFSETを指定することで任意の部分からLENGTH分の長さの読み出しが可能になります。
STDIN
標準入力の意味で、パソコンの場合はキーボード入力が標準入力デバイスとして指定されています。
FILEHANDLEが指定されなかった場合もSTDINになります。
split関数(split /PATTERN/, [EXPR, LIMIT])
PATTERNをスプリッターとしてEXPRを分解します。
LIMITを指定することで分解する最大数を指定することができます。
foreach構文(foreach SCALAR ( LIST ) { BLOCK })
LISTを順にSCALARへと代入し、BLOCKを実行します。
LISTのからの代入が終了した時点でループは終了します。
pack関数(pack TEMPLATE, LIST)
LISTをTEMPLATEで指定したフォーマット文字によりパック(変換)します。
長いし複雑なので省略します。
ただし日本語のデコード操作には必須です。
hex関数(hex EXPR)
EXPRを16進数と解釈し、10進数へと変換します。
tr///演算子(tr/SEARCH/REPLACE/cds)
検索文字SEARCHに含まれる各文字を置き換え文字REPLACEに一文字ずつ置き換えます。
この場合の一文字ずつ置き換えるとは、例えば
$str = 'ABACBC'; $str =~ tr/ABC/DEF/;
のとき、tr演算子はAをDに、BをEに、CをFに置き換えます。結果は
print $str; # DEDFEF
となります。
tr///演算子の詳しい動作、オプションについては省略します。
※SEARCH、REPLACE とも、正規表現ではないので注意。
s///演算子(s/PATTERN/REPLACE/egimosx)
検索文字PATTERNを使って検索を行い、PATTERNにマッチする文字が見つかればREPLACEで置き換えます。
s///演算子の詳しい動作、オプションについては省略します。
※SEARCH は正規表現、REPLACE は正規表現ではないので注意。
関連項目
このページのコメント
- ページ名はsub decodeのほうがいいかもしれない・・・ - Uchimata (2007年01月15日 03時47分03秒)