MIMEとはデータの小包や内容物に付加して複数の種類のデータをひとまとめにするための「インデックス」だと前回書きました。
商取引に関するデータなどには「電子署名」と「見積書」などテキスト以外のデータが同時に送受信されることが多々あります。つまり、電子署名を含み、かつマルチパートのデータを送受信することになります。
電子署名を含み、かつマルチパートのデータの送受信というと二重にややこしいように思えますがそんなことはありません。電子署名はテキストデータなので、通常のマルチパートのデータを送受信するのと何も変わらず扱うことができます。ただ、電子署名がダイジェストを生成する範囲が明示されるという違いのみです。
ここで、いまいちど、MIME mutipartデータのやりとりについておさらいしてみます。
1. メールの場合(POP/SMTP/IMAP)
電子メールはヘッダー(メールの配送情報など)とボディー(メール本文)から構成される下記に示すような複雑なテキストデータであり、これをメーラー(メールを表示するソフトウェア)がPOPやIMAPで受け取って我々が普段見ている状態で表示します。メール本文に「テキスト」と「画像」のように複数の要素が含まれる場合、ボディーはMIME multipartのフォーマットに準拠してテキストで記載されます。画像もBase64エンコードされたテキスト形式で記述されます。
このMIME multipartフォーマットのデータは送信側のメーラーで自動生成され、受信側のメーラーで自動解読されるので我々は普段は特にこのフォーマットでデータを生成するコードを書く必要はありません。もし手作業で生成・解読したい場合はmb_encode_mimeheaderなどの関数を使って実装することはできますが、実際にそのような必要性に遭遇することはほぼないでしょう。
なお、テキストへの電子署名の項目でも述べましたが、メールの文面に対して電子署名を生成する場合にも「どこからどこまでを対象に電子署名を生成したか」でダイジェスト値が変わってしまうという微妙な問題があります。ですので、メールの電子署名生成には「S/MIME(Secure/Multipurpose Internet Mail Extensions)」という方式で「どこからどこまで」を明確にした状態で電子署名が生成され、送信するデータの中に署名が込められます。
(例)テキストと添付ファイルを含むメールデータの生データ:
<!-- メールのルーティング情報がついているが諸事情につき省略 --> X-Received: by 2002:aca:3c8a:: with SMTP id j132mr803939oia.52.1605257781689; Fri, 6 Nov 2020 00:56:21 -0800 (PST) MIME-Version: 1.0 From: "Some Sender" <some_sender@gmail.com> Date: Fri, 6 Nov 2020 17:56:10 +0900 Message-ID: <CA+k0eBRt=VXW0fOTQva5WyChC_Tj7p=xWek_Hewfq7MT=7iSug@mail.gmail.com> Subject: multipart mail message 1 To: Ryusuke D. Matsubara <admin@electric-blue-industries.com> <!-- ここからがMIME multipart構成のデータ本体 --> Content-Type: multipart/related; boundary="000000000000d9e53705b3f9336e" --000000000000d9e53705b3f9336e Content-Type: multipart/alternative; boundary="000000000000d9e53605b3f9336d" --000000000000d9e53605b3f9336d Content-Type: text/plain; charset="UTF-8" This is a test message. [image: Ehj6ze3UwAQx6H-.jpeg] --000000000000d9e53605b3f9336d Content-Type: text/html; charset="UTF-8" <div dir="ltr">This is a test message.<div><img src="cid:ii_khg10nni0" alt="Ehj6ze3UwAQx6H-.jpeg" width="542" height="355"><br></div></div> --000000000000d9e53605b3f9336d-- --000000000000d9e53705b3f9336e Content-Type: image/jpeg; name="Ehj6ze3UwAQx6H-.jpeg" Content-Disposition: inline; filename="Ehj6ze3UwAQx6H-.jpeg" Content-Transfer-Encoding: base64 Content-ID: <ii_khg10nni0> X-Attachment-Id: ii_khg10nni0 /9j/4AAQSkZJRgABAQAASABIAAD/2wBDAAQEBAQEBAcEBAcKBwcHCg0KCgoKDRANDQ0NDRAUEBAQ EBAQFBQUFBQUFBQYGBgYGBgcHBwcHB8fHx8fHx8fHx//2wBDAQUFBQgHCA4HBw4gFhIWICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICD/wgARCAF8AkQDASIA AhEBAxEB/8QAHAAAAgMBAQEBAAAAAAAAAAAAAgMAAQQFBgcI/8QAGgEBAQEBAQEBAAAAAAAAAAAA AQACAwQFBv/aAAwDAQACEAMQAAAB+V6sGvz6blYqmRS4am1aL0o20S5WdCSic6l0qYsb1ljVNy1B <!-- Base64エンコードされた画像だが長いので中略 --> 4ACR64xAfgxACVAAhAIBk4wfY+FYgAQrqfwogwCGKHgAH1Gk+EICU0ssYuD+MRpR1ofAonvkSMNq DP8AY+QgEBvyIACZPIBAB/EIQQEBR8yAKA4nzAQQAJhFDzAAiInAjIlYcNEfX+2PwCURU5xkQWeh T4iDNp4ADBwHpK3KGniEI6X/AMQAAEAgQABgQGAACEAVIeERH8/OL/Grg+OXh5CCJUMCVxR5FGhg M3MPJgI4lQy4M/EA14AqkRcOHmLXErS5g8/gDKqsIuCofSP/2Q== --000000000000d9e53705b3f9336e--
上記のメールデータがメーラーに表示されると下記のようになります。つまり、メールに含まれるMIME multipartデータのデコード処理はサーバーでではなくメーラーというメールクライアントが行うため、基本的にサーバーでMIME multipartデータのデコード処理をする必要性はほとんどありません。
テキストと添付ファイルを含むメール(上記の生データをメーラーで見た状態):
2. POSTの場合(HTTP)
メールのMIME multipartデータのハンドリングはメーラーが処理してくれますが、ではHTTP通信で送受信する場合はどうしたら良いでしょうか?
ケース1: ブラウザからサーバーへマルチパートのデータを送受信する
ブラウザからサーバーにマルチパートのデータを送受信する場合はHTMLのフォームを用いてPOST送信を行うことがほとんどだと思います、その際、複数のパラメータをPOSTするには単純なHTMLで下記のように書けます。
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>form post</title> </head> <body> <form method="POST" enctype="multipart/form-data"> <!-- 画像を指定して送る --> <input type="file" name="image_1"> <!-- パラメータ(文字列)を指定して送る --> <input type="text" name="text_1"> <input type="submit" id="submit" value="POST data" /> </form> <br><br> <?php // getallheadersでPOSTされた際のヘッダー情報を全取得して表示する var_dump(getallheaders()); // ファイルがPOSTされた場合は$_FILESにその情報が格納される var_dump($_FILES); // パラメータ(文字列)がPOSTされた場合は$_REQUESTにその情報が格納される var_dump($_REQUEST); ?> </body> </html>
下記のようにgetallheaders()で取得されたヘッダー情報(マルチパートであることを示すContent-Typeもデータの境目を示すboundaryも自動的に付加されている)
array(18) { ["Content-Length"]=> string(5) "27090" ["Content-Type"]=> string(68) "multipart/form-data; boundary=----WebKitFormBoundaryOiIrO5lcfnTIBxBh" ["Accept"]=> string(135) "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" ["Accept-Encoding"]=> string(17) "gzip, deflate, br" ["Accept-Language"]=> string(23) "en-US,en;q=0.9,ja;q=0.8" ["Cache-Control"]=> string(9) "max-age=0" ["Cookie"]=> string(1018) " 諸事情により省略 " ["Host"]=> string(28) "electric-blue-industries.com" ["Origin"]=> string(36) "https://electric-blue-industries.com" ["Referer"]=> string(54) "https://electric-blue-industries.com/test/formpost.php" ["Sec-Fetch-Dest"]=> string(8) "document" ["Sec-Fetch-Mode"]=> string(8) "navigate" ["Sec-Fetch-Site"]=> string(11) "same-origin" ["Sec-Fetch-User"]=> string(2) "?1" ["Upgrade-Insecure-Requests"]=> string(1) "1" ["User-Agent"]=> string(119) "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36" ["X-Https"]=> string(1) "1" ["X-Real-Ip"]=> string(12) "110.65.7.111" }
POSTされたファイルの情報は$_FILESに格納されている。
array(1) { ["image_1"]=> array(5) { ["name"]=> string(40) "7a62c40a067018c2ceae00ba7c6845cb_600.jpg" ["type"]=> string(10) "image/jpeg" ["tmp_name"]=> string(14) "/tmp/php159qsb" ["error"]=> int(0) ["size"]=> int(26773) } }
POSTされたパラメータ(文字列)の情報は$_REQUESTに格納されている。
array(1) { ["text_1"]=> string(7) "nekosan" }
ケース2: サーバー間でマルチパートのデータを送受信する
サーバー間でマルチパートのデータを送受信する場合も特に難しいことはありません。よくある方法としては下記のようにcUrlで複数のパラメータを要素として持つ配列をPOSTしてあげれば良いだけです。
$params = array( 'image_1' => $image_1_data, 'image_2' => '@/usr/tmp/image_2.jpg', 'text_1' => $text_1_data ) ; $ch = curl_init(); curl_setopt_array($ch, array( CURLOPT_URL => 'http://target_server_url', CURLOPT_POST => true, CURLOPT_POSTFIELDS => $params, )); curl_exec($ch);
画像の送り方ですが、「@」に「/usr/tmp/image_2.jpg」のようなパスを記述してファイルのまま送ることも可能で、この場合は受信側の$_FILESにファイルの情報が格納されます。また、ファイルをBase64エンコードしたテキストとして送ることも可能であり、その場合は受信側の$_REQUESTにファイル自体が格納されます。
普段何気なく使っているブラウザやウェブサーバにはたくさんの処理機能があらかじめ含まれていてありがたいですね。