33

我正在寻找使用 PHP 代码将原始电子邮件解析为部分的良好/工作/简单的方法。

我已经写了几个蛮力解决方案,但是每次都会出现一个小的变化/标题/空间/某些东西,我的整个解析器都会失败,项目就会崩溃。

在我被指向 PEAR/PECL 之前,我需要实际的代码。我的主机有一些古怪的配置或其他东西,我似乎永远无法正确构建 .so。如果我确实得到了 .so,路径/环境/php.ini 的一些差异并不总是使其可用(apache vs cron vs CLI)。

哦,还有最后一件事,我正在解析原始电子邮件文本,而不是 POP3,而不是 IMAP。它通过 .qmail 电子邮件重定向传送到 PHP 脚本中。

我不希望 SOF 为我编写它,我正在寻找一些“正确”的技巧/起点。这是我知道已经解决的那些“轮子”问题之一。

4

15 回答 15

23

你希望最后得到什么?正文、主题、发件人、附件?您应该花一些时间了解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 生成的电子邮件都将进行MIME编码。那可能是 uuencoded 文本,它可能是 html,它可能是一个 uuencoded excel 电子表格。

我希望这有助于为理解一些非常基本的电子邮件桶提供一个框架。如果您提供更多有关您尝试处理数据的背景信息,我(或其他人)可能会提供更好的指导。

于 2008-08-16T02:18:11.893 回答
19

试试 Plancake PHP 电子邮件解析器: https ://github.com/plancake/official-library-php-email-parser

我已经将它用于我的项目。它工作得很好,它只是一个类,它是开源的。

于 2011-05-15T21:56:52.790 回答
6

我把它拼凑在一起,一些代码不是我的,但我不知道它来自哪里......我后来采用了更强大的“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());
}
?>
于 2011-05-10T18:27:22.740 回答
3

您可以尝试使用 Mailparse 函数:http://php.net/manual/en/book.mailparse.php 但不在默认的 php conf 中。

于 2010-11-05T14:36:40.127 回答
2

Pear 库 Mail_mimeDecode 是用纯 PHP 编写的,您可以在此处查看:Mail_mimeDecode 源

于 2011-11-07T20:49:34.487 回答
2

有一个用于将原始电子邮件消息解析为 php 数组的库 - http://flourishlib.com/api/fMailbox#parseMessage

静态方法 parseMessage() 可用于将完整的 MIME 电子邮件解析为与 fetchMessage() 返回相同的格式,减去 uid 键。

$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
);
于 2015-07-02T13:51:57.273 回答
2

这个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
于 2016-11-23T12:34:43.630 回答
1

编写自己的 MIME 解析器可能不会有太多乐趣。您发现“过度开发的邮件处理包”的原因是因为 MIME 是一组非常复杂的规则/格式/编码。MIME 部分可以递归,这是乐趣的一部分。我认为您最好的选择是编写最好的 MIME 处理程序,解析消息,丢弃所有不是 text/plain 或 text/html 的内容,然后强制传入字符串中的命令以 COMMAND: 或类似内容作为前缀这样你就可以在泥土中找到它。如果你从这样的规则开始,你就有很大的机会处理新的提供者,但你应该准备好在新的提供者出现时进行调整(或者,如果你当前的提供者选择改变他们的消息传递架构)。

于 2008-08-16T16:06:14.577 回答
1

我不确定这是否会对您有所帮助——希望如此——但它肯定会帮助其他有兴趣了解更多关于电子邮件的人。Marcus Bointon在今年 3 月的 PHP 伦敦会议上做了题为“Mail() 和 Mail() 之后的生活”的最佳演讲之一,幻灯片MP3在线。他说话有一定的权威性,曾在电子邮件和 PHP 方面进行过深入的广泛工作。

我的看法是,您在尝试编写一个真正通用的解析器时遇到了一个痛苦的世界。

编辑 - PHP London 网站上的文件似乎已被删除;在 Marcus自己的网站上找到了幻灯片:第 1 部分第 2 部分虽然在任何地方都看不到 MP3

于 2008-08-16T18:19:41.810 回答
1

在 PHP 中解析电子邮件并非不可能完成的任务。我的意思是,你不需要一个工程师团队来做这件事;作为个人是可以实现的。我发现真正最困难的部分是创建 FSM 来解析 IMAP BODYSTRUCTURE 结果。我在 Internet 上的任何地方都没有看到这个,所以我自己写了。我的例程基本上从命令输出创建了一个嵌套数组的数组,数组中的深度大致对应于执行查找所需的部件号。所以它非常优雅地处理嵌套的 MIME 结构。

