メール送信

今まで作成してきたメールデータを使って、メールフォームCGIから電子メールを送信してみましょう。

このプロセスがメールフォームCGIの中で、いちばん難しいところになりますが、お決まりのパターンになっている部分なので、わからなくても問題ありません。

通常、Perl/CGIプログラムから電子メールを送信するには、Sendmailと呼ばれるインターネットサーバーから電子メールを送信するソフトウェアを使います。

Sendmailであれば、ほとんどのレンタルサーバーに採用されているシステムですから問題ないと思います。

あとは、電子メールの送信規約に従って、メールデータに変更を加えていくだけです。

  1. ダウンロード
  2. 解説

ダウンロード

サンプルプログラムをダウンロードするにはここをクリックします。

ZIP形式で圧縮されているので解凍作業が必要です。

改行コードはWindows形式です。

文字コードは、Perl/CGIプログラム本体および各種設定ファイル、シフトジスバージョンの入力フォーム、thanks.htmlはShift_Jisコードです。

もちろんEUCバージョンの入力フォームはEUC、UTF-8バージョンの入力フォームはUTF-8コードになっています。

実際にメールが送信できますから、サンプルは設置していません。

解説

電子メールを送信するために、「mail.pl」に、base64encode関数とsend関数を追加しました。

さらに、「lib」フォルダ内に、MIMEモジュール本体も追加しました。

base64encode関数は、受け取った文字列をBase64エンコードして返す関数です。

send関数は、Sendmailにメールデータをわたし、送信を促す関数です。

base64encode関数

base64encode関数は、受け取った文字列をBase64エンコードして返す関数です。

実際には、送信者名やタイトルに使用する文字列を、JISコードに変換し、エンコードします。

さらにエンコード後の文字列が、76バイト以内になるように一度、文字列を文字単位に分割し、1文字ずつ足し合わせてバイト数を見積もってエンコード文字列を作成していきます。

この関数の引数は、エンコードしたい文字列です。

戻り値は当然、エンコードされた文字列です。

それではこの関数の流れを見ていきましょう。

1、文字変換

メールの送信者名やタイトルに使用する文字列の中に、半角カタカナが含まれている場合には、文字化けが発生する可能性があります。

ですから、なるべく半角カタカナは使わないようにします。

しかし今回のように、訪問者が入力した氏名をメールのタイトルに含める場合には、プログラム側での工夫が必要です。

半角カタカナのパターンマッチでひっかかったらエラーメッセージを出すという手もありますが…。

それだとユーザー側に負担がかかってくるので、送信者名やタイトルを作成する段階で、半角カタカナを発見したら、全角カタカナに変換して処理させることにします。

半角カタカナを全角カタカナに変換している部分は以下です。

my $jcode = Jcode->new();
$jcode->set($string, 'sjis');
$string = $jcode->h2z->sjis;

文字変換には、Jcode.pmモジュールを使用します。

まずは、文字列のオブジェクトを生成します。

my $jcode = Jcode->new();

次に、対象の文字列をJcodeオブジェクトにセットします。

$jcode->set($string, 'sjis');

半角カタカナを全角カタカナに変換する関数を呼び出します。

$string = $jcode->h2z->sjis;

これで、変数「$string」内の半角カタカナは、すべて全角カタカナになりました。

2、文字単位に分割

この後、1文字ずつ変換し、バイト数を見積もっていかなくてはいけないので、まずは、文字列を文字単位にまで分解します。

1文字ずつにするには、文字単位に分割するパターンマッチを使います。

文字コードの範囲を指定している部分は以下になります。

my $oneByte1 = '[\x20-\x7E]';
my $oneByte2 = '[\xA1-\xDF]';
my $twoBytes1 = '[\x81-\x9F][\x40-\xFC]';
my $twoBytes2 = '[\xE0-\xEF][\x40-\xFC]';

シフトジスコードでは、1バイトで表現できる文字(半角英数と半角カタカナなど)と…。

2バイトで表現できる文字(全角文字など)があります。

これらをすべて正規表現で定義しておいて、後は、パターンマッチで1文字ずつに分解します。

my @char = $string =~ /$oneByte1|$oneByte2|$twoBytes1|$twoBytes2/gox;

配列「@char」に、1文字ずつに分けられた文字が代入されました。

つまり、文字数の分だけ、配列「@char」の要素数が存在するということです。

3、Base64エンコード

今から作成していくプログラムについて解説します。

1文字ずつに分解した文字を、順にJISコードに変換します。

そして、1単位76バイト以内になるように、文字をつなげていきます。

このとき、エンコード後の文字というのは、約1.3倍ほどにバイト数が膨れ上がることを考慮しなくてはいけません。

さらに、規定により、いくつか追加しなくてはいけない文字列というのがあります。

「Subject:」と空白文字が1つ。
「=?ISO-2022-JP?B?」と「?=」。

これらの文字列を合計すると、27バイト(エンコードは不要)。

