2

使用 Python 3.x 中的emailandsmtplib模块,经过大量研究,我可以发送带有 Unicode 主题、文本正文和名称(对于发件人和收件人)的电子邮件,这很棒,但它不会让我向本身包含 Unicode(或其他非 ASCII)字符的地址发送电子邮件。它似乎不受支持(如果您查看其中的注释,email.utils说明:即“地址必须(根据 RFC)是 ascii,因此如果不是,请引发 UnicodeError。”)任何尝试这样做无论如何(包括但不仅限于密件抄送收件人——为了可能绕过任何邮件头限制)都因一种或另一种形式的 Unicode 错误而失败。评论没有说明哪个 RFC(我不认为他们都指定电子邮件地址应仅使用 ASCII。)

有没有其他方法可以做到这一点,因为有传言说这样的地址可以存在于某些地方:úßerñame@dómain.com?我的意思是,还有其他支持它的电子邮件模块吗?

如果我的问题的前提不正确,电子邮件地址是否旨在成为全世界的 ASCII 码(尽管有传言称其中一些使用其他字符)?

我看到这个问题适用于其他语言,但不适用于 Python。

4

1 回答 1

12

电子邮件地址是否打算成为全世界的 ASCII 格式?

不; 事实上,恰恰相反。电子邮件地址仅为ASCII。它们打算成为 Unicode,而我们正在努力实现;这只是一个缓慢的过渡。


在现代电子邮件中,电子邮件地址有两部分:1 DNS 主机名(在 之后的部分@),以及该主机上的邮箱(在 之前的部分@)。它们受完全不同的标准管理,因为 DNS 必须适用于 HTTP 和除电子邮件之外的所有其他事物。


DNS 最后一次更新是在 1987 年的RFC 1035中,它要求 ASCII 的一个受限子集(并且不区分大小写)。

但是,在RFC 5890中指定的 IDNA(应用程序的国际化域名)允许应用程序有选择地将 Unicode 字符集的大部分映射到 DNS 名称以呈现给用户。

因此,您不能拥有域名dómain.com。但是你可以拥有域名xn--dmain-0ta.com。许多应用程序会接受dómain.com来自用户的输入并自动翻译,并接受xn--dmain-0ta.com来自网络的输入并将其显示为dómain.com. 2

在 Python 中,一些 Internet 协议库会自动为您对域名进行 IDNA 编码;否则不会。如果他们不这样做,您可以手动执行,如下所示:

>>> 'dómain.com'.encode('idna')
b'xn--dmain-0ta.com'

请注意,在 3.x 中,这是 a bytes,而不是 a str; 如果你需要一个str,你总是可以这样做:

>>> 'dómain.com'.encode('idna').decode('ascii')
'xn--dmain-0ta.com'

邮箱名称由 SMTP 定义,最近在RFC 5321RFC 5322中定义,这清楚地表明,如何解释地址的“本地部分”完全取决于接收主机。例如,大多数电子邮件服务器使用不区分大小写的名称;许多允许“加标签”(例如,shule@gmail.comshule+so@gmail.com是同一个邮箱);一些(如 gmail)忽略所有点;等等

问题是 SMTP 从未指定用于标头的字符集。传统的 SMTP 服务器只有 7 位 ASCII,因此,实际上,直到最近,您只能在标头中使用 ASCII,因此在邮箱名称中也只能使用 ASCII。

RFC 6530和相关提案中指定的 EAI(电子邮件地址国际化)允许在 SMTP 会话中协商 UTF-8。在 UTF-8 会话中,标头和这些标头中的地址被解释为 UTF-8。(主机名的 IDNA 编码不是必需的,但仍然允许。)

这很好,但是如果您的客户端、服务器、收件人的服务器或沿途的任何中继服务器不使用 SMTPUTF8 怎么办?为了处理这种情况,每个拥有 UTF-8 邮箱的人也都有该邮箱的 ASCII 名称。理想情况下,它与消息一起发送,并且链上的最后一个 SMTPUTF8 程序在遇到第一个非 SMTPUTF8 程序时切换到 ASCII 替代程序。更常见的是,它只是收到一条错误消息并将其传播回用户以手动处理。3

这个想法是,最终,互联网上的大多数主机都将使用 SMTPUTF8,所以你可以úßerñame@dómain.com——但与此同时,你的服务器dómain.com具有úßerñameussernyame作为同一个邮箱的别名。任何无法处理 SMTPUTF8 的人都会将您(并且必须引用您)视为ussernyame. (事实上​​,他们的邮件客户端会将您视为ussernyame@xn--dmain-0ta.com,但它可以修复最后一部分;如果第一部分在传输过程中丢失,它无能为力。)

截至 2018 年年中,大多数主机不使用 SMTPUTF8,许多客户端库也不使用。

从 Python 3.5 开始,4标准库smtplib支持SMTPUTF8. 如果您使用的是高级sendmail功能:

如果SMTPUTF8包含在 mail_options 中,并且服务器支持它,则from_addrto_addrs可能包含非 ASCII 字符。

所以,你做的是这样的:

try:
    server.sendmail([fromaddr], [toaddr], msg, mail_options=['SMTPUTF8'])
except SMTPNotSupportedError:
    server.sendmail([fromaddr_ascii], [toaddr_ascii], msg)

(理论上最好用 来检查 EHLO 响应has_extn,但在实践中,尝试它似乎更值得顺利。这可能会随着服务器生态系统和/或的未来改进而改变smptlib。)

你从哪里得到fromaddr_asciitoaddr_ascii?这取决于你的程序。DNS部分,你只使用IDNA,但对于邮箱部分,没有这样的规则;您必须知道邮箱的备用 ASCII 邮箱名称。也许你问用户。也许您有一个数据库,其中存储了 EAI 和传统地址的联系人。也许您只担心一个特定的域,并且您知道它使用了一些您可以实施的规则。


1. 实际上,addr-spec 有两个部分;地址是一个addr-spec 加上可选的显示名称和注释。但别介意。

2. 有一些例外。例如,如果您键入http://staсkoverflow.com,您的浏览器可能会警告您使用西里尔字母小写字母 Es 代替拉丁字母小写字母 Cee 可能是一种劫持尝试。或者,如果您尝试导航到http://dómain.com,告诉您域不存在的错误页面可能会显示xn--dmain-0ta.com,因为这对于调试更有用。

3. 这是希望随着时间的推移会变得更好的事情之一,但在它不再重要之前可能不会变得足够好......</sub>

4. 如果你使用 Python 3.4 或 2.7 怎么办?那么你没有 SMTPUTF8 支持。升级,去寻找第三方库而不是smtplib,或者编写自己的 SMTP 代码。

于 2018-09-02T04:06:59.457 回答