PHP実習 (05) メール送信の仕組み

lecture

*それぞれのコードは「入力フォームのコード」「確認ページのコード」「メール送信のコード」をクリックすると見られます。

メール送信の仕組みを学習する上での参考例です。セキュリティ問題を完全に解決したものではありませんのでそのまま運用はしないでください。解説をよく読んでセキュリティ対策を理解した上で実験しましょう。完全運用するためには各自オリジナルのセキュリティ対策をしっかり行って使用してください。
サンプル フォームで入力した内容を一度確認した上でメール送信をします。その際に、入力もれや悪意のある入力対策を行います。
入力フォームのコード
<?php
session_start();
$token = md5(uniqid(mt_rand(), TRUE));
$_SESSION['token'] = $token;
?>
<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>フォームサンプル</title>
<link rel="stylesheet" href="css/reset.css">
<link rel="stylesheet" href="css/base.css">
<style type="text/css">
#formTable {
	border: 1px solid #666;
	border-collapse: collapse;
	margin: auto;
	background-color: #fff;
}
tr, td {
	border: 1px solid #666;
	border-collapse: collapse;
	padding:5px;
}
th {
	background-color: #CCC;
}
#form1 #formButton {
	text-align: center;
}
</style>
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<script type="text/javascript">
function clearFormAll() {
    for (var i=0; i<document.forms.length; ++i) {
        clearForm(document.forms[i]);
    }
}
function clearForm(form) {
    for(var i=0; i<form.elements.length; ++i) {
        clearElement(form.elements[i]);
    }
}
function clearElement(element) {
    switch(element.type) {
        case "submit":
        case "button":
            return;
        case "text":
        case "textarea":
            element.value = "";
            return;
        case "checkbox":
        case "radio":
            element.checked = false;
            return;
        case "select-one":
        case "select-multiple":
            element.selectedIndex = 0;
            return;
        default:
    }
}
</script>
</head>
<body>
<header>
  <div class="inner">
    <h1>フォーム Sample</h1>
  </div>
</header>
<article>
  <form name="form1" id="form1" method="post" action="sample4a.php">
    <table cellpadding="0" cellspacing="0" summary="お問い合わせフォーム" id="formTable">
      <caption>
      お問い合わせフォーム
      </caption>
      <tr>
        <th><label for="subject">お問い合わせ内容</label></th>
        <td><select name="subject" id="subject">
        	<?php
    $sub = array('選択してください','予約について','メニューについて');
    foreach ($sub as  $val) {
             print('<option value="'.$val.'"');
			 if ($val == $_SESSION['subject']) {
				 print(' selected="selected">'.$val.'</option>');
			 }else{
            	print('>'.$val.'</option>');
			 }
	} ?>
          </select></td>
      </tr>      
      <tr>
        <th><label for="name">お名前</label></th>
        <td><input type="text" name="name" id="name" value="<?php print($_SESSION['name']); ?>" size="20" /></td>
      </tr>
      <tr>
        <th><label for="tel"><br />電話番号 *ハイフン(-)なしで記入してください。
        </label></th>
        <td><input type="text" name="tel" id="tel" value="<?php print($_SESSION['tel']); ?>" size="20" /></td>
      </tr>
      <tr>
        <th><label for="email">メールアドレス</label></th>
        <td><input type="text" name="email" id="email" value="<?php print($_SESSION['email']); ?>" size="20" /></td>
      </tr>
      <tr>
        <th>ご連絡方法</th>
        <td>
          <?php
    $tools = array('メール', '電話');
    foreach ($tools as $tool) {
      print('<label>');
      print('<input type="radio" name="contact" value="'.$tool.'"');
      if ($contact == $_SESSION['メール']) { print(' checked'); }
      print(' />');
      print($tool.'&ensp;</label>');
    }
    ?></td>
      </tr>
      <tr>
        <th>当店を何でお知りになりましたか</th>
        <td id="checkBoxData">
        <?php
    $whats = array('magazine'=>'雑誌','web'=>'ホームページ','friend'=>'友人・知人','signboard'=>'看板','etc'=>'その他');
   
    foreach ($whats as $k_what => $v_what) {
      print('<label>');
      print('<input type="checkbox" name="what[]" value="'.$k_what.'"');
      if (isset($_SESSION['what']) === TRUE) {
        foreach ($_SESSION['what'] as $what) {
          if ($k_what === $what) { print(' checked'); }
        }
      }
      print(' />');
      print($v_what.'</label></br>');
    }
    ?>
      </td>
      </tr>
      <tr>
        <th><label for="free">自由記入欄</label></th>
        <td><textarea name="free" id="free" cols="25" rows="5"><?php print($_SESSION['free']); ?></textarea>
          <p class="notes">※ご予約の場合は、希望日時をご記入ください。</p></td>
      </tr>
    </table>
    <div id="formButton">
    <input type="hidden" name="token" value="<?php print($token); ?>" />
      <input type="submit" name="submit" id="submit" value="送信" />
      <input type="button" name="reset" id="reset" value="リセット" onClick="clearFormAll();" />
    </div>
  </form>
