私のパソコン雑記帖

ファイル添付フォームメール・テクニカルノート(2)

mail 関数の実装

カテゴリー: PHP
10May2005⇒2Aug2014更新

ファイル添付メールは、メールに画像を添付したり、文書ファイルを添付する応募フォームのようなニーズがあります。また、ファイル名に日本語が使える、件名に日本語が使える(コンテンツの日本語は当然)というような日本語処理も求められます。ファイル添付メールフォーム汎用スクリプトを制作する際に学んだ技術要素を整理して残します。その2:mail 関数の実装。


データの流れ

  1. フォームから受信、デコード・サニタイズ。
    受信したデータ(変数値)を、$name(送信者名)、$email(送信者のメールアドレス)、$comment(通信文)、$file1(添付ファイル1)、$file2(添付ファイル2)、とします。

  2. 添付ファイル数は、2で理解しておけば3以上に拡張することは容易です。

  3. メール関数は mail($mailto, $subject, $message, $add_header) のように4つの引数を必要とします。順に第1引数~第4引数と呼ぶことにします。第1引数、第2引数はメールフォーム提供側が用意する情報です。$to(送信先メールアドレス)、$toName(送信先メールアドレス名称、”ウェブマスター”等)を基に第1引数を、$title(フォームの名称、”ファイル添付送信"等)を基に第2引数を作ります。第3、第4引数は受信データを基に作ることになります。

  4. メールメッセージに実装。これはメールソフトが行うことですが、メール関数の第1~第4引数がどのように実装されるか、またその時に適用されるルールを理解しないと、上記3の処理が正しく行えません。

メールメッセージ実装の基本ルール

  1. メールメッセージの冒頭部(ヘッダー部)に含まれる日本語は、MIMEエンコード "iso-2022-jp Bエンコード" が必要。B もしくは Qエンコードとされていますが、Windows Live Mail や Thunderbird はBエンコードで処理しているようです。
    一方 mail 関数の第1、第2、第4引数は、ヘッダー部に実装される項目。従ってここに含まれる日本語はすべてMIMEエンコードが必要です。

  2. 行の先頭が空白か水平タブの場合は前行からの続きであるとみなされます。Windows Live Mail や Thunderbird のメッセージソースのヘッダー部を見ると何箇所かそのような行がみられます。

  3. Windows Live Mail や Thunderbird のメッセージ・ソースをみると、ヘッダー部に「X-」で始まるフィールドがいくつか追加されています。これらのフィールドは、アプリケーション独自の採用ということ。mail 関数側は関与しないフィールドです。ただし mail 関数側で独自のフィールドを追加することも出来ます。

  4. ヘッダー部の次はボディー部、空行を置いて区分されます。mail 関数の第3引数はボディー部に実装される項目です。
    ボディー部は、iso-2022-jp にコード変換する必要があります。ただし、ボディー部に含まれるファイル名に日本語がある場合は、この部分のみMIMEエンコード "iso-2022-jp Bエンコード" が必要です。Bエンコードは厳密にはルール違反らしいのですが、Windows Live Mail も Thunderbird もBエンコード処理しています。現実的にはこれで問題なく日本語ファイル名を表示できます。

  5. 添付ファイルがある場合、本文とファイルデータを boundary で区分します。boundary は任意の文字列ですが、出来るだけ推測困難なものがすすめられています。boundary の前には "--" を置くこと。スクリプトの最後の boundary は後ろにも "--" を置く。なお boundary 文字列を推測困難にするのはセキュリティー上の理由からでしょうか、その因果はよく理解できていません。

  6. 文字列化したファイルデータを分割する常套手段として chunk-split が使われます。

  7. その他にも決まった書式があります。

参照:メールにかけられた呪文「MIME~前編/後編」

メッセージソース:
(Windows Live Mail) ファイル→プロパティ→詳細→メッセージソース
(Thunderbird) 表示→メッセージのソース


引数を仕立てる

変数値

フォームから受信してデコード・サニタイズされたデータ(変数値):
$name(送信者名)、$email(送信者のメールアドレス)、$comment(通信文)、$file1(添付ファイル1)、$file2(添付ファイル2)、とします。

メールフォーム提供側が指定する変数値:
$to(送信先メールアドレス)、$toName(送信先メールアドレス名称、”ウェブマスター”等)、$title(フォームの名称、”ファイル添付送信"等)、とします。


スクリプト パート1

<?php
$subject = $title;
$message = "";
$boundary = md5(uniqid(rand())); //$boundary の変数値 基本ルール第5項
$mailto=mb_encode_mimeheader($toName, "iso-2022-jp", "B")."<$to>"; //基本ルール第1項
$subject=mb_encode_mimeheader($subject, "iso-2022-jp", "B"); //基本ルール第1項
$name1=mb_encode_mimeheader($name, "iso-2022-jp", "B"); //基本ルール第1項
$from =$name1."<$email>";
$add_header = "From: $from ";
$add_header .= "Reply-To: $from ";
$add_header .= "MIME-version: 1.0 "; //基本ルール第7項
?>

スクリプト パート2

<?php
if(file_exists($file1) || file_exists($file2)) {
 $add_header .= "Content-Type: multipart/mixed; "; //基本ルール第7項
 $add_header .= " boundary="$boundary" ";
   //水平タブ ルール2、boundary定義 基本ルール第5項
 $message .= "This is a multi-part message in MIME format. "; //ルール7
 $message .= "--$boundary ";  //基本ルール第5項 添付ファイルがある場合に特有
 $message .= "Content-Type: text/plain; charset=ISO-2022-JP "; //基本ルール第7項
 $message .= "Content-Transfer-Encoding: 7bit "; //基本ルール第7項
} else {
 $add_header .= "Content-Type: text/plain; charset=ISO-2022-JP "; //基本ルール第7項
 $add_header .= "Content-Transfer-Encoding: 7bit "; //基本ルール第7項
}
 $message .= "お名前 = $name ";
 $message .= "E-mail = $email ";
 $comment = str_replace("\r", "", $comment); //注1
 $message .= "コメント = $comment ";
?>

注1:$comment として受けられる通信文は通常 "textarea" に記述されます。この textarea 内の記述で改行したところは、Windows Live Mail では特別な処理をしなくても、その通りに表示されますが、Thunderbird では一行空行が入ります。CR(\r)を除くとこの空行が除かれます。

添付ファイルが無い場合はここ迄で第1引数~第4引数は全て完成。


スクリプト パート3

<?php
if(file_exists($file1)) {
 $fp = fopen($file1, "r") or die("error");
 $contents = fread($fp, filesize($file1));
 fclose($fp);
 $f_encoded = chunk_split(base64_encode($contents)); //基本ルール第6項
 $message .= " --$boundary "; //基本ルール第5項
 $message .= "Content-Type: " . $file1_type . "; ";
 $file1_name=mb_encode_mimeheader($file1_name,"iso-2022-jp","B"); //基本ルール第3項
 $message .= " name="$file1_name" "; //水平タブ 基本ルール第2項
 $message .= "Content-Transfer-Encoding: base64 "; //基本ルール第7項
 $message .= "Content-Disposition: attachment; "; //基本ルール第7項
 $message .= " filename="$file1_name" "; //水平タブ 基本ルール第2項
 $message .= "$f_encoded ";
}
if(file_exists($file2)) {
 $file1 が $file2 に変わるだけで上と同じ。
}
if(file_exists($file1) || file_exists($file2)) { //注2
   $message .= "--$boundary--"; //基本ルール第5項
}
?>

注2:スクリプトの最後にくる boundary は、Thunderbird では「ファイルが存在する場合」と条件をつけないと、添付が無い場合でも表示されてしまいます。このほうが厳密な処理ですが、Windows Live Mail では条件をつけなくても表示されません。

以上で引数の仕立ては全て完了。


mail 関数の実行

<?php
if(mail($mailto, $subject, $message, $add_header)) {
 print "正常に送信されました<br> ";
} else {
 print "送信に失敗しました。<br> ";
}
exit;
?>



コメント