トップ 新規 一覧 Farm 検索 ヘルプ RSS ログイン

sub decodeの変更点

  • 追加された行はこのように表示されます。
  • 削除された行はこのように表示されます。
!!!sub decode解説
!!!sub decode
目次
{{outline}}

!!はじめに
ここでは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/</&lt;/g;
 		$value =~ s/>/&gt;/g;
 		$value =~ s/"/&quot;/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("C", hex(82), hex(C8))/e; # pack("C", hex(82), hex(C8)) = な
 $value =~ s/%82%DC/pack("C", hex(82), hex(DC))/e; # pack("C", hex(82), hex(DC)) = ま
 $value =~ s/%82%A6/pack("C", hex(82), hex(A6))/e; # pack("C", hex(82), hex(A6)) = え
 $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秒)