CGIのワンポイント知識

CGIのワンポイント知識

ファイルロック

『投稿内容をログファイルに記録する』という処理は

  1. ログファイルを読み込みモードで開く。
  2. ファイルの内容を読み込む。
  3. ファイルを閉じる。
  4. 読み込んだ内容に新たに登録したい内容を追記する。
  5. 1で開いたログファイルを書き込みモードで開く。
  6. 4で作成したデータをファイルに書き込む。
  7. ファイルを閉じる。

という過程を経ます。

投稿された内容をファイルに書き込むにはそのファイルを書き込みモードで開く必要がありますが、書き込みモードでファイルを開くと一度ファイルの内容が消去されます。つまり、5と6のわずかな時間の間、ログファイルには何も記録されていない状態になります。この瞬間に別の人が投稿し、上の1~7の過程を実行する際、1で何も記録されていない状態のファイルを開く可能性があります。

そのまま処理が進むと何も記録されていないデータに自分の投稿内容を記録し、ファイルに書き込むことになるので、結果的に今までのデータがすべて消去され、自分の投稿内容のみログファイルに記録することになります。いわゆる『ログファイルが破損した』状態になります。

こういったログの破損を防ぐため、通常はファイルロックと呼ばれる処理を行います。ファイルロックとは、1の処理に入る前に『これからファイルの読み書きをするから他の人はちょっと待ってね。』とし、7の処理が終わると『次の人がファイルの読み書きをしてもいいですよ。』とする処理です。こういった処理を行えばファイルに同時にアクセスされることがなくなり、ログファイルの破損が起こらなくなります。

ファイルのロックと解除は、具体的には

### ロック作成
sub file_lock {
    my $self = shift;

    my $flag;

    foreach (1 .. 5) {
        if (-e $self->{file}) {
            if (time > (stat($self->{file}))[9] + 10) {
                $self->file_unlock($self->{file});
                next;
            }
            sleep(1);
        } else {
            if (open(FH, ">$self->{file}")) {
                close(FH);
                $flag = 1;
            }
            last;
        }
    }

    return $flag;
}

### ロック解除
sub file_unlock {
    my $self = shift;

    unlink($self->{file});

    return;
}

このような処理になっています。

これらの処理は、file_lockfile_unlock を呼び出すことで機能します。ファイルロック処理の内容は

  1. ファイルがロックされているかチェックする。
  2. 10秒以上前のロックならば『何らかの理由でロックが解除されなかった』とみなし、強制的にロックを解除する。
  3. 正常なロックならば1秒待って再度ロックに挑戦する(1に戻る)。
  4. ファイルがロックされていなければロックファイルを作成する。
  5. 結果的にロックファイルが作成できなければ、エラーを表示してログファイルへの記録を中止する。

となっています。

Perlではロックを行うための flock という専用の命令が用意されています。ですが flock は環境によっては動作しないので、Web Liberty で配布しているCGIは open という命令を使用してファイルロックを行います。

open はファイルを新規に作成したり、既存のファイルを開いたりする命令です。この命令を利用し、記事の登録処理を実行する直前に diary.lock などという専用のファイルを作成し、登録処理が終了すると diary.lock を削除します。(ファイルの削除には unlink という命令を使用します。)

ただし、ファイルロック処理を行ったからといってログの破損がまったく起こらなくなるわけではありません。不注意でログファイルを消去してしまうこともあるので、ログファイルの定期的なバックアップをお勧めします。

環境変数

環境変数とは、スクリプトのどこからでも参照できる特殊な連想配列変数です。$ENV{'環境変数名'} と記述することで参照できます。例えば、スクリプト中に

print "アクセス元は $ENV{'HTTP_REFERER'} です。";

と記述するだけで

アクセス元は http://www.web-liberty.net/info/index.html です。

と表示することができます。以下はCGIでよく使用される環境変数です。

環境変数名 意味
REQUEST_METHOD CGIの実行方法(フォームからの入力ならPOST、それ以外ならGETを返す)
CONTENT_LENGTH 標準入力からCGIスクリプトに渡される引数の合計バイト数。POSTメソッドで使用する。
QUERY_STRING URLに付加されたCGIスクリプトに渡される引数。GETメソッドで使用する。
HTTP_REFERER リンク元のページのURLが格納される。
HTTP_USER_AGENT 訪問者のブラウザソフトの情報が格納される。
HTTP_COOKIE 訪問者のクッキー情報が格納される。
REMOTE_ADDR 訪問者のIPアドレスが格納される。
REMOTE_HOST 訪問者のホスト名が格納される。

