【Webプログラミング - Code.004】CGI(Perl)基本動作 その4:「URLエンコード・デコード」

   Code.004                                                 2002年10月28日発行
■━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━■
                           【 Webプログラミング 】

                       〜 猫的プログラマーとその軌跡 〜
■━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━■

              ▼毎週月曜日に配信しています。
              ▼等幅フォントでご覧いただくとキレイに見えます。
              ▼登録・解除はこちらから可能です。
                < http://www.ichikoro.com/webp/ >
                  ※ぜひお友達にもご紹介ください(^^)/

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 CGI(Perl)基本動作 その4:「URLエンコード・デコード」
──────────────────────────────────────
URLでは使ってはいけない文字があるんです。
例えば全角文字をそのままURL上に記述してはいけません。

  http://www.google.co.jp/search?q=愛のタテーガミ〜♪   ← マズイ
                                   ~~~~~~~~~~~~~~~~~~
そんな時は対象の文字に対して「URLエンコード」を施してやります。
例えばGoogleで検索した場合、

  http://www.google.co.jp/search?q=%88%A4%82%CC%83%5E%83e%83K%83%7E%81%60%81%F4

というようなURLを見たことありませんか?

URLエンコードは通常ブラウザ(クライアント)が行います。
ところが、CGIで受け取った値もURLエンコードが施された状態のため、
そのままでは利用できません。

そこで、まずはエンコードされた物を元に戻す「デコード」と呼ばれる作業が
必要になるのです。

今回は、「URLエンコード」と、そのデコードについて解説したいと思います。

───────
 ソース1(HTML)
───────
<HTML>
<HEAD>
    <TITLE>クエリー表示</TITLE>
</HEAD>
<BODY bgcolor="#FFFFFF">

<H2>クエリー表示</H2>

<FORM action="printquery.cgi" method="POST">
    氏名:<INPUT type="text" name="namae"><BR>
    年令:<INPUT type="text" name="age" size="10">歳<BR>
    性別:<INPUT type="radio" name="sex" value="man">男性 
          <INPUT type="radio" name="sex" value="woman">女性<BR>
    居住地域:<SELECT name="area">
                    <option value="1">北海道
                    <option value="2">東北
                    <option value="3">関東
                    <option value="4">東海
                    <option value="5">北陸
                    <option value="6">関西
                    <option value="7">中国
                    <option value="8">四国
                    <option value="9">九州・沖縄
                    <option value="99">その他
            </SELECT><BR>

    好きな食べ物:
        <INPUT type="checkbox" name="koubutsu" value="ichigo">いちご 
        <INPUT type="checkbox" name="koubutsu" value="mikan">みかん 
        <INPUT type="checkbox" name="koubutsu" value="ringo">りんご 
        <INPUT type="checkbox" name="koubutsu" value="banana">ばなな<BR>
    <BR>
    何か一言:<BR>
    <TEXTAREA rows="5" cols="50" name="word"></TEXTAREA><BR>
    <BR>
    <BR>
    <INPUT type="submit">
</FORM>

<HR>
<DIV align="right"><SMALL><I>Webプログラミング</I></SMALL></DIV>

</BODY>
</HTML>


───────
 ソース2(CGI)
───────
#!/usr/bin/perl

;#
;#渡されたクエリーを表示する(printquery.cgi)
;#

use strict;

#==================================================#
#                  メインルーチン                  #
#==================================================#
package main;
{
    my %form;
    my $key;

    #--------------------------------------------------#
    #             クエリーをデコードし取得             #
    #--------------------------------------------------#
    util::getQuery(\%form);

    #--------------------------------------------------#
    #                   ヘッダ表示                     #
    #--------------------------------------------------#
    print "Content-type: text/html\n\n";
    print <<"END_OF_HTML";
<HTML>
<HEAD>
    <TITLE>printQuery</TITLE>
</HEAD>
<BODY bgcolor="#FFFFFF">

<H2 align="center">printQuery</H2>
<DIV align="center">
<TABLE border="1">
END_OF_HTML

    #--------------------------------------------------#
    #                   クエリー表示                   #
    #--------------------------------------------------#
    foreach $key ( sort keys %form ){
        print qq{<TR><TD align="right">$key</TD><TD>$form{$key}</TD></TR>\n};
    }


    #--------------------------------------------------#
    #                   フッタ表示                     #
    #--------------------------------------------------#
    print <<"END_OF_HTML";
</TABLE>
</DIV>

<HR>
<DIV align="right"><SMALL><I>Webプログラミング</I></SMALL></DIV>

</BODY>
</HTML>
END_OF_HTML


    #--------------------------------------------------#
    #                      正常終了                    #
    #--------------------------------------------------#
    exit(0);
}


