Chapter09 メールフォームの作成

概要と目標 メールフォームを、
完成させよう。

エラーの表示や入力確認画面、メール送信処理を行い
メールフォームを完成させましょう。

今回のゴール

RERUN

エラーメッセージの表示 エラーメッセージは、
セッションに格納した。

「set.php」で$_SESSION['error']の中に、各項目ごとのエラーメッセージを格納した。
後は、このエラーメッセージを、適切な箇所に表示する。

エラーメッセージを表示してみよう。

  1. 「mail-form」フォルダ内ある「index.php」をテキストエディタで開く
  2. エラーメッセージはセッションに格納されているので、
    ファイルの冒頭に、セッションを開始するPHPを記述
mail-form/index.php
<?php
  // セッションの開始
  session_start();
?>
  1. htmlspecialchars()関数は、画面に出力する度に使うが、毎回記述量が多くてめんどくさい。
    楽に記述できるように独自関数をセッションの開始処理の後に作成する
mail-form/index.php
<?php
  // セッションの開始
  session_start();

  // XSS対策
  function h($s) {
    return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
  }
?>
  1. 各入力項目の input要素の下にエラーメッセージを表示するPHPを記述
mail-form/index.php
<form action="set.php" method="post">
  <dl>
    <dt><label for="name">お名前</label></dt>
    <dd>
      <input type="text" name="name" id="name">
      <?php if ( isset($_SESSION['error']['name']) ) : ?>
      <p><?php echo h($_SESSION['error']['name']); ?></p>
      <?php endif; ?>
    </dd>
    <dt><label for="email">メールアドレス</label></dt>
    <dd>
      <input type="text" name="email" id="email">
      <?php if ( isset($_SESSION['error']['email']) ) : ?>
      <p><?php echo h($_SESSION['error']['email']); ?></p>
      <?php endif; ?>
    </dd>
    <dt><label for="message">お問い合わせ内容</label></dt>
    <dd>
      <textarea name="message" id="message" rows="8" cols="50"></textarea>
      <?php if ( isset($_SESSION['error']['message']) ) : ?>
      <p><?php echo h($_SESSION['error']['message']); ?></p>
      <?php endif; ?>
    </dd>
  </dl>
  <p><input type="submit" value="入力確認へ"></p>
</form>
  1. 上書き保存
  2. ブラウザで「mail-form」フォルダ内の「index.php」にアクセス
    http://localhost/php-lessons/mail-form/
  3. エラーメッセージが表示されるかを確認
ブラウザでの表示例

RERUN

入力内容の復帰 1度入力した内容が、
消えたら使いにくい。

ユーザーが入力した内容も、「set.php」で $_SESSION['post']に格納している。
セッションにデータがある場合は、入力フォームの初期値として値を表示する。

入力内容を復帰してみよう。

  1. mail-from」フォルダ内の「index.php」をテキストエディタで開く
  2. input要素のvalue属性や、textarea要素の要素内容に
    $_SESSION['post']のデータを初期値として出力
mail-form/index.php
<form action="set.php" method="post">
  <dl>
    <dt><label for="name">お名前</label></dt>
    <dd>
      <input type="text" name="name" id="name" value="<?php if (isset($_SESSION['post']['name'])) { echo h($_SESSION['post']['name']); } ?>">
      <?php if ( isset($_SESSION['error']['name']) ) : ?>
      <p><?php echo h($_SESSION['error']['name']); ?></p>
      <?php endif; ?>
    </dd>
    <dt><label for="email">メールアドレス</label></dt>
    <dd>
      <input type="text" name="email" id="email" value="<?php if (isset($_SESSION['post']['email'])) { echo h($_SESSION['post']['email']); } ?>">
      <?php if ( isset($_SESSION['error']['email']) ) : ?>
      <p><?php echo h($_SESSION['error']['email']); ?></p>
      <?php endif; ?>
    </dd>
    <dt><label for="message">お問い合わせ内容</label></dt>
    <dd>
      <textarea name="message" id="message" rows="8" cols="50"><?php if (isset($_SESSION['post']['message'])) { echo h($_SESSION['post']['message']); } ?></textarea>
      <?php if ( isset($_SESSION['error']['message']) ) : ?>
      <p><?php echo h($_SESSION['error']['message']); ?></p>
      <?php endif; ?>
    </dd>
  </dl>
  <p><input type="submit" value="入力確認へ"></p>
