【问题标题】:Multipart messages including multiple attachments ("attachment" and "inline") with Zend Mail – RFC-compliant?Zend Mail 的多部分消息,包括多个附件(“附件”和“内联”) - 符合 RFC?
【发布时间】:2013-10-30 03:08:55
【问题描述】:

我们公司基于 Zend(版本 1.8.4)开发了自己的 CMS。暂时无法切换到新版本。

我们正在使用 Zend Mail 发送带有嵌入图像 (Content-Disposition: inline;) 和可下载附件 (Content-Disposition: attachment;) 的(多部分)消息。

几天前,一位客户报告在他的 Apple iPhone 5(内部邮件客户端)上打开此类邮件时出现问题:在收件箱中,邮件确实标有一个符号,表示邮件有附件。但是,打开邮件后,附件是不可见的。当前版本的 Outlook、Thunderbird 和各种网络邮件客户端不存在该问题。

我通过根据附件的存在更改邮件的 Content-Type 来解决问题:

  • 邮件包含嵌入的图像和可下载的附件:Content-Type: multipart/mixed;
  • 邮件包含嵌入图像但没有可下载的附件:Content-Type: multipart/related;

我还必须更改 Zend/Mail/Transport/Abstract.php 中的函数 _buildBody 关于不同部分的边界组装。

所以,我想知道 Zend Mail 是否发送不符合 RFC 的邮件。

这是添加我的更改之前(不适用于 Apple Mail)和之后(适用于大多数常见邮件客户端)的邮件结构。您能告诉我哪个版本符合 RFC 标准吗?

Zend Mail 标准结构(不适用于 Apple Mail):

Content-Type: multipart/related; charset="utf-8"; boundary="=_0a0dbd2691e7728ea0f689fba0366bed"
MIME-Version: 1.0

--=_0a0dbd2691e7728ea0f689fba0366bed
Content-Type: multipart/alternative; boundary="=_a70ea5862a6842785870a9a4d003a2a7"
Content-Transfer-Encoding: 8bit

--=_a70ea5862a6842785870a9a4d003a2a7
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

[MAIL_TEXT]

--=_a70ea5862a6842785870a9a4d003a2a7
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: quoted-printable

[MAIL_HTML]

--=_a70ea5862a6842785870a9a4d003a2a7--

--=_0a0dbd2691e7728ea0f689fba0366bed
Content-Type: application/pdf
Content-Transfer-Encoding: base64
Content-ID: <test.pdf>
Content-Disposition: attachment; filename="test.pdf"

[PDF_ATTACHED]

Content-Type: image/jpeg
Content-Transfer-Encoding: base64
Content-ID: <test.jpg>
Content-Disposition: inline; filename="test.jpg"

[IMAGE_EMBEDDED]

--=_0a0dbd2691e7728ea0f689fba0366bed--

Zend Mail 自定义结构(在最常见的邮件客户端中工作):

Content-Type: multipart/mixed; charset="utf-8"; boundary="=_8ab337ec2e38e1a8b82a01a5712a8bdb"
MIME-Version: 1.0

--=_8ab337ec2e38e1a8b82a01a5712a8bdb
Content-Type: multipart/related; boundary="=_HTML60dd2cb7fc955f6c8a626c92c76aa2db"
Content-Transfer-Encoding: 8bit

--=_HTML60dd2cb7fc955f6c8a626c92c76aa2db
Content-Type: multipart/alternative; boundary="=_ALTd40db860af4718399b954c403d0b0557"
Content-Transfer-Encoding: 8bit

--=_ALTd40db860af4718399b954c403d0b0557
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

[MAIL_TEXT]

--=_ALTd40db860af4718399b954c403d0b0557
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: quoted-printable

[MAIL_HTML]

--=_ALTd40db860af4718399b954c403d0b0557--

--=_HTML60dd2cb7fc955f6c8a626c92c76aa2db
Content-Type: image/jpeg
Content-Transfer-Encoding: base64
Content-ID: <gemeinschaft.jpg>
Content-Disposition: inline; filename="gemeinschaft.jpg"

[IMAGE_EMBEDDED]

--=_HTML60dd2cb7fc955f6c8a626c92c76aa2db--

--=_8ab337ec2e38e1a8b82a01a5712a8bdb
Content-Type: application/pdf
Content-Transfer-Encoding: base64
Content-ID: <glamus-test-schnellwarnung.pdf>
Content-Disposition: attachment; filename="glamus-test-schnellwarnung.pdf"

