FORMデータの処理


次にこの章でそのデータをCGIで受け取って適切に処理する方法を解説します。


● FORMデータの形式

FORMから送られたデータはCGIプログラムの標準入力(stdin)から読み込むことが できます。そのデータを受け取る方法にはgetchar()やfread()などが ありますが、おそらくfread(stdin)がもっとも適切でしょう。

FORMから送られるデータは、その各種データが転送用に加工され 全体が一行にまとめられて送られてきます。
変数の名前と値は'='マークで連結された一綴りの文字列に自動的に 変換されています。さらにその文字列が他の変数の同様な文字列と「'&' マークで連結されて長い一文字列となって送られてくるのです。 つまり次の様です。

name1=value1&name2=value2&name3=value3&name4=value4

この変数の順番はFORMでの配置順です。

このように '=' や '&' などの文字が特別の意味を持つので、 ユーザーがFORMに書き込んだこれらの文字と区別できるようにするため、 いくつかの特殊文字はbrowserによって特殊加工されてから送信されます。 その特殊加工の仕方は簡単で、%の文字に続いて、その特殊文字のascii codeを 16進数の2桁の数字の文字列を加えます。
漢字の文字も同様に特殊文字とみなされます。漢字は2byte文字なので1byte ごとにこの特殊加工が行われます。例えば「渡辺尚貴」なら %93%6E%95%D3%8F%AE%8B%4Dのような具合です。 さらに半角スペースは「+」のマークに変換されてます。

FORM処理CGI側は送られてきた文字列が何byteなのかを知る必要が あります。その情報は環境変数 CONTENT_LENGTH にセットされます。


● FORMデータの受信

まず環境変数 CONTENT_LENGTH を参照してstdinから来る文字列の長さを 調べます。それによってその文字列を格納できる分の配列変数を用意します。 そしてfreadで読み込んでプログラム内の変数に格納します。FORMから来る 文字列にはnull terminater '\0'が付いていません。付けましょう。 これらの処理をするプログラムは以下のようになるでしょう。

int length = atoi( getenv("CONTENT_LENGTH") );
char* buf = new char [ length+1 ];

int n = fread( buf, 1, length, stdin );
buf[n] = '\0';

● FORMデータの分割

上記の部分で文字列配列bufにFORMからのデータが格納されました。 bufの中身は、例えば次の様です。

name1=value1&name2=value2&name3=value3&name4=value4\0

この文字列を'='と'&'で区切って、FORMの変数の名前と値にばらばらに します。ばらばらにして、各文字列の先頭アドレスを何らかのポインタ配列 に格納します。FORM変数の個数を関数 Terms() で 数えることにしましょう。関数の引数 ptr に buf のアドレスを渡します。 変数の数は '='のマークの個数で判断できます。

//---- 変数の個数を数える関数
int Terms( char* src )
{
  int terms = 0;
  do{
    if( *src == '=' ) terms++;
  }while( *src++ );

  return terms;
}

FORMの各項目の変数の名前と値を格納する構造体 FormTerm を定義すると便利です。

struct FormTerm
{
  char* name;
  char* value;
};
必要な分のFormTermの変数を配列として確保します。

FormTerm* term = new FormTerm [terms];

次に分割です。文字列の実体はもとの buf に置いておきましょう。 先に確保した構造体配列 term の各要素に各FORM変数の 名前と値を表す文字列の先頭アドレスを設定します。 そして名前と値の区切り記号である '=' と '&' を '\0' に 取り換えます。これでもとの buf は複数の分割された文字列となるのです。 その作業をする関数を Assign() としましょう。第1引数に 確保したばかりの term配列の先頭アドレスを渡し、 第2引数に bufのアドレスを渡すことにしましょう。

//---- 変数の名前と値のアドレスを設定する関数
void Assign( FormTerm* term, char* src )
{
  int i=0;
  for( term[i].name = src; *src; src++ ){
    if( *src == '=' ){
      *src = '\0';
      term[i++].value = src+1;
    }else if( *src == '&' ){
      *src = '\0';
      term[i].name = src+1;
    }
  }
}

これで、FORMデータは扱い易いかたちになりました。


● FORMデータの復号

変換された特殊記号を復号します。FORMが自動的に行った変換は 文字のASCII codeを2桁の16進数の文字にして '%' を冠した変換と、 white spaceを '+' に置換した変換の2種類です。 なので復号の作業は、'%' の文字があったら次の2文字も含めて一つの1byte整数に 変換し、'+' の文字をwhite spaceに変換することです。 復号すると文字列は短くなるので復号した結果は元のところに格納できます。 関数 Escape() に先に分割したデータの1つのアドレスを渡します。

//---- 大文字表記の16進数を数字に変換する補助関数
inline char AtoH( char c )
{
  if( '0' <= c && c <= '9' ) return c - '0';
  if( 'A' <= c && c <= 'F' ) return c - 'A' + 10;
  return 0; // 楽観的なエラー対処
}

//---- %で始まる特殊記号を変換する関数
void Escape( char* src )
{
  char* dst;
  for( dst=src; *src; src++ ){
    if( *src == '%' ){
      *dst++ = (AtoH(*++src)<<4) + AtoH(*++src);
    }else if( *src == '+' ){
      *dst++ = ' ';
    }else{
      *dst++ = *src;
    }
  }
  *dst++ = '\0';
}

● 漢字コードをHTMLで使う漢字コードに統一する

日本語対応CGIとして大事な作業が漢字コードを適切に変換して あなたがHTML文章で使っている漢字コードに統一することです。 コード変換はブラウザ側で半自動的にページに表示されている コードと同じコードに変換されるはずなのですが、変換がうまく 行われないことがよくあります。なのでCGIで完全にコードを 統一する必要があります。その方法については次の章で 解説することにします。


● 完全な処理sourceの公開

いままでに紹介したプログラム部分は、FORMの処理を理解してもらうために 書いたものなので、実際に使うには少々難があります。
実際の使用のために設計したFORM処理CGIのC++によるソースをここに 置いておきますのでご利用下さい。

form.cc

このプログラムにはmain関数が付いています。それを見れば使い方は わかるでしょう。 これから先のプログラムは、みなさんの創意工夫の花開くところです。 いろいろ楽しいホームページを作ってください。


目次

Copyright(C) by Naoki Watanabe. Oct 21st, 1995.
渡辺尚貴 naoki@cms.phys.s.u-tokyo.ac.jp