そして、それぞれの単位をつなげるために、改行コードと空白文字が必要なので、さらに2バイト必要です。

そうなると「76=(1単位当たりの文字数×約1.3)+27+2」となります。

つまり、安全にデータ処理を行うためには、1単位当たり約30バイトを基準に分割していくのが良いことになります。

今から、これらの条件を満たすプログラムを作成していきます。

3-1、バイト数の見積もり

1単位が、30バイトということがわかったので、まずは、ジスコードに変換後、30バイト単位で配列に格納します。

my @jisline = ();
my $jisline = "";
foreach (@char) {
&Jcode::convert(\$_, 'jis', 'sjis');
$jisline .= $_;
if (length($jisline) > 30) {
push(@jisline, $jisline);
$jisline = "";
}
}
if (length($jisline)) {
push(@jisline, $jisline);
}

これで、配列「@jisline」に30バイト単位で格納されました。

3-2、文字列の追加

「=?ISO-2022-JP?B?」と「?=」で、base64エンコードされた文字列をはさみます。

my @encoded = ();
my $encoded = "";
foreach (@jisline) {
my $base64 = MIME::Base64::Perl::encode_base64($_, "");
my $line = '=?ISO-2022-JP?B?' . $base64 . '?=';
push (@encoded, $line);
}

配列「@encoded」に、76バイト以内で、エンコードされた文字列が格納されました。

3-3、文字列の結合

それぞれの単位を、改行コードと半角スペースでつなぎます。

$encoded = join("\n\x20", @encoded);

これで、日本語を扱ったメールヘッダーが完成しました。

4、終了

最後に、完成した文字列を返して関数終了です。

return $encoded;

send関数

send関数は、Sendmailにメールデータを渡し、送信を促します。

引数は、メールデータを格納したリファレンス形式の連想配列です。

戻り値は、最後まで処理が進めば1で、Sendmailへの接続がうまくいかないなどのトラブルがありましたら何も返されません。

それでは、流れをひとつずつ見ていきましょう。

1、データ加工

送信者名とタイトルをエンコードし、本分をJisコードに変換します。

my $encodedSender = &base64encode($mail->{sender});
my $encodedSubject = &base64encode($mail->{subject});
&Jcode::convert(\$mail->{body}, 'jis', 'sjis');

送信者名とタイトルは、前述のbase64encode関数を使ってエンコードします。

本文は、Jcodeモジュールを使ってJisコードに変換します。

2、Sendmailに接続

Sendmailのパスを使って、Sendmailに接続し、ハンドルと関連付けます。

open(MAIL,"| $mail->{sendmail} -t") || return;

ファイル処理のときと同様に、open関数でSendmailを開き、ハンドル名「MAIL」と関連付けます。

Sendmailに接続できなかった場合は、「||」以降のreturn関数が実行されこの関数終了です。

3、メールデータの出力

Sendmailへの接続ができたら、ハンドル名「MAIL」を使って、メールデータを出力していきます。

print MAIL "From: $encodedSender\n\x20\x3C$mail->{from}\x3E\n";
print MAIL "To: $mail->{to}\n";
print MAIL "Bcc: $mail->{bcc}\n" if ($mail->{bcc} ne "");
print MAIL "Subject: $encodedSubject\n";
print MAIL "Errors-To: $mail->{error}\n";
print MAIL "MIME-version: 1.0\n";
print MAIL "X-Mailer: Mailform CGI\n";
print MAIL "Content-type: text/plain; charset=\"ISO-2022-JP\"\n";
print MAIL "Content-transfer-encoding: 7bit\n";
print MAIL "\n";
print MAIL "$mail->{body}";
print MAIL "\n";

送信者名やfromアドレス、メールタイトルなどを先に出力してから、本文を出力します。

このあたりはお決まりの形なので、こういうものだと思ってください。

最後の「\n」出力はなくてもかまいません。

4、Sendmailを閉じる

ファイル処理のときと同様に、処理が終了したらclose関数を使って、Sendmailへの接続を閉じます。

close (MAIL);

5、終了

これでSendmailにメールデータを渡すことができたので、この関数を終了します。

return 1;

メイン処理

メイン処理では、send関数にメールデータを渡し、メール送信させています。

さらに、送信後は、「thanks.html」にジャンプさせています。

print "Location: $config->{location}\n\n";

あとは、じみにSendmailとメールファイルまでのパスが正しいかどうかも調べています。

(-e $config->{sendmailpath}) or &script::html('Error !', "$message->{sendmailcheckerror} $config->{sendmailpath}", 1);
(-f $config->{resmailfilepath}) or &script::html('Error !', "$message->{filecheckerror} $config->{resmailfilepath}", 1);
(-f $config->{infomailfilepath}) or &script::html('Error !', "$message->{filecheckerror} $config->{infomailfilepath}", 1);

ここまでで一応、最低限度の機能しかありませんが、メールフォームCGIとして動作するPerl/CGIプログラムが完成しました。

あとは、あなたの希望する機能を追加していくだけです。