連載
» 2007年03月09日 00時00分 公開

サンプルで理解! フォームデータの受け渡し今から始める MySQL入門(3)(2/3 ページ)

[鶴長鎮一,@IT]

フォーム中に値を埋め込む方法(2)

確認画面(confirm.php)

 次に、sample1の「確認画面(confirm.php)」でポイントとなる個所を解説します。

1  <?php
2  isset($_POST["order_no"]) or die("不正な呼び出しです");
3  ?>
(省略)

1〜3行目

 ここでも、1行スクリプトを用いてPHPコードを埋め込んでいます。1〜3行目は注文番号が適正にセットされているか「isset($変数)」で確認し(2行目)、不正操作を判断しています。

 例えば、メニュー画面(menu.php)からの呼び出しではなく、確認画面(confirm.php)から直接呼び出された場合には、$order_idは何もセットされておらず初期化されていません。isset()は、変数が初期化されていなければFALSEを返し、初期化されていればTRUEを返します。戻り値がFALSEであれば、die("メッセージ")で処理を中断します。

 ちなみに、isset()は変数が初期化されていれば空であってもTRUEを返すため、単純な判定に限定されます。本格的な用途では、注文番号のけた数やチェックデジットを用いるなどします。

(省略)
49  <input type="hidden" name="order_no" value="<?php print
 $_POST["order_no"]; ?>">
(省略)
59  <input type="hidden" name="apache" value="<?php print
 $_POST["apache"]; ?>">
(省略)
67  <input type="hidden" name="qmail" value="<?php print
 $_POST["qmail"]; ?>">
(省略)
75  <input type="hidden" name="mysql" value="<?php print
 $_POST["mysql"]; ?>">
(省略)
83  <input type="hidden" name="bind" value="<?php print
 $_POST["bind"]; ?>">
(省略)

49、59、67、75、83行目

 メニュー画面(menu.php)で入力された値を、完了画面(purchase.php)に引き渡すため、<input type="hidden"...>タグを利用し再びフォームに埋め込みます。

(省略)
62  <td><?php print $_POST["apache"] . "個"; ?></td>
(省略)

62行目

 文字列の連結には「.」を用います。

(省略)
63  <td align=right><?php print $_POST["apache"]*480 . "円"; ?></td>
(省略)
93  <?php
94  print $_POST["apache"]+$_POST["qmail"]+$_POST["mysql"]
+$_POST["bind"] ."個";
95  ?>
(省略)
98  <?php
99  print $_POST["apache"]*480+$_POST["qmail"]*950+$_POST["mysql"]
*850+$_POST["bind"]*780 . "円";
100 ?>
(省略)

63、93〜95、98〜100行目

 各アイテムの小計や合計金額などの四則演算には、「+」「-」「*」「/」を用います。PHPは格納される値によって型を自動に判断するため、$_POST[""]に整数値が格納されていれば、「+」「-」「*」「/」を用いることができます。またprint文では、整数型でも明示的に型変換することなく文字列として出力されます。

完了画面(purchase.php)

 続いて、sample1の「完了画面(purchase.php)」でポイントとなる個所を解説します。ちなみに、1〜41行目、56、63、66行目でPHPコードを使用しています。56、63、66行目は単に値を表示しているだけなので1〜41行目に絞って解説します。

1   <?php
2
3   isset($_POST["order_no"]) or die("不正な呼び出しです");

5   $order_no = escapeshellcmd($_POST["order_no"]);
6   $order_file = "/tmp/order" . $order_no . ".txt";
7
8   if(file_exists($order_file)){
9     die("すでに注文済みです");
10  }
11 
12  $now = date("Y/m/d H:i:s", time());
13  $name = escapeshellcmd($_POST["name"]);
14  $name = htmlspecialchars($name);
15  $address = escapeshellcmd($_POST["address"]);
16  $address = htmlspecialchars($address);
17  $apache = escapeshellcmd($_POST["apache"]);
18  $qmail = escapeshellcmd($_POST["qmail"]);
19  $mysql = escapeshellcmd($_POST["mysql"]);
20  $bind = escapeshellcmd($_POST["bind"]);
21
22  $str =<<<EOS
23  ご注文時刻:$now
24  注文番号  :$order_no
25  お名前    :$name
26  住所      :$address
27  【注文内容】
28  Apache:$apache
29  qmail :$qmail
30  MySQL :$mysql
31  BIND9 :$bind
32  EOS;
33
34  $file = fopen($order_file, "a") or die("ファイルをオープンできませんでした");
35  set_file_buffer($file, 0);
36  flock($file, LOCK_EX);
37  fputs($file, $str);
38  flock($file, LOCK_UN);
39  fclose($file);
40 
41  ?>

