2017-09-15 4 views
1

-Code senden, das Senden von E-Mail (Arbeits gut):Perl SMTP: keine E-Mail mit Nicht-ASCII-Zeichen in Körper

#!/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 = '[email protected]'; 
    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('[email protected]', '[email protected]', 'Hello', 'test email'); 

Sobald ich nicht-ASCII-Zeichen an den Körper hinzufügen:

send_email('[email protected]', '[email protected]', 'Hello', 'test email. Русский текст'); 

es hängt mit der letzten Nachricht in Debug-Ausgabe:

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

Wie zu beheben?

Antwort

1

TL; TR: Die Lösung ist einfach, aber das Problem selbst ist komplex. Um dies zu beheben das Problem hinzu:

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

bevor die Mail an sendmail(...) geben. Beachten Sie jedoch die Warnung am Ende dieser Antwort auf mögliche Probleme beim Senden von 8-Bit-Daten wie diese in einer Mail an erster Stelle.


Um zu verstehen, tatsächlich das Problem und das Update einer tiefer in den Umgang mit Zeichen gegen Oktetts in Steckdosen in Perl zu suchen hat:

  • Email::Sender::Transport::SMTP verwendet Net::SMTP die sich die syswrite Methode der Verwendungen zugrunde liegenden IO::Socket::SSL oder IO::Socket::IP (oder IO::Socket::INET) Socket, abhängig davon, ob SSL verwendet wurde oder nicht.
  • syswrite erwartet Octets und erwartet die Anzahl der Oktette, die in den Socket geschrieben werden.
  • Aber die Mail, die Sie mit Email::Simple erstellen, gibt keine Oktette zurück, sondern eine Zeichenfolge mit dem gesetzten UTF8-Flag. In dieser Zeichenfolge unterscheidet sich die Anzahl der Zeichen von der Anzahl der Oktette, da die russische текст als 5 Zeichen behandelt wird, während sie bei der Konvertierung mit UTF-8 10 Byte beträgt.
  • Email::Sender::Transport::SMTP leitet einfach die UTF8-Zeichenfolge der E-Mail an Net::SMTP, die es in einem syswrite verwendet. Die Länge wird unter Verwendung von length berechnet, was die Anzahl der Zeichen angibt, die sich in diesem Fall von der Anzahl der Oktette unterscheidet. Aber an der Socket-Site werden die Oktette und nicht die Zeichen aus der Zeichenfolge genommen und die gegebene Länge wird als Anzahl von Oktetten behandelt.
  • Da die angegebene Länge als Oktette und nicht als Zeichen behandelt wird, sendet sie letztendlich weniger Daten an den Server als von den oberen Schichten des Programms erwartet.
  • Auf diese Weise wird der Ende-von-Mail-Marker (Zeile mit einem Punkt) nicht gesendet, und der Server wartet daher darauf, dass der Client mehr Daten sendet, während der Client nicht mehr zu sendende Daten kennt.

Als Beispiel nehmen Sie eine Mail, die nur aus zwei russischen Zeichen "ı" besteht.Mit Zeile endet und die End-of-Mail besteht es Marker von 7 Zeichen:

ий\r\n.\r\n 

Aber sind diese 7 Zeichen tatsächlich 9 Oktetts, weil die ersten zwei Zeichen sind zwei Bytes jeder

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

Jetzt ein syswrite($fd,"ий\r\n.\r\n",7) wird nur das erste 7 Bytes des Zeichens 7 aber 9 Oktetts lange Zeichenfolge schreiben:

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

Dies bedeutet, dass der End-of-Mail-Marker unvollständig ist. Dies bedeutet, dass der Mail-Server auf mehr Daten wartet, während der Mail-Client keine weiteren Daten kennt, die gesendet werden müssen. Das bewirkt, dass die Anwendung im Wesentlichen hängen bleibt.

Nun, wer ist dafür verantwortlich?

Man könnte argumentieren, dass IO :: Socket :: SSL :: syswrite mit UTF8 Daten in einer vernünftigen Art und Weise umgehen sollte und das, was angefordert wurde, aber in RT#98732. Aber die Dokumentation für syswrite in IO :: Socket :: SSL sagt eindeutig, dass es auf Bytes funktioniert. Und da es praktisch unmöglich ist, ein vernünftiges zeichenbasiertes Verhalten zu erstellen, wenn nicht blockierende Sockets berücksichtigt werden, wurde dieser Fehler zurückgewiesen. Auch Nicht-SSL-Sockets werden Probleme mit UTF8-Zeichenfolgen haben: Wenn Sie SSL nicht an erster Stelle verwenden würden, würde das Programm nicht hängen bleiben, sondern stattdessen mit Wide character in syswrite ... abstürzen.

Nächste Schicht würde erwarten, dass Net::SMTP solche UTF8-Strings richtig behandelt. Nur, so heißt es ausdrücklich in der documentation of Net::SMTP::data:

DATA ein Verweis auf eine Liste sein kann, oder eine Liste und müssen vom Anrufer Oktetts codiert werden von was auch immer Codierung erforderlich ist, z.B. mit der encode() - Funktion des Encode-Moduls.

Nun könnte man argumentieren, dass entweder Email::Transport UTF8 Strings richtig behandeln soll oder dass Email::Simple::as_string sollte einen UTF8-String in erster Linie nicht zurück.

Aber man könnte noch eine weitere Schicht hinauf gehen: zum Entwickler selbst. Mail ist traditionell nur ASCII, und das Senden von Nicht-ASCII-Zeichen in einer Mail ist eine schlechte Idee, da es nur zuverlässig mit Mail-Servern mit der Erweiterung 8BITMIME funktioniert. Wenn E-Mail-Server beteiligt sind, die diese Erweiterung nicht unterstützen, sind die Ergebnisse unvorhersehbar, d. H. E-Mail kann transformiert werden (was Unterschriften brechen kann), kann unlesbar gemacht werden oder irgendwo verloren gehen. Verwenden Sie daher besser ein komplexeres Modul wie Email::MIME und legen Sie eine geeignete Codierung für die Inhaltsübertragung fest.

Verwandte Themen