</article>
<footer> <small>All rights reserved STUDIO.M 2014</small> 
  <!-- footer --></footer>
</body>
</html>
確認ページのコード
<?php
ini_set('session.use_trans_sid', '0');
ini_set('session.use_only_cookies','1');
session_start();
if (isset($_POST['name']) === TRUE) { $_SESSION['name'] = $_POST['name']; }
if (isset($_POST['tel']) === TRUE) { $_SESSION['tel'] = $_POST['tel']; }
if (isset($_POST['email']) === TRUE) { $_SESSION['email'] = $_POST['email']; }
if (isset($_POST['contact']) === TRUE) { $_SESSION['contact'] = $_POST['contact']; }
if (isset($_POST['subject']) === TRUE) { $_SESSION['subject'] = $_POST['subject']; }
if (isset($_POST['free']) === TRUE) { $_SESSION['free'] = $_POST['free']; }
if (isset($_POST['what']) === TRUE) { $_SESSION['what'] = $_POST['what']; }
$errors = array();
foreach($_SESSION as $key => $value) {
  if (is_array($value)) { $value = implode('', $value); }
  if (!mb_check_encoding($value)) {
    $errors[] = '文字コードに誤りがあります。';
    break;
  }
}
if (trim($_SESSION['name']) === '') {
  $errors[] = '名前はかならず入力してください。';
}
if (mb_strlen($_SESSION['name']) > 50) {
  $errors[] = '名前は50文字未満で入力してください。';
}
if (!ctype_digit($_SESSION['tel'])) {
  $errors[] = '電話番号は整数値で入力してください。';
}
if (mb_strlen($_SESSION['tel'])>13) {
  $errors[] = '電話番号は13文字以内で入力してください。';
}
$contact = array('メール', '電話');
if (isset($_SESSION['contact'])) {
  foreach ($_SESSION['contact'] as $con_value) {
    if (!in_array($con_value, $contact)) {
      $errors[] = 'contactは決められた選択肢の中から選択してください。';
      break;
    }
  }
}
$subject = array('選択してください', '予約について', 'メニューについて');
if (isset($_SESSION['subject'])) {
  foreach ($_SESSION['subject'] as $sub_value) {
    if (!in_array($sub_value, $subject)) {
      $errors[] = 'whatは決められた選択肢の中から選択してください。';
      break;
    }
  }
}
if($_SESSION['subject'] == '選択してください'){
	 $errors[] = 'お問い合わせは何についてですか?選択してください';
}
$whats = array('magazine', 'web', 'friend','signboard','etc');
if (isset($_SESSION['what'])) {
  foreach ($_SESSION['what'] as $value) {
    if (!in_array($value, $whats)) {
      $errors[] = 'whatは決められた選択肢の中から選択してください。';
      break;
    }
  }
}else{
	 $errors[] = '当店を何でお知りになりましたか?どれか選択をしてください。';
}	
if (!preg_match('/^\w+([-+.\']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/iD', $_SESSION['email'])) {
  $errors[] = '不正なメールアドレスです。';
}
?>

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>sample</title>
<link rel="stylesheet" href="css/reset.css">
<link rel="stylesheet" href="css/base.css">
</head>
<body>
<?php
if (!isset($_POST['token']) || $_POST['token'] !== $_SESSION['token']) {
  die('不正なアクセスが行われました。もう一度最初からやり直してください。');
}
if (count($errors) > 0) {
  die(implode('<br />', $errors).
    '<br />[<a href="sample4.php">戻る</a>]');
}
?>
<header>
  <div class="inner">
    <h1>フォーム Sample</h1>
  </div>
