アプリケーション開発ポータルサイト
ServerNote.NET
Amazon.co.jpでPC関連商品タイムセール開催中!
カテゴリー【PHPDebianPostfix
PHPでメールの中身をフルに分解して出力する(日本語&添付ファイル対応)
POSTED BY
2023-10-12

PEAR::Mail_mimeDecodeを使う。準備編はこちら

標準入力からメールデータを読み込み、From,To,件名等各種ヘッダおよび本文を表示し添付ファイルを保存するサンプル。JIS文字コード本文に対応するため外部コマンドnkfを使っているので、それは入れておく。

apt install nkf

テストメールは、以下のような感じでGmailで作成し自分に送信。

Subject: テストメールです(添付つき)
From: Note Server <servernote.net@gmail.com>
To: servernote.net@gmail.com

こんにちは。
テストメールを送ります。
サンプル画像も添付します。
----
WEBMASTER

添付ファイルは、以下のサンプル雲画像cloudy.jpgを添付。

受信側に送られて来るメールの生データは以下のような感じである。

sourcetestmail.emlGitHub 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--

以下、本記事のメイン、解析プログラムはこちら。

PHPmaildecode.phpGitHub 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 < testmail.eml

として解析実行。結果はまずオブジェクトがダンプされた後(添付があるので見苦しいが)、

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@gmail.com>
from: servernote.net@gmail.com
to: servernote.net@gmail.com
to: servernote.net@gmail.com
message-id: <CA+LSZZyMBz4o80f24u=+qtEYGF=ghBRJHCrB6FhaE-tpXqr+eQ@mail.gmail.com>
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

のように出力され、カレントディレクトリに添付ファイル
cloudy.jpg
がファイル名とともに正しく保存されていればオールOK。

標準入力から読む訳であるので、メールエイリアスで直接このPHPを指定すれば、
届いたメールをそのまま解析することができる。

vi /etc/aliases

hogeuser: "| /usr/local/bin/php /home/hogeuser/maildecode.php"

newaliases

hogeuser@example1155.jp(仮称)にメールを送れば↑実行される。
ただしこの場合、maildecode.php内でカレントディレクトリをファイル保存可能な適切な場所に移動すること。

chdir( "/home/hogeuser" );
※本記事は当サイト管理人の個人的な備忘録です。本記事の参照又は付随ソースコード利用後にいかなる損害が発生しても当サイト及び管理人は一切責任を負いません。
※本記事内容の無断転載を禁じます。
【WEBMASTER/管理人】
自営業プログラマーです。お仕事ください!
ご連絡は以下アドレスまでお願いします★

☆ServerNote.NETショッピング↓
ShoppingNote / Amazon.co.jp
☆お仲間ブログ↓
一人社長の不動産業務日誌
【キーワード検索】