POSTとGETの違い

CGIに引数を渡す方法には『POSTメソッド』と『GETメソッド』があります。POSTメソッドは標準入力を使って引数を渡す方法で、GETメソッドはURLの後に引数を付加して渡す方法です。

Webページを巡回しているとよく

http://www.web-liberty.net/cgi-bin/diary/diary.cgi?year=2005&month=4

のように拡張子の後に文字が付加されている事があります。これがGETメソッドによる引数のやり取りです。? の後に続く year=2002month=12 が引数です。引数が複数ある場合、各引数は & で結合されます。

GETメソッドでは上の例のように、引数をURLに付加して渡すため、長い引数を指定することができません。掲示板などでは本文が長くなることが多いのでGETメソッドの利用はお勧めできません。

また、GETメソッドで送られた引数は見てのとおり誰の目にも触れられる状態です。ブラウザやサーバーのアクセスの履歴を調べられると、やり取りしたデータが丸見えになってしまいます。大量の情報や重要な情報をGETメソッドでやり取りするのは不向きなため、掲示板などのフォームからの送信にはPOSTメソッドを用いるのが通例となっています。

ちなみにメソッドの指定はformタグで行います。配布しているスクリプトに

<form action="${INFO_FILE}" method="post">

という記述がありますが、method="post" の部分がメソッドの指定です。method="get" とすればGETメソッドで送信が行われますが、通常は特に変更する必要はありません。

URLエンコード

URLエンコードとは、フォームから送信されるデータをURLで使用可能な文字のみに変換する作業です。Webページを巡回しているとよく

http://www.search.or.jp/search.cgi?mode=search&word=%83v%83%8d%83O%83%89%83

%83v%83%8d のような意味不明な文字列が表示されることがありますが、これがURLエンコードです。具体的には

といった処理を行います。この処理を行うことにより、漢字コード、改行文字などの送信が可能になります。URLエンコードされた文字列は各CGIのデコード処理により、元の形式に戻されてから利用されます。

ちなみに、POSTメソッドで送信すると ? 以下の部分は非表示になります。

投稿時のHTMLの使用

Web Liberty で配布しているほとんどの掲示板系のCGIは、投稿の際にHTMLを使用することができません。掲示板のように、不特定多数の人が投稿できる形式のCGIでは、HTMLの使用をすべて有効にしてしまうと問題が発生します。

例えば文字色や文字サイズのタグを

<font color="#FF0000">赤い文字

と、fontタグを閉じずに記述されると、これ以降の文字がすべて赤い文字になってしまいます。タグを閉じ忘れただけならばまだましですが

<font color="

<table><tr><td>

と入力されると、それ以降のメッセージがすべて表示されなくなってしまいます。いたずらでなくても入力ミスなどで表示が崩れる可能性もあります。

しかし、これらに対処するためだからといって単にHTMLタグの使用を全面禁止するのはつまらないです。そこで、Web Board Professional では、特定のHTMLタグのみの使用を許可し、タグを閉じ忘れるとHTMとして認識しないように設定できます。

また、ダブルクォートを閉じ忘れると強制的にダブルクォートを閉じるようにもつくられています。ですが、これだけではまだ不十分です。これではイタズラで

<b style="font-size:9999px">巨大な文字!</b>

と入力されると表示が著しく崩れてしまいます。そこで、スタイルシートを使用すると『スタイルシートは使用できません』とエラーメッセージを表示するようになっています。ですが、これでもまだ不十分です。例えばイタズラで

<b onmouseover="while(1){alert('ループ')}">無限ループ</b>

と入力されると、文字にマウスカーソルが触れるだけで警告ダイアログが開き、閉じることができなくなります。(実験したければ自己責任でどうぞ。(^^;)これくらいなら大した影響はありませんが、もっと悪質な仕掛けを仕込むことも可能です。

そこで、Web Board Professional では、簡易装飾機能を使用できるようになっています。これは

/+大きな文字+/

と入力されると文字を拡大し、

/!強調文字!/

と入力すると文字を太字にしたり、と独自の装飾機能を搭載したものです。これならばイタズラによって掲示板の表示が崩れるようなことは起こらないと思います。趣味サイトならば、ここまで神経質にならなくてもいいかもしれませんが。(^^;