</header>
  <article>
    <h2>フォーム入力内容</h2>
    <section>
      <p>
        <?php
		$name = htmlspecialchars($_POST['name'], ENT_QUOTES);
		$tel = htmlspecialchars($_POST['tel'], ENT_QUOTES);
		$email = htmlspecialchars($_POST['email'], ENT_QUOTES);
		$contact = htmlspecialchars($_POST['contact'], ENT_QUOTES);
		$subject = htmlspecialchars($_POST['subject'], ENT_QUOTES);
		$free = htmlspecialchars($_POST['free'], ENT_QUOTES);
		echo "<br>お問い合わせ内容:".$subject.
			"<br>名前:".$name.
			"<br>電話番号:".$tel.
			"<br>メールアドレス:".$email.
			"<br>連絡方法:".$contact.
			"<br>当店を何でお知りになりましたか:";
		foreach($_POST['what'] as $what){
			print(htmlspecialchars($what, ENT_QUOTES)."、");
		}
		echo "<br>自由記入欄:".$free;
		?>
      </p>
      <p><a href="sample4.php">戻る</a> | <input type="button" value="送信" onclick="location.href='sample_end4.php'"; />
    </section>
  </article>
  <footer> <small>All rights reserved STUDIO.M 2014</small> </footer>

</body>
</html>
メール送信のコード
<?php
ini_set('session.use_trans_sid', '0');
ini_set('session.use_only_cookies','1');
session_start(); 
?>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>sample</title>
<link rel="stylesheet" href="css/reset.css">
<link rel="stylesheet" href="css/base.css">
</head>
<body>
<header>
  <div class="inner">
    <h1>フォーム Sample</h1>
  </div>
</header>
  <article>
    <h2>フォーム送信</h2>
    <section>
      <p>
        <?php
		$to = "info@xxx.xx"; //ここに自分宛のメールアドレスを記述
		mb_language("japanese");
		mb_internal_encoding("UTF-8");
 
		if (!empty($_SESSION['email'])) {
			$subject = '予約の件';
			foreach ($_SESSION as $key => $value){
				if($key == 'what'){
					$body .= "[{$key}]". implode('&', $value)."\n";
				}else{
					$body .= "[{$key}] {$value}\n";
				}
			}	
			$success = mb_send_mail($to,$subject,$body,"From:".$_SESSION['email']);
		}
		if ($success) {
			print('送信しました');
			} else {
			print('送信できませんでした。');
		}
		?>
      </p>
    </section>
  </article>
  <footer> <small>All rights reserved STUDIO.M 2014</small> </footer>
<?php session_unset(); ?>
</body>
</html>
スポンサーリンク

解説

入力フォーム

入力フォームはPHP実習 (4)のものと基本的に変わりません。
変えた部分はセキュリティを考慮してtokenをつけました。
これはクロスサイトリクエストフォージェリ攻撃の対策です。md5(uniqid(mt_rand(), TRUE))はランダムな数字を作成しています。
この値を input type=”hidden” の値に設定して確認ページへ送ります。

$token = md5(uniqid(mt_rand(), TRUE));
$_SESSION['token'] = $token;

確認ページのif文で判定して、もしフォームページから正規に送られたものでなければtokenが合わなくなりエラーとなる仕組みです。

if (!isset($_POST['token']) || $_POST['token'] !== $_SESSION['token']) {
  die('不正なアクセスが行われました。もう一度最初からやり直してください。');
}

けれども、これも完璧ではありません。
攻撃者がソケット通信などでワンタイムトークンを取得して、罠ページのhidden情報にそのトークンを注入して送信先することで回避できてしまいます。

参考サイトなど今後リンク予定

確認ページ

確認のページでは、URLのクエリに「PHPSESSID=……」という文字列をつけて攻撃してくる対策としてcookie以外のセッションIDを受け付けなくします。

ini_set('session.use_trans_sid', '0');
ini_set('session.use_only_cookies','1');

ここではif文でフォーム入力が正しくできているかチェックします。

特に重要なポイントはメールアドレスの入力データです。
正しくメールアドレスが入力されているか正規表現パターンで判定します。
さらに、ポイントは単一のメールアドレスであることです。
ここのメールアドレスはそのまま、メール送信するための関数 mb_send_mail のヘッダー部分で使用します。
もし、このメールアドレスに改行を入れてbccなどのアドレスを入れると勝手なアドレスにメールが送信されることになります。この手法をメールヘッダーインジェクション脆弱性といいます。

