wassy's workshop

標準C++でCGIを

最終更新日 :

クエリーを扱う

map による連想配列

Perl では、よく下のようなコードを書きます。

if ($FORM{'name'} eq "") {
	die "Name in form is empty!!";
}

つまり、"name" というキーに対応した文字列を取り出しているのですが、これと同様のことが map でできます。

#include <iostream>           // cout, endl
#include <string>             // string
#include <map>                // map

using namespace std;

void main() {
    map<string, string> form;
    form["name"] = "wassy";
    form["email"] = "nori@washitake.com";

    cout << "Your name is: " << form["name"] << endl;
    cout << "Your site is: " << form["url"] << endl;
}

-----
実行結果
Your name is: wassy
Your site is:

map は、二つの値のペアーをデータとして持ち、一方の値から対応する値を取り出すためのコンテナです。従って、キーは文字列である必要はありません。整数型でもポインターでも、独自のクラスでも何でも OK です。ペアーの型は map<typename1, typename2> という風に宣言します。

また、存在しないキーを参照しようとすると、新しいペアーを作って返してくれるので、不正なメモリアクセスをしたりせず、安全です。

この map を利用すれば、Perl と同様のことができます。

まず、プログラムの先頭で

#include <iostream>           // cout, endl
#include <string>             // string
#include <map>                // map
#include <sstream>            // istringstream

using namespace std;

と、宣言しておいて、必要なところで

map<string, string> form;
istringstream iss(getenv("QUERY_STRING"));

while (getline(iss, line, '&').good()) {
    istringstream iss2(line.c_str());
    getline(iss2, key, '=');
    getline(iss2, value);
    form[key] = value;
}

とします。そして、たとえば入力されたフォームに "name" という項目があって、その値を知りたいときは

if (form["name"] == "wassy") {
    cout << "Welcome back!!" << endl;
    exit(0);
}

だとか、文字の長さを調べたいときは

if (form["name"].length() == 0) {
    cout << "warning: the "name" is empty!!" << endl;
    exit(0);
}

などとします。

URI エスケープ

URI として使用できる文字は RFC 2396 (j) によって定められていて(RFC の意義から言えば「提案されています」が正しいですけど)、その Perl 式の正規表現は [;\/?:@&=+\$,A-Za-z0-9\-_.!~*'()] となります[大崎さんのPerlメモによる]

ここで、文字コード 0x20〜0x7e の外は原則としてすべてエスケープしなければなりませんが、0x20〜0x7e の範囲は条件分岐できるほど単純ではないので、テーブルを作ってしまいます。正規表現でも、結局はテーブルを作るようなものなので、処理としては同じようなものです。以下の is_uric_table が、その "URIとして使っても良い文字の集合" を表すテーブルです。直後の isuric() 関数のように使用します。

bool is_uric_table[] = {
  false, true,  false, false, true,  false, true,  true, 
  true,  true,  true,  true,  true,  true,  true,  true, 
  true,  true,  true,  true,  true,  true,  true,  true, 
  true,  true,  true,  true,  false, true,  false, true, 
  true,  true,  true,  true,  true,  true,  true,  true, 
  true,  true,  true,  true,  true,  true,  true,  true, 
  true,  true,  true,  true,  true,  true,  true,  true, 
  true,  true,  true,  false, false, false, false, true, 
  false, true,  true,  true,  true,  true,  true,  true, 
  true,  true,  true,  true,  true,  true,  true,  true, 
  true,  true,  true,  true,  true,  true,  true,  true, 
  true,  true,  true,  false, false, false, true,  false,
};

bool isuric(char& ch) {
    if (ch < 0x20 || ch > 0x7e)
        return false;
    return is_uric_table[ch - 0x20];
}

あとは、string を順番に前から読み、 isuric() が true であればそのまま、false なら '%xx' (xx はその文字コードの16進表現) という形式に変換すればよいのです。

ちなみに、個人的には isuric が <ctype.h> に導入されるべきだと思う。

char char2hex(char& ch)
{
    if (ch < 0)
        return ch;
    if (ch < 10)
        return ch + '0';
    if (ch < 16)
        return ch + 'a';
    return ch;
}

string escape(string& source)
{
    string result;
    char buffer[3] = "%";
    for (unsigned int i=0; i<source.length(); i++) {
        if (isuric(source[i]))
            result.append(1, source[i]);
        else {
            buffer[1] = char2hex((source[i] & 0xf0) / 0x10);
            buffer[2] = char2hex(source[i] & 0xf);
            result.append(3, buffer);
        }
    }
	return result;
}

URI アンエスケープ

アンエスケープはエスケープの逆です。こちらはなにも考えずに、ひたすら %xx という3文字を案エスケープするだけです。具体的には、下のようなコードになります、たぶん。

char hex2char(char& ch)
{
    if (isdigit(ch))
        return (ch - '0');
    if (ch <= 'F')
        return (ch - 'A' + 10);
    return (ch - 'a' + 10);
}

string unescape(string& source)
{
    string result = source;
    unsigned int xpos = 0;
    while ((xpos = result.find('%', xpos)) != string::npos) {
        if (isxdigit(result[xpos+1]) && isxdigit(result[xpos+2])) {
            result[xpos] = hex2char(result[xpos+1]) * 16 + hex2char(result[xpos+2]);
            result.erase(xpos+1, 2);
            xpos++;
        }
    }
    return result;
}

実際にコンパイルして実行したわけではないので保証できませんが。