package util;

#--------------------------------------------------------------#
#■クエリーを取得する
#    内容:GET/POST判定をしクエリーを取得する。
#          URLデコード後、指定のハッシュへ格納する。
#
#    引数:(1)格納先ハッシュ:リファレンス
#  戻り値:なし
#--------------------------------------------------------------#
sub getQuery{
    my $form = shift;     #クエリーをセットするハッシュ(リファレンス)
    my $buff;
    my @pairs;
    my $pair;

    #-- クエリー文字列取得 --#
    if ($ENV{'REQUEST_METHOD'} eq "POST"){
        read(STDIN, $buff, $ENV{'CONTENT_LENGTH'});
    }
    else{
        $buff = $ENV{'QUERY_STRING'};
    }

    #-- name=valueの組み合わせに分ける --#
    @pairs = split(/&/, $buff);      # $buffは
                                     #     name1=value1&name2=value2& ... &nameN=valueN
                                     # となっているハズ

    #-- デコードしつつハッシュにセット --#
    foreach $pair ( @pairs ){
        my( $name, $value ) = split(/=/, $pair);  #$pairは name=value となっているハズ

        #-- URLデコード --#
        $value =~ tr/+/ /;    #“+”を空白に
        $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;   # %XX を本来の
                                                                       # 文字コードに

        #-- ハッシュにセットする --#
        if( exists( $form->{$name} ) ) {
            $form->{$name} .= "," . $value;        #同名の場合は','で連結する
        }
        else{
            $form->{$name} = $value;
        }
    }

}

__END__


───────
   実行方法
───────
テキストエディタ(メモ帳やSimpleText)などで上記のソース(プログラム)を
保存してください。ソース1は適当な名前(xxxx.html)でOKです。ソース2は
「printquery.cgi」とすると、HTMLを変更せずにすみます。

CGIの詳しい実行方法については、Code.001をご参照ください。
http://backno.mag2.com/reader/BackBody?id=200210050820000000080329000

フォーム要素(プルダウンやラジオボタンなど)を色々といじったり、
追加・削除しつつ試してください。

  ★重要!
   このCGIはセキュリティーホールを含みます。
   学習以外の用途には使用しないでください。


───────
    解 説
───────

■どんな時に必要なの?<URLエンコード

    デコードとはエンコードの逆の作業のことです。
    というわけで、まずはURLエンコードについて学びます。

    それでは、どんな時に必要になってくるのでしょうか?
    ここでは“エンコード不用の文字”を上げた方が理解しやすいでしょう。

        ・エンコード不用の文字

            (1)半角英数字
                a b c d e f g h i j k l m n o p q r s t u v w x y z
                A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
                0 1 2 3 4 5 6 7 8 9

            (2)一部の記号
                * - . @ _

    例えば、以下の様な場合は問題ありません。

        ・半角英数字のみ
            http://www.google.co.jp/search?q=WebPrograming
                                             ~~~~~~~~~~~~~
        ・記号を含む
            http://www.google.co.jp/search?q=katsube@ichikoro.com
                                             ~~~~~~~~~~~~~~~~~~~~

■URLエンコードのルール

    これ以外の文字について、以下の様なルールでエンコードを行います。

        ・半角スペース
            半角スペースは一律 '+' へ変換します。

                name=value1 value2
                        ↓
                name=value1+value2

        ・それ以外
            1byteづつ、文字コードを2桁の16進数に変換しその頭に
            '%' を付加する。

                name=Hello!
                    ↓
                name=Hello%21


■具体的にどうやって行うの?

    ・手動で行う
        半角文字の場合は、ASCIIコード表と照らし合わせることで可能です。

            ・e-Words:ASCII文字コード
              http://e-words.jp/p/r-ascii.html
                ※16進の列の、0xの右にある値をそのまま使います。

    ・プログラム
        半角の場合(で尚且つ少量の場合)は上記の方法で行えますが、大抵の場合
        あまり現実的な方法とはいえません。
        そんな時は

            ○プログラム例(Perl)

                print url_encode("こんにちは〜");

                sub url_encode{
                    my $str = shift;

                    $str =~ s/(\W)/sprintf("%%%02X", unpack("C", $1))/eg;    #ここでエンコード

                    return($str);
                }

            ○実行例(文字コードはSJIS)
                C:\>perl encode.pl
                %82%B1%82%F1%82%C9%82%BF%82%CD%81%60

        といったプログラムを組むと良いかもしれません。
            ※URLエンコードを行うモジュールがありますが、その解説は後日。


