はじめに

ここでは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("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 は正規表現ではないので注意。

関連項目

  • コラム

コメント

コメントはありません。 Comments/リファレンスマニュアル/サブルーチン/decode?

お名前:

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2019-04-30 (火) 00:57:39 (718d)