Perlのメールフォームで添付メールを送る

ウェブ制作関連

あぁ、いつ以来の投稿だろう・・・

なんだか色々メディアを増やしすぎて自分的古参メディアが放ったらかしになっている感じなんだが、たまに書きたい事は有るもののなかなか時間が取れない。
で、今回はPerlメールフォームを作ったんだけど、ファイル添付をする時にはまった事があったので、備忘録も兼ねて残しておく。

今まで、Perlメールフォームは腐るほど書いてきたし、なんだったら大規模なプログラムだとクライアントのオリジナルでECサイトを作った事が有るぐらいなのに、何故かファイル送信をするフォームは作る事が無かった。

で、今回初めてのオーダーで、作ることになった。
添付ファイルがある場合のメールフォーマットもなんとなく知っていたので、できる事は知っていたのだが・・・
不安だったので、
『これ、作った事が無いので、早めに仕様をください』
とお願いしていたのだが・・・まぁ事故もあって、仕様などが入手できたのが締切の4日前・・・
しかもこれは開発のほんの一部で他のものあるので、4日間それだけに使えるわけでは無い感じ。
仕方がないので、とりあえず、その他の作業を圧縮して終わらせて、この件に使える時間を確保した感じだ。

まず、色々と情報を収集。

HTML上で
<input type=”file”・・・>
と、するだけで、ファイルをローカルから添付するようなアイテムを表示する事はできる


↑これはファイルが張り付くだけで稼働しませんよ

しかも、ファイル名を画面上貼り付けるところまではブラウザのデフォルト機能として動作する。

と、ここで一つ思った事、
『これって以前はパスが張り付いてなかったかなぁ・・・どのぐらいか前からファイル名だけしか表示されなくなったなぁ、これって裏側はどうなってるんだろう・・・』

記憶が曖昧だが、昔はフルパスが張り付いていたように思う。
HTMLの記述などで見られる相対パスだと、表示をしているファイルなどの位置から見た相対の位置というのが存在するが、サーバー上のファイルからローカルを見た時に相対の位置は存在しないと思うので、当然絶対パスが必要だろうと思っていた。
まず、ここがはまる一つ。

次に、私が作っているフォームの構成は、

  1. 入力画面
  2. エラーもしくは入力確認画面
  3. 送信処理後に完了画面

という感じになっている。
まぁよくある構成だと思う。

という事は、入力画面でファイルを貼り付けさせると、その後そのファイル位置を実際の送信処理を行うまで保持する必要がある。
テキストボックスやテキストエリア、ラジオボタン、チェックボックス、プルダウンメニューなどは『Value』という属性で入力された値を保持したり、プログラムであればそこに設定する事で、規定値とする事ができる。

ただ、type=”file”にはvalue属性が無い
これがあると色々セキュリティ的によろしく無いのだ。
考えてみると、なるほど・・・アレをこうして・・・、うん、よろしく無い。

という事で、色々試したのだが・・・
まぁここでも、後で分かる理解できていないポイントが有ったので、そのうち試しても良いのだが、もし出来たとしてもやっぱりセキュリティ的にはよろしくないはずなので、付け焼き刃で変なハードルを越えるよりもここはセオリーどおりに行こうという感じで、添付ファイル以外の項目が入力されて確認画面へ進めたタイミングで添付させて送信という流れにすることにした。

で、流れ的には問題ないのだが・・・
届いたメールを見るとなぜか添付ファイルが壊れている。

こういう時は一個づつチェックをするしかない。

まずは、フォームから取得しているファイルの内容のチェックをしてみると、どうやらこれが既に空になっている。
色々情報を集めて書いているのだが、その時のコードはこんな感じ

