Perl/CGI研究室 'PERL-LABO'

Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

ファイルの添付の研究

研究内容

メールにはファイルを添付することができます。 Perlとsendmailを使ってファイルを添付する方法を研究しましょう。

詳細

メールにファイルを添付する方法

sendmailを使用してPerlからメールを送信するには、 メールヘッダを含むデータをsendmailに渡してあげるんでした。 残念ながら、ファイル名を指定するだけでそのファイルが添付されるような 便利な機能はないようで、 メールデータの中に自力でファイルが添付されていることを示すヘッダや 添付するファイルデータなどを加える必要があるようです。

ファイルを添付したときの、ヘッダなどを含むメールの内容は次のようになります。

Content-Type: Multipart/Mixed; boundary="(バウンダリー文字列)"
Content-Transfer-Encoding:Base64
From: 送信元
To: 送信先
Subject: 件名

--(バウンダリー文字列)
Content-Type: text/plain; charset="ISO-2022-JP"

(メール本文)

--(バウンダリー文字列)
Content-Type: application/octet-stream; name="(添付ファイル名)"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="(添付ファイル名)"

(添付するデータをbase64でエンコードしたもの)

--(バウンダリー文字列)--

まず、「Content-Type: Multipart/Mixed; 〜」というので、 このメールが複数のパートに分かれていることを示します。 (バウンダリー文字列)というのは、任意の文字列で、 データの境界、パートの始まりを示すのに使用されます。 「--(バウンダリー文字列)」となっているのがデータの境界です。 この境界に続いて、そのパートの内容を「Content-Type」で指定してやります。 「Content-Type: text/plain;」となっている1つ目のパートがメール本文、 「Content-Type: application/octet-stream」という2つ目のパートが添付ファイルを 示すパートです。 「--(バウンダリー文字列)--」となっているのはメールデータの終わりです。

