Perl/CGI研究室 'PERL-LABO'

Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

フォームデータのデコード

研究内容

ブラウザから送られてくる文字の一部はエンコードされています。これをデコードして 元の文字に戻す研究です。

詳細

なんで %〜 に変えて送ってくるの

HTMLのフォームから送られたデータは、半角文字の一部(& とか / など)、 それに日本語などの全角文字は %〜 というなんだか わからん文字に変換されてCGIプログラムに送られてきますよね。 これは、全角文字に含まれている & や = などと、 変数の区切りの & や変数と値を結ぶ = とを区別するためなのかなと思います。

エンコードって?

あるデータを一定のルールに従って別のデータに変換することをエンコードといいます。 ブラウザが文字を %〜 に変えるのが、エンコードですね。

デコードって?

エンコードされたデータを元のデータに戻すことをデコードっていいます。

変換のルール

まず、スペースが + になります。これはなんだか分かりませんがそうなってるので仕方ありません (^^;

スペース以外の & / などは、次のルールで変換されています。 半角文字は、1文字1バイトで、1バイトは0〜255までの数値を表すんだっていうのを 始めの方で書きました。A は 65 で、B は 66、というように。 そこで、変換するときは %数字 っていうように、この % の後ろにこの数値を付けるっていうのが、 ブラウザから送られてくるときのエンコードのルールです。

ただしこの数字、普段我々が使う10進数ではありません。 16進数っていうやつです。10進数では、10になると桁が上がりますが、16進数では16で桁があがります。 数字だと0〜9ですが、16進数だとこれでは足りないので 0〜9、A〜F を使います。 並べて書くと、16進数では 0 1 2 3 4 5 6 7 8 9 A B C D E F。 F は 15 です。10進数で16は、16進数だと1桁あがって、10 になります。

例えば、/ はこのままの文字では送られずに、%2Fってなって送られます。 試してみてください。

つまり、/ という文字は16進数の 2F という数字なんですね。パソコンの中では。

全角文字の場合、1文字が2バイトです。2バイトということは、 1バイトの数値(0〜255)が2つ、ということです。なので、 全角1文字は %〜%〜 というように %〜 が2つになります。 試してみてください。あ の場合 %82%A0 となります。

こんな感じの変換ルールに従ってデータが送られてきてます。 これを、元の文字に戻す研究をしましょう。

結果

元に戻すには

変換ルールは、スペースが + になっていることと、 もう1つ、文字を元の数値で表して、それを16進数にして %(2桁の16進数)ってなってるんでした。 これを元に戻すには、 + をスペースに置き換える、 %〜 となっているところを、〜の数値で表される文字に置き換えるっていうことを すればいいですね。

+ を元のスペースに戻すには

さて、ここでまたperlの凄い便利な機能がありますのでそれを使います。 置換機能です。置換というのは、テキストエディタとかにもありますね。 ○を△に置き換える、とかそういうやつです。 Perlには、文字列の中の○を△に置き換えるっていうのが、凄く簡単にできるんです!

ここで必要な置換のやり方は次のようなものです。

$string =~ tr/123/abc/;

これは、1→a、2→b、3→c、というようにそれぞれ対応させて置換します。 123をabcに変えるんじゃないっていうことに注意しましょう。 文字列の置換じゃなくて、文字の置換ですね。 置換されるのは、$string に入ってる文字列です。 =~ っていうのは見慣れないというか初めて見ましたが、 「=~ tr/」でセットになっていると考えればいいみたいです。

これを使って、+ をスペースに戻すことができますね。次のように すればいいんです。これで、1つは片付きました。

$string =~ tr/+/ /;
%〜 を元の文字にするには

%〜 を元の文字に置換する前に、 %〜 を元の文字に戻す方法を研究しましょう。それには、packっていう関数を使います。 packは次のように使います。

$char = pack("C", 数値);

"C" っていうのは、後ろの数値を元の文字にしてくださいっていう意味です。 例えば数値のところが 65 なら、元の文字 A が変数 $char に入ります。

それじゃ、数値のところに16進数を入れたい場合はどうしたらいいでしょう? だって、ブラウザから送られてくるのは、16進数で表した数値ですもんね。 そのためには、hex関数っていうのを使います。hex関数は、hex(16進数)ってすると、 10進数の数値に変えてくれるっていう便利なやつです。これを使って、

$char = pack("C", hex(16進数));

これで、%〜 を元に戻す方法が少し分かってきました。 でも、まだ全然ですね。%〜 の 〜のところを取り出すにはどうしたらいいんでしょうか。 前に勉強した split は使えそうもありませんし…。

ふたたび置換

さて、ここでまた置換機能です。 置換はさっき + をスペースに戻すのに使いましたが、 Perlの置換機能は、さらにプラスアルファの機能があって、 %〜 の 〜のところを取り出しながら %〜 を別の文字に置き換える、っていうのが一発で できちゃうんです!これで一気に解決です。Perlってホントいろんなことが できてしまうんですねぇ。

この置換のやり方は次のようなものです。

$string =~ s/探すパターン/置換後の文字列/g;

さっきの1文字ずつ置き換える「=~ tr/」に似ていますが、 ちょっと違います。とりあえず、 tr が s になっていることと、最後に g が付いていることが分かりますね。 最初の s は、文字列の置換です、という意味。 最後の g は、見つかったやつを全部置換しますよ、という意味です。

次に「探すパターン」のところですが、ここが実は結構難しい! 正規表現っていうやつを使うんです。 Perlを使いこなすには正規表現をマスターしなきゃ駄目、みたいなことを 耳にするくらい、Perlでは正規表現ってやつが凄く便利に使えるみたいです。 でも、初めて出てきた言葉ですし正規表現ってなんでしょう?

実は管理人、正規表現をよく知りません。初心者ですから…。 ここまで頑張ってきましたが、正規表現ってそれだけで分厚い本になっちゃうくらい 奥が深いものらしくて、ちょっと研究するには今はシンドイですね。 でも、ここで必要な正規表現だけはなんとか学ぶようにしましょう。

話を戻して、「探すパターン」ですが、ここで探すのは、「%16進数16進数」っていうもの でした。%のあとに16進数が2つです。16進数っていうのは、0〜9、A〜Fで表されるんでした。 だから、これは言い換えると、1つの文字を ( ) で囲むことにして、 「(%) (0〜9、A〜F) (0〜9、A〜F)」という3文字のものを探すんですね。 正規表現っていうのは、こういう「パターン」を表現する表現方法のこと。 正規表現では、探しているパターンは次のようになります。

%[A-Fa-f0-9][A-Fa-f0-9]

最初の % はそのまんまですね。後ろは、まず [ ] で囲まれているところが1文字を 表しています。[ ] の中は、その1文字の条件で、A〜Fまたはa〜fまたは0〜9、という意味に なっています。 アルファベットの大文字小文字は区別されますので、どちらでもいいように 大文字と小文字の両方を指定しています。 それが、2つ。なんとなく、分かりますよね。 さっき考えた、探すパターンをそのまんま表現している感じです。 表現の仕方のルールが独特ですが、それも、この例に関しては結構分かるように思いました。

さてこの正規表現を使って、置換命令はこうなります。

$string =~ s/%[A-Fa-f0-9][A-Fa-f0-9]/置換後の文字列/g;

さあ、次は「置換後の文字列」のところです。 ここに、先ほど作った、16進数を元の文字に戻すやつを入れてみましょう。

$string =~ s/%[A-Fa-f0-9][A-Fa-f0-9]/pack("C", hex(16進数))/eg;

まず、重要なこととして、末尾の g が eg になりました。 g は同じく、見つかったやつ全部置換しますっていう意味ですが、 e は、「置換後の文字列」のところを「式」とするっていう意味です。 「pack("C", hex(16進数))」っていう文字列に変換して欲しいんじゃなくて、 これを計算した結果に変換してもらいたいので、この e を忘れずにつける必要があります。

さて、赤字にしましたが、後ろの方のhexの中の16進数のところには、 正規表現のとこの[A-Fa-f0-9][A-Fa-f0-9]にマッチした(合致した)文字が 入るわけです。これつまり、マッチする文字列が見つかったら、 その見つかった文字列(の16進数の部分)を16進数の ところに入れて処理しないといけないっていうことですね。ここで、Perlのプラスアルファの 機能が役に立ちます。まさにやりたいことができます。見つかった文字を一時的な変数に 入れながら処理を行うことができるんです!こういうふうに

$string =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack("C", hex($1))/eg;

正規表現の中の16進数2桁にあたる部分を ( ) で囲みました。 そうすると、この ( ) の中の部分に対応する文字列が、一時的に $1 という変数に格納されるんです。 この $1 をhex関数に渡せば、見事に、やりたかったことが実現できます! いやーちょっと複雑になってきたけど、凄いですね。 ちなみに、$1 の 1 は、1つ目の ( ) で囲まれたやつっていう意味です。 今回は ( ) が1個しかないですが、( ) が複数ある場合は $1 $2 というようにして 区別することができるんでしょうね。いやはやよくできてます…。

デコード完成

これで、どうやらデコード(元に戻すこと)ができたようです。 まとめると、こうなります。

$string =~ tr/+/ /;
$string =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack("C", hex($1))/eg;

うーん大変だったけどたったの2行 (^^; これもPerlがよくできてるってことですね。

getformdata関数を修正

デコードするやつをgetformdata関数に組み込んで、今回の研究は終わりです。

作成したCGIプログラム

getformdatatest.cgi
#!/usr/bin/perl

require 'getformdata.pl';

print "Content-type: text/html\n";
print "\n";

%formdata = plab::getformdata();

print "input1 : $formdata{'input1'}<br>";
print "input2 : $formdata{'input2'}<br>";
getformdata.pl
package plab;

sub getformdata
{
	local $rawdata;
	local %formdata;
	local @inputs;
	local($input, $name, $val);

	if ($ENV{'REQUEST_METHOD'} eq "POST") {
		read(STDIN, $rawdata, $ENV{'CONTENT_LENGTH'});
	}
	elsif ($ENV{'REQUEST_METHOD'} eq "GET") {
		$rawdata = $ENV{'QUERY_STRING'};
	}

	@inputs = split('&', $rawdata);

	foreach $input (@inputs) {
		($name, $val) = split('=', $input);
		$val =~ tr/+/ /;
		$val =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack("C", hex($1))/eg;
		$formdata{$name} = $val;
	}

	return %formdata;
}

1;

実行結果

下の入力ボックスに文字列を入力して、送信ボタンを押してください。 別窓が開きます。

input1
input2

考察

動きました

いやー今回の研究は結構大変でした。 最初にGETメソッドの研究をしてから、ずっと気になっていながら、 後回しにしていた文字変換がようやくできました。 勉強になりました〜

あともうちょっと!

フォームからのデータの受け取り、これでほぼ完成です。 でも、まだ問題が残ってますね。文字コードの問題です。 文字コードについてはなにも処理していないです。 そのへんをどうするか。次回の研究テーマです。

分かったこと

  1. フォームから一部の半角文字、全ての全角文字は変換(エンコード)されて送られます。
  2. エンコードのルールは、スペースを+、他のは%〜に変えるっていうものでした。
  3. デコードは、その逆をやってあげます。
  4. デコードに必要な知識は、文字置換、文字列置換、正規表現、pack、hexです。
  5. 文字置換は「=~ tr/〜/〜/;」でできました。
  6. 文字列置換は「=~ s/正規表現/置換後文字列/eg;」でできました。
  7. 数値を文字にするには pack を使います。
  8. 16進数を10進数にするには hex を使います。
  9. 正規表現は難しいみたいですがここで使った正規表現は意外と理解しやすかったです。
  10. 正規表現の中で ( ) で囲った部分を $1 として参照できます。
Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

Copyright (c) 'PERL-LABO' All Rights Reserved.  リンクフリーです。