</form>
  1. 上書き保存
  2. ブラウザで「mail-form」フォルダ内の「index.php」にアクセス
    http://localhost/php-lessons/mail-form/
  3. 入力内容が残るかを確認
ブラウザでの表示例

RERUN

入力確認画面の作成 セッションに保存した値を表示。

入力確認画面は、「set.php」で$_SESSIONに格納した内容を表示すればよい。
ただし、textarea要素で入力された内容は改行を含んでいる場合がある為、
改行コードを br要素に変換するnl2br関数が必要となる。

nl2br関数 ・・・ 改行文字の前に改行タグを挿入する関数
nl2br(改行文字を含む文字列, XHTMLかどうか)
                           省略可

*.第2引数を false にすると、HTMLの出力になる

詳細はPHPマニュアルを参照

入力確認画面を作成しよう。

  1. 「mail-form」フォルダ内の「conf.php」をテキストエディタで開く
  2. ファイルの冒頭に、セッションの開始と、
    何度も使うhtmlspecialchars()関数を楽に記述するための独自関数を記述
mail-form/conf.php
<?php
  // セッションの開始
  session_start();

  // XSS対策
  function h($s) {
    return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
  }
?>
  1. $_SESSION['post]が空の時は、入力フォームを経由していないので、
    $_SESSION['post]が空の時は入力フォームにリダイレクトさせる処理を
    XSSの独自関数の後に記述
mail-form/conf.php
<?php
  // セッションの開始
  session_start();

  // XSS対策
  function h($s) {
    return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
  }

  // $_SESSION['post']が空ならリダイレクト
  if ( empty( $_SESSION['post']) ) {
    header('Location: ./');
    exit();
  }
?>
  1. <dd></dd>の間に、入力内容を表示するPHPを追記
    ただし、textarea要素から送信された内容は、改行を含む場合があるので、
    改行をbr要素に変換する nl2br関数を使う
mail-form/conf.php
<dl>
  <dt>お名前</dt>
  <dd>
    <?php echo h($_SESSION['post']['name']); ?>
  </dd>
  <dt>メールアドレス</dt>
  <dd>
    <?php echo h($_SESSION['post']['email']); ?>
  </dd>
  <dt>お問い合わせ内容</dt>
  <dd>
    <?php echo nl2br( h($_SESSION['post']['message']), false); ?>
  </dd>
</dl>
  1. 上書き保存
  2. ブラウザで「mail-form」フォルダ内の「index.php」にアクセス
    http://localhost/php-lessons/mail-form/
  3. データを送信して、入力確認画面に正しく表示されるかを確認
ブラウザでの表示例

RERUN

メール送信 メール送信に成功すれば、
セッションを破棄。

サイト管理者にメールを送信する。
送信に成功したら、セッションを破棄して送信完了ページにリダイレクトする。

メールを送信してみよう。

  1. 「mail-form」フォルダ内の「send.php」をテキストエディタで開く
  2. ファイルの冒頭に、セッションの開始と、日本語の設定に関する処理を記述
mail-form/send.php
<?php
  // セッションの開始
  session_start();

  // 日本語の設定
  mb_language('ja');
  mb_internal_encoding('UTF-8');
  1. 日本語の設定の後に、メールアドレスが空の場合は、入力フォームにリダイレクトする処理を記述
mail-form/send.php
<?php
  // セッションの開始
  session_start();

  // 日本語の設定
  mb_language('ja');
  mb_internal_encoding('UTF-8');

  // メールアドレスが空の場合はリダイレクト
  if ( empty($_SESSION['post']['email']) ) {
    header('Location: ./');
    exit();
  }
  1. メールアドレスが空だった時の処理の後に、mb_send_mail()関数に必要なデータを変数に代入する。
    [メールアドレス] は 実際にメールを受信できるメールアドレスに変更しておく
mail-form/send.php
<?php
  // セッションの開始
  session_start();

  // 日本語の設定
  mb_language('ja');
  mb_internal_encoding('UTF-8');

  // メールアドレスが空の場合はリダイレクト
  if ( empty($_SESSION['post']['email']) ) {
    header('Location: ./');
    exit();
  }

  // 値を変数に格納
  $to = '[メールアドレス]'; // お問い合わせを受け取れるメールアドレスに変更
  $subject = '【ダミー】お問い合わせありがありました。';

  $message  = 'お問い合わせがありました。' . PHP_EOL;
  $message .=  PHP_EOL;
  $message .= '■お名前' .PHP_EOL;
  $message .= $_SESSION['post']['name'] .PHP_EOL;
  $message .= PHP_EOL;
  $message .= '■メールアドレス' .PHP_EOL;;
  $message .= $_SESSION['post']['email'] .PHP_EOL;
  $message .= PHP_EOL;
  $message .= '■お問い合わせ内容' .PHP_EOL;;
  $message .= $_SESSION['post']['message'];

  $from = mb_encode_mimeheader('ダミー') . '<noreply@dummy.com>';
  1. 必要なデータを変数に格納した処理の後に、 mb_send_mail()関数でメールを送信する。
    メールの送信が成功したか、失敗したかを判断する為に、戻り値を変数に受け取っておく。
mail-form/send.php
<?php
  // セッションの開始
  session_start();

  // 日本語の設定
  mb_language('ja');
  mb_internal_encoding('UTF-8');

  // メールアドレスが空の場合はリダイレクト
  if ( empty($_SESSION['post']['email']) ) {
    header('Location: ./');
    exit();
  }

  // 値を変数に格納
  $to = '[メールアドレス]'; // お問い合わせを受け取れるメールアドレスに変更
  $subject = '【ダミー】お問い合わせありがありました。';

  $message  = 'お問い合わせがありました。' . PHP_EOL;
  $message .=  PHP_EOL;
  $message .= '■お名前' .PHP_EOL;
  $message .= $_SESSION['post']['name'] .PHP_EOL;
  $message .= PHP_EOL;
  $message .= '■メールアドレス' .PHP_EOL;;
  $message .= $_SESSION['post']['email'] .PHP_EOL;
  $message .= PHP_EOL;
  $message .= '■お問い合わせ内容' .PHP_EOL;;
  $message .= $_SESSION['post']['message'];

  $from = mb_encode_mimeheader('ダミー') . '<noreply@dummy.com>';

  // メールの送信
  $resulut = mb_send_mail($to, $subject, $message, 'From: '. $from);
  1. メール送信処理の後に、 送信成功時と、失敗時で処理を分岐し、
    成功時は、セッションのデータを破棄して、"thanks.html" にリダイレクト
    失敗時は、"error.htmlにリダイレクト"する。
mail-form/send.php
<?php
  // セッションの開始
  session_start();

  // 日本語の設定
  mb_language('ja');
  mb_internal_encoding('UTF-8');

  // メールアドレスが空の場合はリダイレクト
  if ( empty($_SESSION['post']['email']) ) {
    header('Location: ./');
    exit();
  }

  // 値を変数に格納
  $to = '[メールアドレス]'; // お問い合わせを受け取れるメールアドレスに変更
  $subject = '【ダミー】お問い合わせありがありました。';

  $message  = 'お問い合わせがありました。' . PHP_EOL;
  $message .=  PHP_EOL;
  $message .= '■お名前' .PHP_EOL;
  $message .= $_SESSION['post']['name'] .PHP_EOL;
  $message .= PHP_EOL;
  $message .= '■メールアドレス' .PHP_EOL;;
  $message .= $_SESSION['post']['email'] .PHP_EOL;
  $message .= PHP_EOL;
  $message .= '■お問い合わせ内容' .PHP_EOL;;
  $message .= $_SESSION['post']['message'];

  $from = mb_encode_mimeheader('ダミー') . '<noreply@dummy.com>';

  // メールの送信
  $resulut = mb_send_mail($to, $subject, $message, 'From: '. $from);

  // 送信に成功したかをチェック
  if ($resulut) {
    // 送信成功時

    // セッションの初期化で、値を削除
    $_SESSION = array();

    // クッキーのセッションIDを削除
    setcookie(session_name(), '', time() - 3600);

    // セッションの破壊
    session_destroy();

    // 送信成功ページにリダイレクト
    header('Location: thanks.html');
  } else {
    // 送信失敗時

    // 送信失敗ページにリダイレクト
    header('Location: error.html');
  }
  exit();
  1. 上書き保存
  2. ブラウザで「mail-form」フォルダ内の「index.php」にアクセス
    http://localhost/php-lessons/mail-form/
ブラウザでの表示例

RERUN

CSRF対策 トークンを埋め込んで、チェックする。

すでにメールフォームとしての機能は実装できているが、
時間があればCSRFという脆弱性の対策を行う。

CSRF Cross クロス Site サイト Request リクエスト forgeries フォージェリ
攻撃者が用意したサイトからのリクエストを受信し処理してしまう脆弱性。
いたずら書き込み、犯罪予告などの被害がある。

CSRFの対策は、自分が用意したフォーム以外からデータを受け取らないようにする。
具体的には、トークンと呼ばれる推測不能なランダムな文字列を作成し、
<input type="hidden">を使って、フォーム内に埋め込む。
そして、ユーザーの入力内容と一緒にトークンを送信し、
受け取り側のPHPファイルでトークン正しいかをチェックすることで対策できる。
トークンは、openssl_random_pseudo_bytes()関数bin2hex()関数を組み合わせて、
推測されにくいランダムな文字列を生成するとよい。

openssl_random_pseudo_bytes関数 ・・・ 疑似乱数のバイト文字列を生成する関数
openssl_random_pseudo_bytes(長さ)

詳細はPHPマニュアルを参照

bin2hex関数 ・・・バイナリのデータを16進表現に変換する関数
bin2hex(16進表現に変換する文字列)

詳細はPHPマニュアルを参照

CSRFの対策をしてみよう。

  1. 「mail-form」フォルダ内の「index.php」をテキストエディタで開く
  2. トークンを作成して$_SESSIONに格納する処理を
    XSSの独自関数の後に記述
mail-form/index.php
<?php
   // セッションの開始
  session_start();

  // XSS対策
  function h($s) {
    return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
  }

  // CSRF対策 ・・・ トークンを生成
  if (!isset($_SESSION['token'])) {
      $_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(16));
  }
?>
  1. <input type="hidden">トークンを埋め込みform要素内に配置
    (「入力確認へ」 ボタンの上)
mail-form/index.php
<p><input type="hidden" name="token" value="<?php echo h($_SESSION['token']); ?>"></p>
<p><input type="submit" value="入力確認へ"></p>
  1. 上書き保存
  2. ブラウザで「mail-form」フォルダ内の「index.php」にアクセスし、
    「ソースの表示」からトークンが埋め込まれていることを確認
    http://localhost/php-lessons/mail-form/
  3. 「mail-form」フォルダ内の「set.php」をテキストエディタで開く
  4. $_POSTで受け取ったトークンと、予め$_SESSIONに格納しておいたトークンが一致するかを確認し、
    一致しなければ、メッセージを表示して処理を終了させるPHPを追記
mail-form/set.php
<?php
  // セッションの開始
  session_start();

  // ポストが空っぽだったらリダイレクト
  if ( empty($_POST) ) {
    header("Location: ./");
    exit();
  }

  // CSRF対策 ・・・ トークンを確認
  if (empty($_POST['token']) || $_POST['token'] != $_SESSION['token']) {
    exit('不正な投稿です。');
  }

  // エラーメッセージ格納用の配列を初期化
  $_SESSION['error'] = array();
  1. 上書き保存
  2. ブラウザで「mail-form」フォルダ内の「index.php」にアクセスし、
    データ送信時にエラーがでないかを確認
    http://localhost/php-lessons/mail-form/
  3. 「mail-form」フォルダ内の「conf.php」をテキストエディタで開く
  4. トークンを作成して$_SESSIONに格納する処理を
    セッションが空の時の処理後に記述
mail-form/conf.php
<?php
  // セッションの開始
  session_start();

  // XSS対策
  function h($s) {
    return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
  }

  // $_SESSION['post']が空ならリダイレクト
  if ( empty( $_SESSION['post']) ) {
    header('Location: ./');
    exit();
  }

  // CSRF対策 ・・・ トークンを生成
  if (!isset($_SESSION['token'])) {
      $_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(16));
  }
  ?>
  1. 入力内容を表示した後に、form要素を作成し、
    <input type="hidden">トークンを埋め込む
    「入力フォームへ」と「送信」のリンクもform要素内に移動し、「送信」のリンクは
    <input type="submit" value="送信">に変更
    <dt>お問い合わせ内容</dt>
    <dd>
      <?php echo nl2br( h($_SESSION['post']['message']), false); ?>
    </dd>
  </dl>

  <form action="send.php" method="post">
    <p><input type="hidden" name="token" value="<?php echo h($_SESSION['token']); ?>"></p>
    <ul>
      <li><a href="./">入力フォームへ</a></li>
      <li><input type="submit" value="送信"></li>
    </ul>
  </form>