[PDF_ATTACHED]

--=_8ab337ec2e38e1a8b82a01a5712a8bdb--

感谢任何帮助。

亲切的问候,

尼尔斯

【问题讨论】:

    标签: email zend-framework attachment multipart rfc


    【解决方案1】:

    我没有在 ZF1 中使用多部分消息,但我认为这应该通过嵌套消息部分来控制。根据您的描述(并假设您始终希望 PDF 显示为附件),您想要的是:

    multipart/mixed
        multipart/alternative
            text/plain
            multipart/related
                text/html
                image/jpeg
        application/pdf
    

    因此,您的消息包含两个不相关的部分(多部分/混合),即消息和附加的 PDF。该消息包含同一事物的两个版本(多部分/替代),这些版本是文本版本和多部分/相关版本。多部分相关版本包含 HTML 消息及其内联图像。

    如果我正确阅读了您的边界(并且似乎缺少一个),那么您目前拥有的是:

    multipart/related
        multipart/alternative
            text/plain
            text/html
        application/pdf
        image/jpeg
    

    因此,严格的邮件客户端忽略 PDF 是有意义的,因为标题(多部分/相关)表明它是主要消息的一部分(并且在其中被内联引用)。

    很难在没有看到的情况下建议是否可以在您的代码中修复它,但希望这会为您指明正确的方向。

    (顺便说一句,这在不支持嵌套多部分消息的 ZF2 中绝对不可能。)

    【讨论】:

      【解决方案2】:

      这条消息似乎有点过时了,但我知道这个问题,因为我驱动一个更大的应用程序,深深植根于 zend FW 1。不幸的是,Zend_Mail 子系统在我看来在某些地方有点未开发。 我认为像swiftmailer 这样的 php 库比 ZF 1.12 甚至 ZF2 做得更好。 因此,如果不能将您的邮件处理切换到 swiftmailer 并且并行使用 ZF 1/ ZF 2,这似乎可以解决这个问题,但由于某些原因,您不希望像我一样被困在 ZF1 中。

      我发现就像 Tim 发布的那样,您的多部分消息的 mime 部分似乎是错误的顺序和包含关系。我找到了这个订单/包含

      multipart/mixed
      multipart/alternative
          text/plain
          multipart/related
              text/html
              image/jpeg
      application/pdf
      

      将执行符合 RFC 的工作并将内联图像与其他附件正确集成。但遗憾的是,您无法使用这种结构构建消息 在 ZF 1 中。

      不可能的原因是ZF 1(v1.12)文件中的这部分代码 Zend/Mail/Transport/Abstract.php

      protected function _buildBody()
      {
          if (($text = $this->_mail->getBodyText())
              && ($html = $this->_mail->getBodyHtml()))
          {
              // Generate unique boundary for multipart/alternative
              $mime = new Zend_Mime(null);
              $boundaryLine = $mime->boundaryLine($this->EOL);
              $boundaryEnd  = $mime->mimeEnd($this->EOL);
      
              $text->disposition = false;
              $html->disposition = false;
      
              **$body = $boundaryLine
                    . $text->getHeaders($this->EOL)
                    . $this->EOL
                    . $text->getContent($this->EOL)
                    . $this->EOL
                    . $boundaryLine
                    . $html->getHeaders($this->EOL)
                    . $this->EOL
                    . $html->getContent($this->EOL)
                    . $this->EOL
                    . $boundaryEnd;**
      
              $mp           = new Zend_Mime_Part($body);
              $mp->type     = Zend_Mime::MULTIPART_ALTERNATIVE;
              $mp->boundary = $mime->boundary();
      
              $this->_isMultipart = true;
      
              // Ensure first part contains text alternatives
              array_unshift($this->_parts, $mp);
      
              // Get headers
              $this->_headers = $this->_mail->getHeaders();
              return;
          }
      
          // If not multipart, then get the body
          if (false !== ($body = $this->_mail->getBodyHtml())) {
              array_unshift($this->_parts, $body);
          } elseif (false !== ($body = $this->_mail->getBodyText())) {
              array_unshift($this->_parts, $body);
          } 
      
          **if (!$body) {
              /**    
               * @see Zend_Mail_Transport_Exception
               */    
              require_once 'Zend/Mail/Transport/Exception.php';
              throw new Zend_Mail_Transport_Exception('No body specified');
          }**
      

      这个例程在发送之前组装你的 Zend 多部分邮件消息,并且做了两件没有好处的事情。 1. 是它强制您的正文 html/plan 文本消息采用严格的形式,正如您在 $body 组装的部分中看到的那样。所以没有办法得到 您在此紧身胸衣中的多部分/相关部分

                    . $boundaryLine
                    . $html->getHeaders($this->EOL)
                    . $this->EOL
                    . $html->getContent($this->EOL)
                    . $this->EOL
                    . $boundaryEnd;
      

      因为您在 Zend_Mail 中操作 html 正文部分的唯一方法不允许您使用所需的多部分 mime 而不是空白 html 文本:

      setBodyHtml(string $html, string $charset = null, string $encoding = \Zend_Mime::ENCODING_QUOTEDPRINTABLE) : 
      

      因此,人们可能会考虑为自己手动做一些事情并从单个部分组装完整的多部分邮件(Zend_Mime_Part 和 Zend_Mime_Message 用于此目的)。

      这里我们遇到了第二个问题,或者 ZF 可能会将其视为功能,我不知道。 但是例程中的部分贴出来了

      if (!$body) {
              /**    
               * @see Zend_Mail_Transport_Exception
               */    
              require_once 'Zend/Mail/Transport/Exception.php';
              throw new Zend_Mail_Transport_Exception('No body specified');
      

      禁止在未使用 Zend_Mail::setBodyHtml 和 Zend_Mail::setBodyText 调用的情况下进行多部分邮件配置。 (在这种情况下 $body 将是空的)如果它们没有设置,将抛出一个错误,并且所有您手动添加的准确组装消息的 Mime_Parts,使用 Zend_Mail::addPart(Zend_Mime_Part) 添加将被简单地忽略。

      要解决这个问题,您必须更改绘图例程的行为以允许多部分消息而不使用 setBodyHtml/setBodyText,如下所示:

      if (!$body) {
              // this will probably only happen in multipart case 
              // where we need to assemble manually ..
              $this->_isMultipart = true;
               // set our manual headers :
              $this->_headers = $this->_mail->getHeaders();
              return; 
      
              /**    
               * @see Zend_Mail_Transport_Exception
               */    
              //require_once 'Zend/Mail/Transport/Exception.php';
              //throw new Zend_Mail_Transport_Exception('No body specified');
          }
      

      在修改 ZF1 代码(取自 v.1.12 Zend/Mail/Transport/Abstract.php)之后,您可以构建 具有您自己的结构的消息。

      我会给你一个例子来说明多部分消息的发布定义,内嵌图像和其他一些二进制附件。我们需要的 mime 嵌套结构是

      multipart/mixed
      multipart/alternative
          text/plain
          multipart/related
              text/html
              image/jpeg
      application/pdf
      

      所以我们这样做

              // create a "multipart/alternative" wrapper
              $mailalternative = new Zend_Mime_Message();            
              // create a "multipart/related" wrapper
              $mailrelated     = new Zend_Mime_Message();
      
              // text/plain
              $mailplain        = new Zend_Mime_Part($textmail);
              $mailplain->encoding = Zend_Mime::ENCODING_QUOTEDPRINTABLE;
              $mailplain->type     = "text/plain; charset=UTF-8";
      
               // add it on right place
              $mailalternative->addPart($mailplain);
      
              // text/html            
              $mailhtml            = new  Zend_Mime_Part($htmlmail);
              $mailhtml->encoding  = Zend_Mime::ENCODING_QUOTEDPRINTABLE;
              $mailhtml->type      = "text/html; charset=UTF-8";
      
              // add it to related part
              $mailrelated->addPart($mailhtml);
      
              // try to add some inline img attachments
              $img_mimes = array('jpg'=>'jpeg','jpeg'=>'jpeg','png'=>'png');
              foreach($attachments as $attachment)
                 if(isset($img_mimes[strtolower($attachment->Typ)]))
                 {
                    $suffix = strtolower($attachment->Typ);
                    $at = new Zend_Mime_Part($attachment->doc_binary);
                    $at->filename    = $attachment->doc_name.'.'.$attachment->Typ;
                    $at->type        = 'image/'.$img_mimes[$suffix].'; name="'.$attachment->doc_name.'.'.$attachment->Typ.'"';
                    $at->encoding    = Zend_Mime::ENCODING_BASE64;
                    $at->disposition = Zend_Mime::DISPOSITION_INLINE;
                    // id is important to address your pics in your html 
                    // part later on. If id = XYZ you will write 
                    // <img src="cid:XYZ"> in your html mail part ...
                    $at->id          = $at->filename;
                    // add them to related part, so they are accessible in html 
                    $mailrelated->addPart($at);
                 }
      
              $partrelated= new Zend_Mime_Part($mailrelated->generateMessage());
              $partrelated->type     = Zend_Mime::MULTIPART_RELATED;
              $partrelated->boundary = $mailrelated->getMime()->boundary();
              $mailalternative->addPart($partrelated);
              $partalternative = new Zend_Mime_Part($mailalternative->generateMessage());
              $partalternative->type = Zend_Mime::MULTIPART_ALTERNATIVE;
              $partalternative->boundary = $mailalternative->getMime()->boundary();
              // default mime type of zend multipart mail is multipart/mixed,
              // so here dont need to change type and simply set part:
              $mail->addPart($partalternative);
      
      
              // now try to add binary non inline attachments
              $img_mimes = array('jpg'=>'jpeg','jpeg'=>'jpeg','png'=>'png');
              foreach($attachments as $attachment)
              if(!isset($img_mimes[strtolower($attachment->Typ)]))
                 {                            
                    $at = $mail->createAttachment($attachment->doc_binary);
                    $suffix = strtolower($attachment->Typ);
                    $at->type = 'application/'.$suffix;                                                
                    $at->filename = $attachment->doc_name.'.'.$attachment->Typ;
                    $at->id       = $at->filename;
                 }
      

      现在您可以像往常一样发送手动组装的多部分邮件,其中包含 邮件->发送();

      mail->send(); 
      

      希望这对需要在更高级情况下使用 ZF1 邮件组件的人有所帮助。

      需要注意的重要一点:如果您处于某种情况下,您想 仅将内联图像附加到您的邮件中,但不附加其他“真实”附件, ZF1又给你添麻烦了。。 我说的是这种情况:

      multipart/mixed
      multipart/alternative
          text/plain
          multipart/related
              text/html
              image/jpeg
      

      注意缺少的第二个混合部分附件,我们现在只有一个部分, 即多部分/替代。 在这种情况下,ZF1 Mail 会做错,因为它是如此的概念化,以至于它只使用一个 Zend_Mime_Part(我的代码中的替代部分)作为 NON-multipart 邮件来处理此配置并剥离所需的 来自我们硬组装的 Zend_Mime_Part 对象的 multipart/alternative header。 (看看 Mail/Transport/Abstract.php _send() 例程

          $count    = count($this->_parts);
          $boundary = null;
          ...
          }
      
          if ($count > 1) {
              // Multipart message; create new MIME object and boundary
              $mime     = new Zend_Mime($this->_mail->getMimeBoundary());
              $boundary = $mime->boundary();
          } elseif ($this->_isMultipart) {
              // multipart/alternative -- grab boundary
              $boundary = $this->_parts[0]->boundary;
          }
      

      在 Zend/Mime/Message.php isMultiPart() 和 generateMessage() 中,

          public function isMultiPart()
      {
          return (count($this->_parts) > 1);
      }
      

      你看 问题。 Zend ZF1 仅通过计算添加的部分来确定多部分 到 Zend_Mail 对象,但在我们手动组装的情况下这是错误的)

      结果是一封不符合您预期的邮件。

      幸运的是,这个问题/情况有一个简单的解决方法,无需更改 ZF1。

      在发送之前,只需将默认 Zend_Mail 标头 mime 从 multipart/mixed 更改为 multipart/alternative(只是我们删除的部分标头) 你的邮件

            if($attachcount == 0 && $inlinecount > 0)
                $mail->setType('multipart/alternative');
            $mail->send();
      

      现在邮件的周围部分已经从混合变为替代,对于没有“真实”附件的情况来说这是绝对正确的。

      对于所有其他情况,ZF1 本身会根据需要撰写邮件,因此通过处理此注释,ZF1 v1.12 可以像其他优秀的电子邮件库一样处理复杂的电子邮件配置,并具有 ZF 集成的优势受益。

      【讨论】:

      • Pete,多么有用的补丁和演练。你为我节省了很多时间迁移到 swiftmailer。谢谢!!
      猜你喜欢
      • 2018-03-19
      • 2013-11-10
      • 2012-04-28
      • 2022-09-29
      • 2014-10-29
      • 2016-01-03
      • 2013-02-07
      • 1970-01-01
      • 2011-07-28
      相关资源
      最近更新 更多