質問
生の電子メールをパーツに解析するための、優れた/機能する/使いやすいPHPコードを探しています。
私は強引な解決策をいくつか書いてきましたが、毎回、小さな変更/ヘッダ/スペース/何かが発生し、パーサー全体が失敗し、プロジェクトが崩壊します。
PEAR/PECL について説明する前に、実際のコードが必要です。私のホストには設定がおかしいか何かがあり、.so を正しくビルドすることができないようです。.so を作成したとしても、path/environment/php.ini に何らかの違いがあるため、常に利用できるとは限りません (Apache、cron、cli)。
ああ、最後にもう 1 つ、私は POP3 や IMAP ではなく、生の電子メール テキストを解析しています。これは、.qmail 電子メール リダイレクトを介して php スクリプトにパイプされます。
私は SOF が私に代わってそれを書いてくれることを期待していません。私はそれを「正しく」行うためのヒントや出発点を探しています。これは、すでに解決されていると私が知っている「車輪」の問題の 1 つです。
解決
最後に何が起こることを期待していますか?本文、件名、送信者、添付ファイル?一緒に時間を過ごしたほうがいいよ RFC2822 メールの形式を理解する必要がありますが、適切な形式のメールの最も簡単なルールを次に示します。
HEADERS\n
\n
BODY
つまり、最初の空行 (二重改行) は HEADERS と BODY の間の区切り文字です。ヘッダーは次のようになります。
HSTRING:HTEXT
HSTRING は常に行の先頭から始まり、空白やコロンは含まれません。HTEXT には、改行文字の後に空白が続く限り、改行を含むさまざまなテキストを含めることができます。
「BODY」は実際には、最初の二重改行に続く任意のデータです。(SMTP 経由でメールを送信する場合は別のルールがありますが、パイプ経由で処理する場合はそのことを心配する必要はありません)。
非常に簡単に言うと、1982 年頃 RFC822 電子メールは次のようになります。
HEADER: HEADER TEXT
HEADER: MORE HEADER TEXT
INCLUDING A LINE CONTINUATION
HEADER: LAST HEADER
THIS IS ANY
ARBITRARY DATA
(FOR THE MOST PART)
ただし、最近の電子メールのほとんどはそれよりも複雑です。ヘッダーは文字セットまたは RFC2047 マイムの言葉や、今は考えていない他のことがたくさんあります。ボディに意味を持たせたい場合、最近ではボディに独自のコードを組み込むのは非常に困難です。MUA によって生成されるほぼすべての電子メールは、 マイム エンコードされた。それは、uuencode されたテキスト、html、uuencode された Excel スプレッドシートである可能性があります。
これが、電子メールの非常に基本的な部分を理解するためのフレームワークを提供するのに役立つことを願っています。データを使って何をしようとしているのかについて、より詳しい背景を提供していただければ、私 (または他の誰か) がより良い方向性を提供できるかもしれません。
他のヒント
Plancake PHP 電子メール パーサーを試してください。https://github.com/plancake/official-library-php-email-parser
私は自分のプロジェクトにそれを使用しました。これは非常にうまく機能し、単なる 1 つのクラスであり、オープンソースです。
これをまとめてみました。一部のコードは私のものではありませんが、どこから来たのかわかりません...その後、より堅牢な「MimeMailParser」を採用しましたが、これはうまく機能します。cPanel を使用してデフォルトの電子メールをそれにパイプすると、うまく機能します。
#!/usr/bin/php -q
<?php
// Config
$dbuser = 'emlusr';
$dbpass = 'pass';
$dbname = 'email';
$dbhost = 'localhost';
$notify= 'services@.com'; // an email address required in case of errors
function mailRead($iKlimit = "")
{
// Purpose:
// Reads piped mail from STDIN
//
// Arguements:
// $iKlimit (integer, optional): specifies after how many kilobytes reading of mail should stop
// Defaults to 1024k if no value is specified
// A value of -1 will cause reading to continue until the entire message has been read
//
// Return value:
// A string containing the entire email, headers, body and all.
// Variable perparation
// Set default limit of 1024k if no limit has been specified
if ($iKlimit == "") {
$iKlimit = 1024;
}
// Error strings
$sErrorSTDINFail = "Error - failed to read mail from STDIN!";
// Attempt to connect to STDIN
$fp = fopen("php://stdin", "r");
// Failed to connect to STDIN? (shouldn't really happen)
if (!$fp) {
echo $sErrorSTDINFail;
exit();
}
// Create empty string for storing message
$sEmail = "";
// Read message up until limit (if any)
if ($iKlimit == -1) {
while (!feof($fp)) {
$sEmail .= fread($fp, 1024);
}
} else {
while (!feof($fp) && $i_limit < $iKlimit) {
$sEmail .= fread($fp, 1024);
$i_limit++;
}
}
// Close connection to STDIN
fclose($fp);
// Return message
return $sEmail;
}
$email = mailRead();
// handle email
$lines = explode("\n", $email);
// empty vars
$from = "";
$subject = "";
$headers = "";
$message = "";
$splittingheaders = true;
for ($i=0; $i < count($lines); $i++) {
if ($splittingheaders) {
// this is a header
$headers .= $lines[$i]."\n";
// look out for special headers
if (preg_match("/^Subject: (.*)/", $lines[$i], $matches)) {
$subject = $matches[1];
}
if (preg_match("/^From: (.*)/", $lines[$i], $matches)) {
$from = $matches[1];
}
if (preg_match("/^To: (.*)/", $lines[$i], $matches)) {
$to = $matches[1];
}
} else {
// not a header, but message
$message .= $lines[$i]."\n";
}
if (trim($lines[$i])=="") {
// empty line, header section has ended
$splittingheaders = false;
}
}
if ($conn = @mysql_connect($dbhost,$dbuser,$dbpass)) {
if(!@mysql_select_db($dbname,$conn))
mail($email,'Email Logger Error',"There was an error selecting the email logger database.\n\n".mysql_error());
$from = mysql_real_escape_string($from);
$to = mysql_real_escape_string($to);
$subject = mysql_real_escape_string($subject);
$headers = mysql_real_escape_string($headers);
$message = mysql_real_escape_string($message);
$email = mysql_real_escape_string($email);
$result = @mysql_query("INSERT INTO email_log (`to`,`from`,`subject`,`headers`,`message`,`source`) VALUES('$to','$from','$subject','$headers','$message','$email')");
if (mysql_affected_rows() == 0)
mail($notify,'Email Logger Error',"There was an error inserting into the email logger database.\n\n".mysql_error());
} else {
mail($notify,'Email Logger Error',"There was an error connecting the email logger database.\n\n".mysql_error());
}
?>
試してみることができる Mailparse 関数があります。 http://php.net/manual/en/book.mailparse.php, ただし、デフォルトのphp confにはありません。
生の電子メールメッセージを解析してphp配列にするためのライブラリがあります - http://flourishlib.com/api/fMailbox#parseMessage.
静的メソッドparsemessage()を使用して、完全なmimeメールメッセージをfetchmessage()が返すのと同じ形式に解析できます。
$ parsed_message = fmailbox :: parsemessage(file_get_contents( '/path/to/email'));
解析されたメッセージの例を次に示します。
array(
'received' => '28 Apr 2010 22:00:38 -0400',
'headers' => array(
'received' => array(
0 => '(qmail 25838 invoked from network); 28 Apr 2010 22:00:38 -0400',
1 => 'from example.com (HELO ?192.168.10.2?) (example) by example.com with (DHE-RSA-AES256-SHA encrypted) SMTP; 28 Apr 2010 22:00:38 -0400'
),
'message-id' => '<4BD8E815.1050209@flourishlib.com>',
'date' => 'Wed, 28 Apr 2010 21:59:49 -0400',
'from' => array(
'personal' => 'Will Bond',
'mailbox' => 'tests',
'host' => 'flourishlib.com'
),
'user-agent' => 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.9) Gecko/20100317 Thunderbird/3.0.4',
'mime-version' => '1.0',
'to' => array(
0 => array(
'mailbox' => 'tests',
'host' => 'flourishlib.com'
)
),
'subject' => 'This message is encrypted'
),
'text' => 'This message is encrypted',
'decrypted' => TRUE,
'uid' => 15
);
これ https://github.com/zbateson/MailMimeParser 私にとっては機能し、mailparse拡張機能は必要ありません。
<?php
echo $message->getHeaderValue('from'); // user@example.com
echo $message
->getHeader('from')
->getPersonName(); // Person Name
echo $message->getHeaderValue('subject'); // The email's subject
echo $message->getTextContent(); // or getHtmlContent
Pear lib Mail_mimeDecode はプレーンな PHP で記述されており、ここで見ることができます。 Mail_mimeDecode ソース
おそらく、独自の MIME パーサーを作成するのはそれほど楽しいものではないでしょう。「過剰に開発されたメール処理パッケージ」が見つかった理由は、MIME が非常に複雑なルール/形式/エンコーディングのセットであるためです。MIME パーツは再帰的に使用できますが、これも楽しみの 1 つです。最善の策は、できる限り最高の MIME ハンドラーを作成し、メッセージを解析し、text/plain または text/html 以外のものをすべて破棄して、受信文字列のコマンドに COMMAND というプレフィックスを強制的に付けることだと思います。または同様のものを使用して、泥の中で見つけられるようにします。このようなルールで開始すると、新しいプロバイダーを処理できる可能性は十分にありますが、新しいプロバイダーが登場した場合 (または、現在のプロバイダーがメッセージング アーキテクチャを変更することを選択した場合) に調整する準備をしておく必要があります。
PHP で電子メールを解析することは不可能な作業ではありません。私が言いたいのは、それを行うのにエンジニアのチームは必要ないということです。それは個人として達成可能です。実際、私が見つけた最も難しい部分は、IMAP BODYSTRUCTURE 結果を解析するための FSM を作成することでした。インターネットのどこにもこれを見たことがなかったので、自分で書きました。私のルーチンは基本的に、コマンド出力からネストされた配列の配列を作成します。配列内の深さは、ルックアップの実行に必要な部品番号にほぼ対応します。したがって、ネストされた MIME 構造を非常に適切に処理します。
問題は、PHP のデフォルトの imap_* 関数では粒度があまり高くないことです。そのため、IMAP ポートへのソケットを開いて、必要な情報を送信および取得する関数を記述する必要がありました (IMAP FETCH 1 BODY.PEEK[1.2])たとえば)、これには RFC ドキュメントを参照する必要があります。
データのエンコーディング (quoted-printable、base64、7 ビット、8 ビットなど)、メッセージの長さ、コンテンツ タイプなど。すべてあなたに提供されます。添付ファイル、テキスト、HTML など。すべてのフィールドが常に 100% 実装されるわけではないため、メール サーバーの微妙な違いも理解する必要がある場合があります。
宝石は FSM です...コンプ サイエンスのバックグラウンドがある場合、これを作成するのは本当に楽しいでしょう (重要なのは、括弧が通常の文法ではないということです ;));そうしないと、従来の方法を使用すると、苦労したり、醜いコードが作成されたりすることになります。また、少し時間が必要です!
お役に立てれば!
これがあなたにとって役立つかどうかはわかりませんが、電子メールについて詳しく知りたい人にとっては間違いなく役立ちます。 マーカス・ボイントン 今年 3 月の PHP ロンドンカンファレンスで、「Mail() と Mail() 後の生活」というタイトルの最高のプレゼンテーションの 1 つを行いました。 スライド そして MP3 オンラインです。彼は、電子メールと PHP を深いレベルで幅広く扱ってきたため、ある程度の権威を持って話します。
私の認識では、真に汎用的なパーサーを作成しようとすると、大変な苦労を強いられることになります。
編集 - ファイルは PHP London サイトから削除されたようです。マーカスのスライドを見つけた 自分のサイト: パート1 パート2 MP3はどこにも見当たりませんでしたが
はい、rfc とその他の基本的なチュートリアルに基づいて、基本的なパーサーを作成できました。しかし、マルチパート MIME のネストされた境界が私を混乱させ続けています。
携帯電話から送信される MMS (SMS ではない) メッセージは単なる標準メールであることがわかったので、受信メールを読み取り、差出人をチェックし (携帯電話からのみ許可するため)、本文部分を使用して別のメールを実行するシステムを持っています。私のサーバー上のコマンド。電子メールによるリモコンのようなものです。
このシステムは画像を送信するように設計されているため、さまざまにエンコードされた部分が多数含まれています。mms.smil.txt 部分、text/plain (役に立たない、「これは HTML メッセージです」と表示されるだけ)、application/smil 部分 (電話が認識する部分)、text/html 部分私のキャリアの広告、次に私のメッセージが含まれますが、すべてHTMLでラップされ、最後にテキストファイルの添付ファイルに私のプレーンメッセージ(これが私が使用する部分です)が含まれます(メッセージに添付ファイルとして画像を押し込むと、その場所に配置されます)添付ファイル 1、base64 エンコード、その後、私のテキスト部分が添付ファイル 2 として添付されます)
私はキャリアからの正確なメール形式でそれを動作させていましたが、他の人の携帯電話からのメッセージをそれを介して実行すると、悲惨な方法で失敗しました。
この電話->メール->解析->コマンドシステムを拡張したい他のプロジェクトがありますが、それを使用するにはメールからさまざまな部分を取得するための安定した/固体/汎用パーサーが必要です。
私の最終的な目標は、生のパイプされたメールをフィードして、ヘッダーの var:val ペアの連想サブ配列を含む大きな配列と、文字列全体としての本文テキスト用の大きな配列を返す関数を用意することです。
これについて検索すればするほど、同じことが見つかります。巨大に開発されすぎたメール処理パッケージは、メールに関連することをすべて実行するもので、(このプロジェクトでは私にとっては)役に立たないチュートリアルです。
覚悟を決めて、自分なりに慎重に何かを書く必要があると思います。
このライブラリは非常にうまく機能します。
http://www.phpclasses.org/package/3169-PHP-Decode-MIME-e-mail-messages.html
同じ問題に遭遇したので、次のクラスを作成しました。電子メール_パーサー。生の電子メールを受け取り、それを優れたオブジェクトに変換します。
PEAR Mail_mimeDecode が必要ですが、WHM 経由またはコマンドラインから直接インストールするのは簡単です。
ここから入手してください: https://github.com/optimumweb/php-email-reader-parser
シンプルな PhpMimeParser https://github.com/breakermind/PhpMimeParser Yuo はファイルや文字列から MIME メッセージを切り取ることができます。ファイル、HTML、インライン画像を取得します。
$str = file_get_contents('mime-mixed-related-alternative.eml');
// MimeParser
$m = new PhpMimeParser($str);
// Emails
print_r($m->mTo);
print_r($m->mFrom);
// Message
echo $m->mSubject;
echo $m->mHtml;
echo $m->mText;
// Attachments and inline images
print_r($m->mFiles);
print_r($m->mInlineList);