PHPでメールの中身をフルに分解して出力する(日本語&添付ファイル対応)
POSTED BY
2024-10-12
2024-10-12


PEAR::Mail_mimeDecodeを使う。準備編はこちら。
標準入力からメールデータを読み込み、From,To,件名等各種ヘッダおよび本文を表示し添付ファイルを保存するサンプル。JIS文字コード本文に対応するため外部コマンドnkfを使っているので、それは入れておく。
apt install nkf
テストメールは、以下のような感じでGmailで作成し自分に送信。
Subject: テストメールです(添付つき) From: Note Server <servernote.net> To: servernote.net@gmail.com こんにちは。 テストメールを送ります。 サンプル画像も添付します。 ---- WEBMASTER </servernote.net>
添付ファイルは、以下のサンプル雲画像cloudy.jpgを添付。

受信側に送られて来るメールの生データは以下のような感じである。
| Shell | testmail.eml | GitHub Source |
MIME-Version: 1.0 Date: Mon, 12 Oct 2020 19:13:51 +0900 Message-ID: <CA+LSZZyMBz4o80f24u=+qtEYGF=ghBRJHCrB6FhaE-tpXqr+eQ@mail.gmail.com> Subject: =?UTF-8?B?44OG44K544OI44Oh44O844Or44Gn44GZ77yI5re75LuY44Gk44GN77yJ?= From: Note Server <servernote.net@gmail.com> To: servernote.net@gmail.com Content-Type: multipart/mixed; boundary="00000000000012547305b1768e3b" --00000000000012547305b1768e3b Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: base64 44GT44KT44Gr44Gh44Gv44CCDQrjg4bjgrnjg4jjg6Hjg7zjg6vjgpLpgIHjgorjgb7jgZnjgIIN CuOCteODs+ODl+ODq+eUu+WDj+OCgua3u+S7mOOBl+OBvuOBmeOAgg0KLS0tLQ0KV0VCTUFTVEVS DQo= --00000000000012547305b1768e3b Content-Type: image/jpeg; name="cloudy.jpg" Content-Disposition: attachment; filename="cloudy.jpg" Content-Transfer-Encoding: base64 X-Attachment-Id: f_kg6dp1er0 Content-ID: <f_kg6dp1er0> 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 --00000000000012547305b1768e3b--
以下、本記事のメイン、解析プログラムはこちら。
| PHP | maildecode.php | GitHub Source |
<?php date_default_timezone_set('Asia/Tokyo');
//標準入力からメールの中身を取り出して表示するサンプル
//添付ファイル保存機能つき
require_once 'Mail/mimeDecode.php';
//ヘッダ取得:$strにそのまま入る
function read_header_1(&$obj, &$key, &$str)
{
$str = '';
if (array_key_exists($key, $obj->headers)) {
$str = $obj->headers[$key];
}
echo $key . ": " . $str . "\n";
}
//ヘッダ取得:$strにそのまま入る+<>に囲まれた部分を$valに入れる
function read_header_2(&$obj, &$key, &$str, &$val)
{
$str = '';
$val = '';
if (array_key_exists($key, $obj->headers)) {
$str = $obj->headers[$key];
$lpos = strpos($str, '<');
$rpos = strpos($str, '>');
if ($lpos !== false && $rpos !== false) {
$lpos++;
$val = substr($str, $lpos, $rpos - $lpos);
} else {
$val = $str;
}
}
echo $key . ": " . $str . "\n";
echo $key . ": " . $val . "\n";
}
//ヘッダ取得:$strにそのまま入る+<>に囲まれた部分を$valに入れる(//で連結)
//上記2関数と異なり$valはグローバル扱いなので呼び側で初期化しておくこと
function read_header_3(&$obj, &$key, &$str, &$val)
{
$str = '';
if (array_key_exists($key, $obj->headers)) {
$str = $obj->headers[$key];
$ref = $str;
if (strpos($ref, '<') !== false) {
while (true) {
$lpos = strpos($ref, '<');
$rpos = strpos($ref, '>');
if ($lpos === false || $rpos === false) {
break;
}
$lpos++;
$sub = substr($ref, $lpos, $rpos - $lpos);
if (strpos($val, $sub) === false) { //すでに連結済みの値はスキップ
$val .= '/' . $sub . '/';
}
$ref = substr($ref, $rpos + 1);
}
} elseif (strpos($val, $ref) === false) {
$val .= '/' . $ref . '/';
}
}
echo $key . ": " . $str . "\n";
# echo $key . ": " . $val . "\n";
}
//標準入力からメールの生データを読み込んでobjectに展開
//etc/aliasesなどから直接起動する場合カレントを適切な場所に移動すること
//chdir( "/home/hogeuser" );
$stdin = file_get_contents('php://stdin');
$decoder = new Mail_mimeDecode($stdin);
$params = []; //オプションをここで設定(コード変換をお任せ設定)
$params['include_bodies'] = true;
$params['decode_bodies'] = true;
$params['decode_headers'] = true;
$params['crlf'] = "\r\n";
//$params['input'] = $stdin;
$object = null;
try {
$object = $decoder->decode($params);
// $object = Mail_mimeDecode::decode($params);
} catch (Exception $e) {
fputs(STDERR, $e->getMessage() . "\n");
$object = null;
exit(); //デコードエラー終了
}
//ダンプ (しない場合コメントアウトのこと)
var_dump($object);
//Date:
$date = $object->headers["date"];
$date_seq = strtotime($date);
$date_str = date('Y-m-d H:i:s', $date_seq);
echo "date: " . $date . "\n";
echo "date_seq: " . $date_seq . "\n";
echo "date_str: " . $date_str . "\n";
//From: To: Message-Id:
$key = 'from';
$str = '';
$val = '';
read_header_2($object, $key, $str, $val);
$key = 'to';
read_header_2($object, $key, $str, $val);
$key = 'message-id';
read_header_2($object, $key, $str, $val);
$msg_id = $val;
//References,In-Reply-Toは//で一気に連結する
$key = 'references';
$val = '';
read_header_3($object, $key, $str, $val);
$key = 'in-reply-to';
read_header_3($object, $key, $str, $val);
echo "references+in-reply-to: " . $val . "\n";
//Subject:
$key = 'subject';
read_header_1($object, $key, $str);
//本文テキストと思われるものを抽出してcontents_bodyに連結して入れる
$contents_body = "";
//シングルパートの場合
if (property_exists($object, 'body')) {
$contents_body .= $object->body;
}
//マルチパートの場合
if (property_exists($object,'parts')) {
$parts = $object->parts;
$n = count($parts);
for ($i = 0; $i < $n; $i++) { //マルチパート取得ループ
$arval = $parts[$i];
if (array_key_exists("content-type", $arval->headers)) {
if (strpos($arval->headers["content-type"], "text/plain") !== false) {
//echo $arval->headers["content-type"] . "\n";
if (property_exists($arval, 'body')) {
//echo $arval->body;
$contents_body .= $arval->body;
}
}
}
if (array_key_exists("content-disposition", $arval->headers)) { //添付ファイル保存
$dsp = $arval->headers["content-disposition"];
$lpp = strpos($dsp, "filename=\"");
if ($lpp !== false) {
$lpp += 10;
$sub = substr($dsp, $lpp);
$rpp = strpos($sub, "\"");
if ($rpp !== false) {
$sub = substr($sub, 0, $rpp);
echo "attachment-filename: " . $sub . "\n";
if (property_exists($arval, 'body')) {
file_put_contents($sub, $arval->body);
}
}
}
}
}
}
//特殊コード等の加工をする
if (strlen($contents_body) > 0) {
$body_full = str_replace("\r\n", "\n", $contents_body); //改行コードの統一
$body_full = str_replace("\t", "", $body_full); //タブは切る
//ここで、PHP標準はUTF-8以外=JISをデコードしてくれないので、
//一旦ファイルに落とし、外部コマンドnkfで判別変換して読み込み
$fbtmp = "body_tmp.txt"; //$msg_id . ".txt";
$fbout = "body_out.txt"; //$msg_id . ".txt";
file_put_contents($fbtmp, $body_full);
if (strcmp(exec("nkf -g " . $fbtmp), "ISO-2022-JP") == 0) { //JIS
exec("nkf -J -w " . $fbtmp . " > " . $fbout); //UTF-8へ変換
$body_full = file_get_contents($fbout); //読み込み
}
echo $body_full;
}
メールの生データファイルを「testmail.eml」とすると、
php maildecode.php として解析実行。結果はまずオブジェクトがダンプされた後(添付があるので見苦しいが)、 <pre> date: Mon, 12 Oct 2020 19:13:51 +0900 date_seq: 1602497631 date_str: 2020-10-12 19:13:51 from: Note Server <servernote.net> from: servernote.net@gmail.com to: servernote.net@gmail.com to: servernote.net@gmail.com message-id: <ca> message-id: CA+LSZZyMBz4o80f24u=+qtEYGF=ghBRJHCrB6FhaE-tpXqr+eQ@mail.gmail.com references: in-reply-to: references+in-reply-to: subject: テストメールです(添付つき) attachment-filename: cloudy.jpg こんにちは。 テストメールを送ります。 サンプル画像も添付します。 ---- WEBMASTER </ca></servernote.net>のように出力され、カレントディレクトリに添付ファイル cloudy.jpg がファイル名とともに正しく保存されていればオールOK。 標準入力から読む訳であるので、メールエイリアスで直接このPHPを指定すれば、 届いたメールをそのまま解析することができる。
vi /etc/aliases hogeuser: "| /usr/local/bin/php /home/hogeuser/maildecode.php" newaliaseshogeuser@example1155.jp(仮称)にメールを送れば↑実行される。 ただしこの場合、maildecode.php内でカレントディレクトリをファイル保存可能な適切な場所に移動すること。
chdir( "/home/hogeuser" );
Android
iPhone/iPad
Flutter
MacOS
Windows
Debian
Ubuntu
CentOS
FreeBSD
RaspberryPI
HTML/CSS
C/C++
PHP
Java
JavaScript
Node.js
Swift
Python
MatLab
Amazon/AWS
CORESERVER
Google
仮想通貨
LINE
OpenAI/ChatGPT
IBM Watson
Microsoft Azure
Xcode
VMware
MySQL
PostgreSQL
Redis
Groonga
Git/GitHub
Apache
nginx
Postfix
SendGrid
Hackintosh
Hardware
Fate/Grand Order
ウマ娘
将棋
ドラレコ
※本記事は当サイト管理人の個人的な備忘録です。本記事の参照又は付随ソースコード利用後にいかなる損害が発生しても当サイト及び管理人は一切責任を負いません。
※本記事内容の無断転載を禁じます。
※本記事内容の無断転載を禁じます。
【WEBMASTER/管理人】
自営業プログラマーです。お仕事ください!ご連絡は以下アドレスまでお願いします★
【キーワード検索】
【最近の記事】【全部の記事】
LetsEncrypt/certbotの証明書自動更新がエラーになる場合Wav2Lipのオープンソース版を改造して外部から呼べるAPI化する
Wav2Lipのオープンソース版で静止画の口元のみを動かして喋らせる
【iOS】アプリアイコン・ロゴ画像の作成・設定方法
オープンソースリップシンクエンジンSadTalkerをAPI化してアプリから呼ぶ【2】
オープンソースリップシンクエンジンSadTalkerをAPI化してアプリから呼ぶ【1】
【Xcode】iPhone is not available because it is unpairedの対処法
【Let's Encrypt】Failed authorization procedure 503の対処法
【Debian】古いバージョンでapt updateしたら404 not foundでエラーになる場合
ファイアウォール内部のWindows11 PCにmacOS Sequoiaからリモートデスクトップする
【人気の記事】【全部の記事】
【Windows10】リモートデスクトップ間のコピー&ペーストができなくなった場合の対処法進研ゼミチャレンジタッチをAndroid端末化する
Windows11+WSL2でUbuntuを使う【2】ブリッジ接続+固定IPの設定
【Apache】サーバーに同時接続可能なクライアント数を調整する
Windows11のコマンドプロンプトでテキストをコピーする
Androidホームで左にスワイプすると出てくるニュース共を一切表示させない方法
【Linux】mkfsコマンドでProceed anyway?と確認を求められるのを回避する
VirtualBoxの仮想マシンをWindows起動時に自動起動し終了時に自動サスペンドする
タスクスケジューラで変更を適用できません。ユーザーアカウントが不明であるか、パスワードが正しくないか、またはユーザーアカウントにタスクを変更する許可がありません。と出た
Googleスプレッドシートを編集したら自動で更新日時を入れる
【カテゴリーリンク】
Android
iPhone/iPad
Flutter
MacOS
Windows
Debian
Ubuntu
CentOS
FreeBSD
RaspberryPI
HTML/CSS
C/C++
PHP
Java
JavaScript
Node.js
Swift
Python
MatLab
Amazon/AWS
CORESERVER
Google
仮想通貨
LINE
OpenAI/ChatGPT
IBM Watson
Microsoft Azure
Xcode
VMware
MySQL
PostgreSQL
Redis
Groonga
Git/GitHub
Apache
nginx
Postfix
SendGrid
Hackintosh
Hardware
Fate/Grand Order
ウマ娘
将棋
ドラレコ