在php中解析原始电子邮件

时间:2008-08-15 23:50:18

标签: php email

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

我写了几个暴力解决方案,但每次都会出现一个小的更改/标题/空格/一些东西,我的整个解析器都会失败并且项目会崩溃。

在我指向PEAR / PECL之前,我需要实际的代码。我的主机有一些棘手的配置或东西,我似乎永远不会得到.so的构建正确。如果我确实得到了.so,那么path / environment / php.ini中的一些区别并不总是可用(apache vs cron vs cli)。

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

我不期待SOF为我写它,我正在寻找一些提示/起点“正确”。这是我所知道的那些“轮子”问题之一已经解决了。

15 个答案:

答案 0 :(得分:21)

你最终希望最终得到什么?身体,主体,发件人,附件?您应该花一些时间与RFC2822一起了解邮件的格式,但这是最简单的电子邮件规则:

HEADERS\n
\n
BODY

也就是说,第一个空白行(双换行符)是HEADERS和BODY之间的分隔符。 HEADER看起来像这样:

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)

大多数现代电子邮件比这更复杂。标题可以编码为charsets或RFC2047 mime字,或者其他很多我现在没想到的东西。如果您希望它们有意义,那么这些天的机构真的很难推出自己的代码。几乎所有由MUA生成的电子邮件都将MIME编码。这可能是uuencoded文本,它可能是html,它可能是一个uuencoded excel电子表格。

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

答案 1 :(得分:18)

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

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

答案 2 :(得分:4)

我拼凑了这个,有些代码不是我的,但我不知道它来自哪里......我后来采用了更强大的“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());
}
?>

答案 3 :(得分:2)

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

答案 4 :(得分:2)

Pear lib Mail_mimeDecode是用纯PHP编写的,你可以在这里看到:Mail_mimeDecode source

答案 5 :(得分:2)

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

  

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

     

$ parsed_message =   fMailbox :: parseMessage(的file_get_contents( '/路径/到/电子邮件'));

     

以下是已解析消息的示例:

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
);

答案 6 :(得分: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

答案 7 :(得分:1)

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

答案 8 :(得分:1)

我不确定这对您是否有帮助 - 希望如此 - 但它肯定会帮助其他有兴趣了解电子邮件的人。 Marcus Bointon在今年3月举行的PHP伦敦会议上做了一个名为“邮件()和邮件后生活”的最佳演讲之一,slidesMP3在线。他在某种程度上说话,在深层次上与电子邮件和PHP进行了广泛的合作。

我的看法是,你正试图编写一个真正通用的解析器。

编辑 - 这些文件似乎已在PHP伦敦站点上删除;发现Marcus上的幻灯片“own sitePart 1 Part 2无法在任何地方看到MP3

答案 9 :(得分:1)

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

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

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

宝石是FSM ...如果你在Comp Sci中有背景,那么制作它真的很有趣(它们的关键是括号不是常规语法;));否则,使用传统方法将是一场挣扎和/或导致丑陋的代码。你还需要一些时间!

希望这有帮助!

答案 10 :(得分:0)

是的,我已经能够编写一个基本的解析器,基于rfc和其他一些基础教程。但它的多部分mime嵌套边界不断弄乱我。

我发现从我的手机发送的MMS(不是短信)消息只是标准的电子邮件,因此我有一个系统可以读取收到的电子邮件,检查来自(仅允许来自我的手机),并使用正文部分在我的服务器上运行不同的命令。它有点像电子邮件的遥控器。

因为系统设计用于发送图片,所以它有一堆不同编码的部分。一个mms.smil.txt部分,一个text / plain(这是没用的,只是说'这是一个html消息'),一个应用程序/ smil部分(手机部分将会拍摄),text / html部分我的载体广告,然后我的消息,但所有包装在HTML,然后最终一个文本文件附件与我的简单消息(这是我使用的部分)(如果我将图像作为附件在邮件中推,它放在附件1,base64编码,然后我的文本部分附加为附件2)

我让它使用我的运营商提供的确切邮件格式,但是当我通过它从别人手机上发送消息时,它以一堆悲惨的方式失败了。

我有其他项目我想扩展这个手机 - &gt; mail-&gt; parse-&gt;命令系统,但我需要一个稳定的/实体/通用解析器来获取不同的部分邮件使用它。

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

我越来越多地搜索到这一点,我越发现同样的事情:巨大的过度开发的邮件处理软件包可以在太阳下完成与邮件相关的所有事情,或者对我(在我这个项目中)无用的教程。

我想我必须咬紧牙关,小心翼翼地写下自己的东西。

答案 11 :(得分:0)

答案 12 :(得分:0)

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

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

在此处获取:https://github.com/optimumweb/php-email-reader-parser

答案 13 :(得分: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);

答案 14 :(得分: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)


?>