</body>
  1. 上書き保存
  2. ブラウザで「mail-form」フォルダ内の「index.php」から「conf.php」にアクセスし、
    「ソースの表示」からトークンが埋め込まれていることを確認
    http://localhost/php-lessons/mail-form/
  3. 「mail-form」フォルダ内の「send.php」をテキストエディタで開く
  4. $_POSTで受け取ったトークンと、予め$_SESSIONに格納しておいたトークンが一致するかを確認し、
    一致しなければ、メッセージを表示して処理を終了させるPHPを追記
mail-form/send.php
<?php
  // セッションの開始
  session_start();

  // 日本語の設定
  mb_language('ja');
  mb_internal_encoding('UTF-8');

  // CSRF対策 ・・・ トークンの確認
  if (empty($_POST['token']) || $_POST['token'] != $_SESSION['token']) {
    exit('不正な投稿です。');
  }

  // メールアドレスが空の場合はリダイレクト
  if ( empty($_SESSION['post']['email']) ) {
    header('Location: ./');
    exit();
  }
  1. 上書き保存
  2. ブラウザで「mail-form」フォルダ内の「index.php」にアクセスし、エラーが出ないことを確認
    http://localhost/php-lessons/mail-form/
ブラウザでの表示例

RERUN

その他のセキュリティ対策
この他にも、よりセキュリティを高めるには、クリックジャッキングセッションハイジャックメールヘッダインジェクションなどの対策を別途講じるほうが好ましい。

練習問題 「メールフォーム」を仕上げよう。

作成したメールフォームに、セクショニング・コンテンツの要素や、
CSSを追加して、オリジナルのメールフォームを完成させよう。

完成イメージ

RERUN

まとめ メールフォームの処理は、
基本が詰まってる。

メールフォームはサーバーサイドプログラミングの基本が詰まってる。

  • 「入力フォーム」→「入力チェック」→「入力確認」の流れが基本
  • トークンを埋め込んでCSRF対策を行う
  • 別途セキュリティへの配慮が必要