!!!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//>/g; $value =~ s/"/"/g; $value =~ s/\,/,/g; $value =~ s/△/▲/g; $value =~ s/\r\n/
/g; $value =~ s/\r/
/g; $value =~ s/\n/
/g; $valueに代入された一意の文字を置き換えています。 HTMLタグや改行コード、システム上スプリッターとして使用している文字等が該当しています。 $Fm{$name} = $value; フォームで送信された値を最終的な形($Fm{'hoge'})として代入しています。 *$Fm{'id'}; という形は%Fmという連想配列(ハッシュ)の参照です。 *id=0000() であるならば *$Fm{'id'} = 0000; となります。これは%Fmというハッシュのキー値idに対してペア値0000を代入している式になります。 この部分がSOSIIの汎用性の高さを物語っていると、勝手に思っています。 } } !!実際の動き 上記のようなフォームであった場合、これをスクリプトへ送信すると、クエリーは「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秒)