なんでこうなるのかというのは、とりあえずおいておきましょう (^^; こうゆうふうにすると、ファイルが添付されているとメーラーの方で処理してくれるわけです。 そういうルールなんですね。このルールの詳細については、またいつか研究することにします。

さて、問題は、添付するファイル本体ですが、 (添付するデータをbase64でエンコードしたもの)でなければいけません。 この点に注意しつつ、プログラムを作成してみました。

作成したCGIプログラム

sendmail.pl
# v 1.04

package plab;

use Jcode;
use MIME::Base64;

# sendmailのパス
$sendmailpath = '/usr/sbin/sendmail';

# 引数 (差出人名, 差出人メールアドレス, 送信先名, 送信先メールアドレス, 
#       件名, 本文 [, 返信先名, 返信先メールアドレス, 日時とホストを追加しない1]
#       [, Cc名, Ccアドレス, Bcc名, Bccアドレス])
# いずれも空欄可
sub sendmail
{
	local $from_name  = $_[0];
	local $from_adr   = $_[1];
	local $to_name    = $_[2];
	local $to_adr     = $_[3];
	local $subject    = $_[4];
	local $body       = $_[5];
	local $rep_name   = $_[6];
	local $rep_adr    = $_[7];
	local $notimehost = $_[8];
	local $cc_name    = $_[9];
	local $cc_adr     = $_[10];
	local $bcc_name   = $_[11];
	local $bcc_adr    = $_[12];

	# Fromメールアドレスがからっぽだとエラーになるので
	if ($from_adr eq "") {
		return 0;
	}

	# JIS に変換します。
	local $jis_body = Jcode::convert($body, 'jis');

	# MIMEヘッダ内の日本語をエンコードします。
	local $header_subject = mimeencode_headnamemail('Subject',  $subject);
	local $header_from    = mimeencode_headnamemail('From',     $from_name, $from_adr);
	local $header_to      = mimeencode_headnamemail('To',       $to_name,   $to_adr);
	local $header_replyto = mimeencode_headnamemail('Reply-To', $rep_name,  $rep_adr);
	local $header_cc      = mimeencode_headnamemail('Cc',       $cc_name,   $cc_adr);
	local $header_bcc     = mimeencode_headnamemail('Bcc',      $bcc_name,  $bcc_adr);

	if (! open(MAIL, "| $sendmailpath -t")) {
		return 0;
	}

	# メールヘッダ〜本文を出力します。
	print MAIL "$header_from\n";
	print MAIL "$header_to\n";
	if ($rep_adr ne "") { print MAIL "$header_replyto\n"; }
	if ($cc_adr  ne "") { print MAIL "$header_cc\n"; }
	if ($bcc_adr ne "") { print MAIL "$header_bcc\n"; }
	print MAIL "$header_subject\n";
	print MAIL "Content-Transfer-Encoding: 7bit\n";
	print MAIL "Content-Type: text/plain; charset=\"ISO-2022-JP\"\n";
	print MAIL "\n";
	print MAIL "$jis_body";

	if ($notimehost ne "1") {
		# ホスト名、送信時間を付加します。
		local $ip   = $ENV{'REMOTE_ADDR'};
		local $host = gethostbyaddr(pack("C4", split('\.', $ip)), 2) || $ip;
		local @tim  = localtime(time());
		local $year = $tim[5] + 1900;
		local $mon  = $tim[4] + 1;
		local $t    = sprintf('%02d:%02d:%02d', $tim[2], $tim[1], $tim[0]);

		print MAIL "\n";
		print MAIL "----------------------------\n";
		print MAIL "DateTime : $year/$mon/$tim[3] $t\n";
		print MAIL "Host     : $host";
	}

	close MAIL;

	return 1;
}

# メールヘッダを作る
# 引数(ヘッダ名, 名前, メールアドレス)
sub mimeencode_headnamemail
{
	local ($head, $name, $mail) = @_;

	if ($name eq "" && $mail eq "") { return ""; }

	# 汚染チェック
	$mail =~ s/\r|\n//g;
	
	local $jis_name  = Jcode::convert($name, 'jis');
	local $mime_name = Jcode::mime_encode([\$jis_name]);

	if ($name ne "" && $mail eq "") { return "$head: $mime_name";        }
	else                            { return "$head: $mime_name<$mail>"; }
}

# 添付ファイル付きでメールを送る
# 引数 (添付するファイルパス, 添付するパス無しファイル名,
#	差出人名, 差出人メールアドレス, 送信先名, 送信先メールアドレス, 
#       件名, 本文 [, 返信先名, 返信先メールアドレス, 日時とホストを追加しない1]
#       [, Cc名, Ccアドレス, Bcc名, Bccアドレス])
sub sendmail_attach
{
	local $fname      = $_[0];
	local $ftitle     = $_[1];
	local $from_name  = $_[2];
	local $from_adr   = $_[3];
	local $to_name    = $_[4];
	local $to_adr     = $_[5];
	local $subject    = $_[6];
	local $body       = $_[7];
	local $rep_name   = $_[8];
	local $rep_adr    = $_[9];
	local $notimehost = $_[10];
	local $cc_name    = $_[11];
	local $cc_adr     = $_[12];
	local $bcc_name   = $_[13];
	local $bcc_adr    = $_[14];

	# Fromメールアドレスがからっぽだとエラーになるので
	if ($from_adr eq "") {
		return 0;
	}

	# ファイルを読み込んでBase64エンコードします
	if (! open(FILE, "< $fname")) {
		return 0;
	}
	$filesize = -s $fname;	# ファイルサイズ
	$readsize = read(FILE, $filebody, $filesize);
	close(FILE);
	$base64filebody = MIME::Base64::encode($filebody);

	# JIS に変換します。
	local $jis_body = Jcode::convert($body, 'jis');

	# MIMEヘッダ内の日本語をエンコードします。
	local $header_subject = mimeencode_headnamemail('Subject',  $subject);
	local $header_from    = mimeencode_headnamemail('From',     $from_name, $from_adr);
	local $header_to      = mimeencode_headnamemail('To',       $to_name,   $to_adr);
	local $header_replyto = mimeencode_headnamemail('Reply-To', $rep_name,  $rep_adr);
	local $header_cc      = mimeencode_headnamemail('Cc',       $cc_name,   $cc_adr);
	local $header_bcc     = mimeencode_headnamemail('Bcc',      $bcc_name,  $bcc_adr);

	if (! open(MAIL, "| $sendmailpath -t")) {
		return 0;
	}

	$boundary = "B4B5O6U7N8D9A0R1Y";

	# メールヘッダを出力します。
	print MAIL "$header_from\n";
	print MAIL "$header_to\n";
	if ($rep_adr ne "") { print MAIL "$header_replyto\n"; }
	if ($cc_adr  ne "") { print MAIL "$header_cc\n"; }
	if ($bcc_adr ne "") { print MAIL "$header_bcc\n"; }
	print MAIL "$header_subject\n";
	print MAIL "Content-Type: Multipart/Mixed; boundary=\"$boundary\"\n";
	print MAIL "\n";

	# 本文を出力します。
	print MAIL "--$boundary\n";
	print MAIL "Content-Transfer-Encoding: 7bit\n";
	print MAIL "Content-Type: text/plain; charset=\"ISO-2022-JP\"\n";
	print MAIL "\n";
	print MAIL "$jis_body\n";

	if ($notimehost ne "1") {
		# ホスト名、送信時間を付加します。
		local $ip   = $ENV{'REMOTE_ADDR'};
		local $host = gethostbyaddr(pack("C4", split('\.', $ip)), 2) || $ip;
		local @tim  = localtime(time());
		local $year = $tim[5] + 1900;
		local $mon  = $tim[4] + 1;
		local $t    = sprintf('%02d:%02d:%02d', $tim[2], $tim[1], $tim[0]);

		print MAIL "\n";
		print MAIL "----------------------------\n";
		print MAIL "DateTime : $year/$mon/$tim[3] $t\n";
		print MAIL "Host     : $host\n";
	}

	# 添付ファイルを出力します。
	print MAIL "--$boundary\n";
	print MAIL "Content-Type: application/octet-stream; name=\"$ftitle\"\n";
	print MAIL "Content-Transfer-Encoding: base64\n";
	print MAIL "Content-Disposition: attachment; filename=\"$ftitle\"\n";
	print MAIL "\n";
	print MAIL "$base64filebody\n";

	# メールの終わり
	print MAIL "--$boundary--\n";

	close MAIL;

	return 1;
}

1;

考察

別関数にしました

出力する内容が違うので、別の関数として作成しました。 sendmail_attach という関数です。 引数の最初に、添付するファイル名と、 メールを受け取った人が見えるファイル名を指定します。 あとは、最初に研究した通りにメールの内容を出力していくだけでした。 ファイルの内容をBase64エンコードする、という部分については、 前にJcodeの研究をしたときに少しだけでてきた MIME::Base64というモジュールのencode関数を呼び出すだけでできてしまいました。 作る前は、ちょっと難しそうだなと思ったんですが、 便利な関数があったおかげで簡単でした (^^ でもどうせなら、最初から便利なメール送信関数が用意されていたらいいのに… と思ったりして。

Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

Copyright (c) 'PERL-LABO' All Rights Reserved.  リンクフリーです。