代码之家  ›  专栏  ›  技术社区  ›  user4035

Perl SMTP:无法发送正文中包含非ascii字符的电子邮件

  •  1
  • user4035  · 技术社区  · 7 年前

    #!/usr/bin/perl
    
    use utf8;
    use strict;
    use warnings;
    
    use Email::Sender::Simple qw(sendmail);
    use Email::Sender::Transport::SMTP ();
    use Email::Simple ();
    use open ':std', ':encoding(UTF-8)';
    
    sub send_email
    {
        my $email_from = shift;
        my $email_to = shift;
        my $subject = shift;
        my $message = shift;
    
        my $smtpserver = 'smtp.gmail.com';
        my $smtpport = 465;
        my $smtpuser   = 'user@gmail.com';
        my $password = 'secret';
    
        my $transport = Email::Sender::Transport::SMTP->new({
            host => $smtpserver,
            port => $smtpport,
            sasl_username => $email_from,
            sasl_password => $password,
            debug    => 1,
            ssl => 1,
        });
    
        my $email = Email::Simple->create(
            header => [
                To      => $email_to,
                From    => $email_from,
                Subject => $subject,
            ],
            body => $message,
        );
    
        $email->header_set( 'Content-Type' => 'text/html' );
        $email->header_set( 'charset' => 'UTF-8' );
        sendmail($email, { transport => $transport });
    }
    
    send_email('user@gmail.com', 'user@gmail.com', 'Hello', 'test email');
    

    在正文中添加非ascii字符后:

    send_email('user@gmail.com', 'user@gmail.com', 'Hello', 'test email. Русский текст');
    

    它与调试输出中的最后一条消息挂起:

    Net::SMTP::_SSL=GLOB(0x8d41fa0)>>> charset: UTF-8
    Net::SMTP::_SSL=GLOB(0x8d41fa0)>>> 
    Net::SMTP::_SSL=GLOB(0x8d41fa0)>>> test email. Русский текст
    Net::SMTP::_SSL=GLOB(0x8d41fa0)>>> .
    

    1 回复  |  直到 7 年前
        1
  •  3
  •   user4035    7 年前

    $email = Encode::encode('utf-8',$email->as_string)
    

    在将邮件发送给之前 sendmail(...)


    要真正理解问题和解决方法,必须深入研究Perl中套接字中字符与八位字节的处理:

    • Email::Sender::Transport::SMTP 使用 Net::SMTP 它本身使用 syswrite IO::Socket::SSL IO::Socket::IP (或 IO::Socket::INET )套接字,具体取决于是否使用了SSL。
    • 需要八位字节,它需要写入套接字的八位字节数。
    • 但是,您使用的邮件 Email::Simple текст 当用UTF-8转换时,被视为5个字符,而它是10个八位字节。
    • 电子邮件::发件人::传输::SMTP 只需将电子邮件的UTF8字符串转发到 在一个 系统写入 length 它给出的字符数与本例中的八位字节数不同。但在套接字站点上,它将从字符串中提取八位字节而不是字符,并将给定的长度视为八位字节数。
    • 由于它将给定的长度视为八位字节,而不是字符,因此最终会像程序上层所期望的那样,向服务器发送更少的数据。
    • 这样,邮件结束标记(带单点的行)就不会发送,因此服务器正在等待客户端发送更多数据,而客户端不知道要发送更多数据。

    以一封仅由两个俄语字符组成的邮件为例。带有行尾和邮件尾标记,由7个字符组成:

    ий\r\n.\r\n
    

    и       й       \r \n   .   \r  \n
    d0 b8   d0 b9   0d  0a  2e  0d  0a  
    

    syswrite($fd,"ий\r\n.\r\n",7) 只写7个字符的前7个八位字节,但写9个八位字节长的字符串:

    и       й       \r \n   . 
    d0 b8   d0 b9   0d  0a  2e
    

    这意味着邮件结束标记不完整。这意味着邮件服务器将等待更多数据,而邮件客户端不知道需要发送更多数据。这实际上会导致应用程序挂起。

    有人可能会认为IO::Socket::SSL::syswrite应该以一种合理的方式处理UTF8数据,这是请求的,但在 RT#98732 . 但是 系统写入 Wide character in syswrite ... 相反

    下一层是预期的 网络::SMTP documentation of Net::SMTP::data :

    数据可以是对列表或列表的引用,并且

    现在有人可能会说 Email::Transport 应该正确处理UTF8字符串 Email::Simple::as_string 首先不应返回UTF8字符串。

    Email::MIME 并设置适当的内容传输编码。