■URLデコードはどうやるの?

    これまで解説したことの逆を行います。
    つまり、

        (1) '+' は半角スペースへ変換
        (2) '%' の後に2桁の数値がある場合は、文字コードに変換

    という作業を行えばいいわけです。
    サンプル上で言えば、サブルーチンgetQuery内の下記の部分になります。

>        $value =~ tr/+/ /;    #“+”を空白に
>        $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;   # %XX を本来の
>                                                                       # 文字コードに


■getQueryの使い方 (1)

    クエリーの値を使用する前に、getQueryを下記のように
    呼び出します。

>    #--------------------------------------------------#
>    #             クエリーをデコードし取得             #
>    #--------------------------------------------------#
>    util::getQuery(\%form);

    指定した連想配列(ハッシュ)に、クエリーが全て詰め込まれます。
    この場合は %form に全て入ることになります。

    では、ここからどうやって値を取り出すかと言うと、
    例えばHTML上で

        <FORM action="〜.cgi">
            <INPUT type="hidden" name="beer_middle" value="380">
            <INPUT type="hidden" name="beer_big"    value="500">
        </FORM>

    というフォームの値をCGIへ渡した場合、
    CGI上では

        util::getQuery(\%form);

        print "Content-type: text/plain\n\n";
        print "中ジョッキは?" . $form{'beer_middle'} . "円\n";
        print "大ジョッキは?" . $form{'beer_big'}    . "円\n";

    などと実行すると、動作が分かると思います。


■getQueryの使い方 (2)
    では、同名のクエリーを渡した場合どうなるのでしょう?

        <FORM action="〜.cgi">
            <INPUT type="checkbox" name="beer" value="big">
            <INPUT type="checkbox" name="beer" value="middle">
        </FORM>

    下記のようなCGIを実行してみましょう。

        util::getQuery(\%form);

        print "Content-type: text/plain\n\n";
        print $form{"beer"};

    ここまで解説すれば、後はgetQueryのコードを追って、
    理解してください。そんなに難しくないハズです。


───────
   次回予告
───────
クエリーの受け渡しについて一通り解説しましたが、実はまだ問題があります。
それは「文字コード」です。

歴史的な経緯から、いくつかのコード体系が存在します。
CGIはどの文字コードで渡ってくるのか、あらかじめ知ることはできません。
そこで、次回は複数の文字コードに対応する術をご紹介したいと思います。


───────
  分からない
───────
いまいちよく分からない場合は、以下へれっつらごー。

    ・サポートBBS
      このメルマガ専用のサポート掲示板
          http://www.ichikoro.com/webp/bbs/

    ・CGIプログラミングML
      CGIなどWebに関する話題を繰り広げるメーリングリスト
      このメルマガとは何の関係もありませんので発言時は注意を。
          http://www.ichikoro.com/cgi/ml/

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
                           編    集    後    記
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
10月22日(火)の朝、大学へ願書を郵送しました。

後は小論文と面接です。
また一つ、やるべきことをやった、そう思うと気が楽になりました。
と同時に色々な思いが頭を駆け抜けました。

大学に通えば(残業代が丸々入ってこないので)収入は2/3程度になりますし、
自分の時間も減ります。その上で学費の工面。そして仕事との両立は、今私
が考えているより圧倒的にハードだと思います。

正直言うと、かなり不安です。
それでも、大学に通って、似たような人たちとワイワイ勉強している
様子を思い浮かべると、今度は期待に打ち震えそうになる自分もいる
のです。

今は、やるべきことを、しっかりやろう。
後悔はしたくないですからね。


そうそう、「読むだけ小論文」という本を教えてもらって読んでいるのですが、
これが読み物としても面白いのです!小論文を書くためのノウハウ集ではなく、
「グローバル化」「豊かな社会のひづみ」など、ポイントを絞ってズバッズバッ
と斬ってあるのが、非常に気持ちいいです。

サラリーマンだと、レジでちょっと勇気がいるかもしれませんが、
興味のある方はどうぞ(^-^)/

 ・読むだけ小論文 (1)  大学受験ポケットシリーズ
  http://www.amazon.co.jp/exec/obidos/ASIN/4053011043
      ※文庫本くらいの大きさです。
        受験コーナーみたいなところに置いてあります。


それでは、また来週お会いしましょう (^-^)/~~

■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

                   【 Webプログラミング Code Sample 】

                    発  行 : ichikoro.com
                発行責任者 : 勝部 麻季人
                              < katsube@ichikoro.com >
                 Webサイト : < http://www.ichikoro.com/webp/ >
            お問い合わせ先 : < mm-webp@ichikoro.com >

                            Powerd by まぐまぐ
    All Right Reserved, CopyRight(C) 2001 Webプログラミング Code Sample
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■