问题是 PHP 的默认 imap_* 函数没有提供太多粒度......所以我必须打开一个到 IMAP 端口的套接字并编写函数来发送和检索必要的信息(IMAP FETCH 1 BODY.PEEK[1.2]例如),这涉及查看 RFC 文档。

数据的编码(quoted-printable、base64、7bit、8bit等)、消息长度、content-type等都提供给您;对于附件、文本、html 等。您可能还必须弄清楚邮件服务器的细微差别,因为并非所有字段都始终 100% 实现。

宝石是 FSM……如果你有 Comp Sci 的背景,那么做这个真的很有趣(关键是括号不是常规语法;));否则,使用传统方法将是一场斗争和/或导致丑陋的代码。你也需要一些时间!

希望这可以帮助!

于 2010-11-20T20:49:47.417 回答
0

是的,我已经能够根据 rfc 和其他一些基本教程编写一个基本的解析器。但它的多部分 mime 嵌套边界一直让我感到困惑。

我发现从我的手机发送的彩信(不是短信)只是标准电子邮件,所以我有一个系统可以读取传入的电子邮件,检查发件人(只允许来自我的手机),并使用正文部分运行不同的我的服务器上的命令。它有点像通过电子邮件进行的远程控制。

因为该系统是为发送图片而设计的,所以它有一堆不同编码的部分。一个 mms.smil.txt 部分,一个 text/plain(没用,只是说“这是一条 html 消息”),一个 application/smil 部分(手机会在上面拍照的部分),一个 text/html 部分有我的运营商的广告,然后是我的消息,但全部用 html 包装,最后是带有我的纯消息的文本文件附件(这是我使用的部分)(如果我将图像作为附件推到消息中,则放在附件 1,base64 编码,然后我的文本部分作为附件 2 附加)

我让它与我的运营商的确切邮件格式一起工作,但是当我通过它从别人的电话中运行一条消息时,它以一大堆悲惨的方式失败了。

我有其他项目我想将此电话->邮件->解析->命令系统扩展到,但我需要有一个稳定/可靠/通用的解析器来从邮件中取出不同的部分来使用它。

我的最终目标是拥有一个函数,我可以将原始管道邮件输入,并取回一个大数组,其中包含标题 var:val 对的关联子数组,一个用于将正文文本作为整个字符串

我在这方面搜索得越多,我就越发现相同的东西:巨大的过度开发的邮件处理包,它们可以在阳光下完成与邮件相关的所有事情,或者无用的(对我来说,在这个项目中)教程。

我想我将不得不咬紧牙关,认真地写一些我自己的东西。

于 2008-08-16T05:09:11.907 回答
0

这个库工作得很好:

http://www.phpclasses.org/package/3169-PHP-Decode-MIME-e-mail-messages.html

于 2011-11-08T21:27:33.653 回答
0

我遇到了同样的问题,所以我编写了以下类:Email_Parser。它接收原始电子邮件并将其变成一个不错的对象。

它需要 PEAR Mail_mimeDecode,但应该很容易通过 WHM 或直接从命令行安装。

在这里获取:https ://github.com/optimumweb/php-email-reader-parser

于 2015-10-23T21:47:40.823 回答
0

简单的 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);
于 2017-11-04T12:15:35.260 回答
0

如果您尝试从 Docker 容器执行此操作,请在构建时使用 PEAR 安装 Mail 和 Mail_mimeDecode。

FROM php:7.4-apache
WORKDIR /var/www/html
EXPOSE 80
WORKDIR /var/www
RUN chown -R www-data html
RUN docker-php-ext-install mysqli
RUN pear install --alldeps mail
RUN pear install Mail_mimeDecode

然后在您的 PHP 代码中,如下所示:

<?php

require_once "/usr/local/lib/php/Mail.php";
require_once "/usr/local/lib/php/Mail/mimeDecode.php";

$mailfiles = ['/var/www/mail/mailFile1','/var/www/mail/mailFile2'];

foreach($mailfiles as $filename){
    $theFile = fopen($filename, "r") or die("Unable to open file!");
    $rawEmail = fread($theFile, filesize($filename));
    fclose($theFile);

    $args = [];
    $args['include_bodies'] = true;
    $args['decode_bodies'] = FALSE;
    $args['decode_headers'] = FALSE;
    $objMail = new Mail_mimeDecode($rawEmail);
    $return = $objMail->decode($args);

    if (PEAR::isError($return)) {
        echo("<p>" . $return->getMessage() . "</p>");
        var_dump($return);
    } else {
        //echo("No error in PEAR::isError(return)");
    }

    if($return->body){
        $decoded = base64_decode($return->body, true);
        var_dump($decoded);
    }//end if(body)

}//end foreach(mailfiles as file)


?>
于 2021-06-10T15:06:19.537 回答