$errors = array();
foreach($_SESSION as $key => $value) {
  if (is_array($value)) { $value = implode('', $value); }
  if (!mb_check_encoding($value)) {
    $errors[] = '文字コードに誤りがあります。';
    break;
  }
}
if (trim($_SESSION['name']) === '') {
  $errors[] = '名前はかならず入力してください。';
}
if (mb_strlen($_SESSION['name']) > 50) {
  $errors[] = '名前は50文字未満で入力してください。';
}
if (!ctype_digit($_SESSION['tel'])) {
  $errors[] = '電話番号は整数値で入力してください。';
}
if ($_SESSION['tel'] <= 13) {
  $errors[] = '電話番号は13文字以内で入力してください。';
}
$contact = array('メール', '電話');
if (isset($_SESSION['contact'])) {
  foreach ($_SESSION['contact'] as $con_value) {
    if (!in_array($con_value, $contact)) {
      $errors[] = 'contactは決められた選択肢の中から選択してください。';
      break;
    }
  }
}
$subject = array('選択してください', '予約について', 'メニューについて');
if (isset($_SESSION['subject'])) {
  foreach ($_SESSION['subject'] as $sub_value) {
    if (!in_array($sub_value, $subject)) {
      $errors[] = 'whatは決められた選択肢の中から選択してください。';
      break;
    }
  }
}
if($_SESSION['subject'] == '選択してください'){
	 $errors[] = 'お問い合わせは何についてですか?選択してください';
}
$whats = array('magazine', 'web', 'friend','signboard','etc');
if (isset($_SESSION['what'])) {
  foreach ($_SESSION['what'] as $value) {
    if (!in_array($value, $whats)) {
      $errors[] = 'whatは決められた選択肢の中から選択してください。';
      break;
    }
  }
}else{
	 $errors[] = '当店を何でお知りになりましたか?どれか選択をしてください。';
}	
if (!preg_match('/^\w+([-+.\']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/iD', $_SESSION['email'])) {
  $errors[] = '不正なメールアドレスです。';
}

メール送信

メール送信はサーバーの環境設定が正しくできていれば簡単です。
mb_send_mail()関数を使用すれば送信することができます。

mb_send_mail(宛先,件名,本文,ヘッダー)

メールヘッダーの指定方法
ヘッダー名: 値[改行]
ヘッダー名: 値[改行]
ヘッダー名: 値[改行]
・・・

主なメールヘッダー
分類 ヘッダ名 概要
基本 Content-Type メール本文のコンテンツタイプ
  Date 送信日時
  subject 件名
アドレス To 宛先
  Cc カーボンコピー
  Bcc ブライドカーボンコピー
  From 差出人アドレス
  Reply-To 返信アドレス
その他 Recieved メール送信に際しての中継サーバー
  X-Mailer 作成したメールソフト

mb_send_mailは正しく送信されるとtrueを返しますので、if文でメールの送信とエラーを表示させます。

if ($success) {
			print('送信しました');
	} else {
			print('送信できませんでした。');
}

$bodyへのデータの代入は本来次のようにするとわかりやすいかもしれません。$whatは配列になっています。これを取り出すにはforeach を使いたくなりますが、それではうまくいきません。この場合はimpload()を使用します。

$what = implode("&", $_SESSION['what']);

$bodyへのデータの代入をもっと効率的に行ったのが次の方法です。

$to = "info@studiom-web.net";
		$name = $_SESSION['name'];
		$tel = $_SESSION['tel'];
		$email = $_SESSION['email'];
		$contact = $_SESSION['contact'];
		$subject = $_SESSION['subject'];
		$free = $_SESSION['free'];
		$what = implode("&", $_SESSION['what']);
		mb_language("japanese");
		mb_internal_encoding("UTF-8");
 
		if (!empty($email)) {
			$subject = '予約の件';
			$body = "名前:".$name."\n"."電話:".$tel."\n"."メール:".$email."\n"."連絡方法:".$contact."\n"."当店を何でお知りになりましたか:".$what."\n"."自由記入欄:".$free;
			$from = mb_encode_mimeheader(mb_convert_encoding("メールシステム","JIS","UTF-8")).$email;
	
			$success = mb_send_mail($to,$subject,$body,"From:".$from);
		}

foreach文を使うことで$_SESSIONに入っている配列を取り出します。取り出したものをそのまま$bodyに書き込んだのが次の方法です。
このようにすることで、入力項目が変更になったり増えたり減ったりしてもこの部分を変更しなくても自動で書き出してくれるようになるわけです。

$to = "info@xxx.xx"; //ここに自分宛のメールアドレスを記述
		mb_language("japanese");
		mb_internal_encoding("UTF-8");
 
		if (!empty($_SESSION['email'])) {
			$subject = '予約の件';
			foreach ($_SESSION as $key => $value){
				if($key == 'what'){
					$body .= "[{$key}]". implode('&', $value)."\n";
				}else{
					$body .= "[{$key}] {$value}\n";
				}
			}	
			$success = mb_send_mail($to,$subject,$body,"From:".$_SESSION['email']);
		}
タイトルとURLをコピーしました