if (! open(FILE, “< $filename")) {
return 0;
}
$filesize = -s $filename; # ファイルサイズ
$readsize = read(FILE, $filebody, $filesize);
close(FILE);
$base64filebody = MIME::Base64::encode($filebody);

※$filename→フォームから送られているファイル名

内容的には、フォームから送られてきたファイルを元に、ファイルハンドルを設定して、サイズを取得、ファイルハンドルを通じてサイズ分取得して、Base64エンコードしているという感じ。

特に、おかしなところは無いと思う。

ここでしばらく悩む・・・、これで行けてる人がいるから、ここ自体は間違いはないはずなのに・・・

と、ふと思い出した事が有った。
フォームから送信された『ファイル名』、上記コードで言う$filename、これは、『ファイルの名前でもあり、ファイルハンドルとしても使える』という記述が有った事を思い出した。

え?『ファイルハンドルとしても使える』の?
じゃあ

open(FILE, “< $filename")

これは、どう言う事?

まぁ考えていても仕方ないので、$filenameをファイルハンドルとして使う感じに書き換えてみた。

if (length $filename != 0 ){

$filesize = -s $filename; # ファイルサイズ
$readsize = read($filename, $filebody, $filesize);

$base64filebody = MIME::Base64::encode($filebody);

}

冒頭のif文はちょっと綺麗じゃないかもしれないけど、とりあえず今回は無視で。
こう書いて、$filebodyの中身をブラウザで表示すると見事にブラウザの窓は文字で埋め尽くされた。

ここからは、私の推測。

従来、<input type=”file”>にパスが張り付いていた時は、本当にファイルのパスだったので、そこからファイルハンドルを作る必要があったけども、仕様が変更されて、これが必要無くなったという事なんだと思う。

という事で、添付ファイルの処理はわかった。
あと、厄介なのは、

  • バウンダリー文字列
  • ファイルタイプの確認

の2点。

まず、添付ファイルのあるメールのフォーマットがこちら

To: 送信先メールアドレス
From: 送信元メールアドレス
Subject: 件名
Mime-Version: 1.0
Content-Type: Multipart/Mixed; boundary=”バウンダリー文字列”

–バウンダリー文字列
Content-Type: text/plain; charset=”UTF-8″
Content-Transfer-Encoding: base64

メール本文

–バウンダリー文字列
Content-Type: MIMEタイプ; name=”ファイル名”
Content-Disposition: attachment; filename=”ファイル名”
Content-Transfer-Encoding: base64

添付ファイル

–バウンダリー文字列–

ざっくり理解すると、冒頭でどういう種類のメールかを宣言して、書くアイテムが続く。
アイテムはそれぞれ、その最初にそういう種類の物かを宣言して、本体が続くという感じ。
で、そう複雑でもなく、添付ファイルが複数ある場合は、

–バウンダリー文字列
Content-Type: MIMEタイプ; name=”ファイル名”
Content-Disposition: attachment; filename=”ファイル名”
Content-Transfer-Encoding: base64

添付ファイル

これを繰り返すだけ。

全体を見ると分かるように、それぞれを区切っているのが『バウンダリー文字列』
このバウンダリー文字列は仕様的にはけっこうシンプル。

1文字から70文字の英数文字と数種類の記号を任意で組み合わせて作られる文字列で問題なくて、メールの都度生成するわけではなくて、どのメールでも固定で大丈夫だ。

https://wiki.suikawiki.org/n/boundary#anchor-4&gsc.tab=0

ただ一点結構悩ましい条件がある。
この文字列は本文中の文字列と重複するとダメだという事。

そうなると、短めの文字列は危なそうで、1文字なんてもっての外、さてどうするか・・・
ここは、最大限70文字を使ってなるだけ意味のない文字列にする必要があると感じた。

意味のない文字列というのが結構難しいので、いくつか文字列をピックアップして、順番を入れ替えたり、記号を混ぜたりしてなんとなく意味のなさげな文字列を作ってみた。
今のところ問題なく動いているので、まぁ大丈夫だと思う。
問題は、添付ファイルの中に重複する文字列があるかどうかだが・・・ここは逆に意味のある長い文字列の方が重複しずらいんじゃないかと思うので、なんとなく両方満たしているとは思っている・・・多分。

さて、次にファイルタイプだ。

今回、送信させるファイルは、JPG、PNG、PDFの3種類。
色々調べてみたが、気持ちよく種類が特定できるものがなさそう。
ファイル名で判断させるのが簡単だが、内容を誤魔化そうと思えば別の拡張子にすることも可能なので避けた。

まず、<input type=”file”>でファイルを選択する場合は、accept属性で、ファイルの種類を限定してやれば、OS側でそれ以外はグレーアウトして選択できないようになるので一つ安心。

JPG、PNG、PDFの3種類の場合、こんな感じ。

<input type=”file” id=”ID” name=”File” accept=”image/png, image/jpeg, .pdf” >

で、副産物的な話だが、ファイルハンドルで悩んでいた時に、ファイルの中身をブラウザで表示した時に気づいた事で、PNGとPDFに関してはファイルの冒頭にフォーマットの宣言のような物がある。
という事で、この2種類は判別が可能として、そうじゃないものをJPGとする事にした。

いくつかサンプル的に内容を確認してみたが、今のところ問題ないので大丈夫だと思っている

あと、ファイル送信をさせるフォームについては、formタグに『enctype=”multipart/form-data”』という属性を追加しておく必要がある。
これ、結構忘れがちで、ハマってしまうので要注意。

とりあえず、こんな感じで、添付ファイル付きメールを送るフォームが完成した。
なんでもそうだけど、できてしまうと何のことはない感じ。
こだわるとハマる。

コメント