3行目

 注文番号の確認は、確認画面(confirm.php)と同様に「isset()」で行います(3行目)。注文番号に問題がなければ、受注内容をファイルに書き出します。sample1では受注データを「/tmp/order注文番号.txt(注文番号が1000000001なら/tmp/order1000000001.txt)」に書き出します。すでに同名ファイルが存在している場合は、注文済みと判断します。

 「purchase.php」を再読み込みさせるか、前画面に戻って再度「購入」ボタンをクリックすると、二重発注と見なされ、「すでに注文済みです」と表示されます。

5、13〜20行目

 選択された各アイテムの個数を$_POST["apache"]、$_POST["qmail"]、$_POST["mysql"]、$_POST["bind"]で取得し、氏名と住所を$_POST["name"]と$_POST["address"]で取得します。

 取得された値はそのままファイルに書き出しますが、念のため「escapeshellcmd("文字列")」用いてLinuxシェルに特別な意味を持つ文字をエスケープします(注)

注:例えば、確認画面(confirm.php)の名前や住所欄に「rm -rf *;」などと入力する、escapeshellcmd()により「rm -rf \*\;」と置き換えられます。


 また、シェルに影響する文字と同様に<HTML>タグとして意味を持つ文字列もエスケープする必要があります。<HTML>タグのエスケープには「htmlspecialchars()」を用います。

 「<form>」という記述であれば、「\<form\>」のような内容に書き換えられます。例えば確認画面(confirm.php)の住所欄で「<form action="悪意のあるスクリプト">〜<input type="submit"></form>」などと入力された場合などに確認画面でフォームがそのまま表示されるような事態を防ぎます。

画面2 悪意のあるフォームがそのまま表示されてしまう例(住所欄に意図していないフォームボタンが埋め込まれています) 画面2 悪意のあるフォームがそのまま表示されてしまう例(住所欄に意図していないフォームボタンが埋め込まれています)

12行目

 受注データには注文された日付や時刻を記録する必要があるため、現在日時を「time()」で取得して「date()」で「2007/02/10 22:15:30」のようにフォーマットをそろえます。

22〜32行目

 値の精査が完了したところで、ファイルに書き出す文面としての文字列を用意します。今回、文字列の生成にヒアドキュメントを使用しています。ヒアドキュメントは、比較的大量の文字列を扱う場合に用いられます。「<<<識別子」〜「識別子」までの文字を文字列に格納します。

34〜39行目

 ファイルへの書き出しは、メニュー画面(menu.php)のカウントデータの書き出しと同様にfopen()、set_file_buffer()、flock()/fputs()、flock()/fclose()の順で行います。

 実際にsample1を動作させて、「/tmp」に受注データが書き出されているかどうかを確認します。日本語が表示されない場合は、使用しているターミナルのキャラクターセットを「UTF-8」に変更します。

ご注文時刻:2007/02/11 00:18:31
注文番号  :1000000001
お名前    :名前
住所      :住所
【注文内容】
Apache:0
qmail :2
MySQL :2
BIND9 :2
      受注データの例 「order1000000001.txt」

問題点

 sample1では、<input type="hidden"...>タグでフォームに値を埋め込み、前々の画面の入力値などを最後の完了画面(purchase.php)に引き渡すようにしました。

 このようなケースでは、クライアント側でデータが改変される可能性を考慮する必要があります。ここで紹介したサンプルは、同一サーバ上ですべてのPHPスクリプトが実行されることを想定していますが、何も対策していない場合には次のような処理が可能となります。

http://他者のサイト/confirm.phpを模したサンプル
       ↓
http://オンラインストア/purchase.php

 sample1では、単に$order_idが埋め込まれているかを確認しているのみです。変数名はフォーム中<input>タグのname属性から簡単に類推できます。そのため、環境変数からRefererを拾い(コラム1参照)、参照元が自サイトかどうかを確認するなどの煩わしい処理が本来は必要です。

 また、ブラウザで再読み込みをされた場合や[戻る]ボタンで前画面に戻られた場合には、表示されるキャッシュデータも考慮する必要があります。こうしたクライアント側でデータを保持する方法には、悪意あるデータ改ざんの危険性があります。

コラム1 PHPでのRefererの利用

Refererを調べることで、どこからページにアクセスしたのかを調査できます。PHPではスーパーグローバル変数「$_SERVER」から取り出すことができます。

<?php
print $_SERVER["HTTP_REFERER"];
?>

また、PHPを使わずにApacheの「.htaccess」を利用して、参照元を制限できます。Apacheの設定や.htaccessの記述方法については、「実用 Apache 2.0運用・管理術 最終回 『接続数/帯域制限で無法なダウンローダを撃退』」を参照してください。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。