# `imaplib` --- IMAP4 协议客户端

源代码： Lib/imaplib.py

本模块定义了三个类： `IMAP4` 、 `IMAP4_SSL` 和 `IMAP4_stream` 。这三个类封装了与IMAP4服务器的连接并实现了 RFC 2060 当中定义的大多数IMAP4rev1客户端协议。其与IMAP4（ RFC 1730 ）服务器后向兼容，但是 `STATUS` 指令在IMAP4中不支持。

适用范围: not WASI.

此模块在 WebAssembly 平台上无效或不可用。 请参阅 WebAssembly 平台 了解详情。

`imaplib` 模块提供了三个类，其中 `IMAP4` 是基类：

class imaplib.IMAP4(host='', port=IMAP4_PORT, timeout=None)

  这个类实现了实际的 IMAP4 协议。 当其实例被初始化时会创建连接并确定协议版本 (IMAP4 或 IMAP4rev1)。 如果未指明 host，则会使用 `''` (本地主机)。 如果省略 port，则会使用标准的 IMAP4 端口 (143)。 可选的 timeout 形参指定连接尝试的超时秒数。 如果未指定超时或为 `None`，则会使用全局默认的套接字超时。

  `IMAP4` 类支持 `with` 语句。 当这样使用时，IMAP4 `LOGOUT` 命令会在 `with` 语句退出时自动发出。 例如:

      >>> from imaplib import IMAP4
      >>> with IMAP4("domain.org") as M:
      ...     M.noop()
      ...
      ('OK', [b'Nothing Accomplished. d25if65hy903weo.87'])

  在 3.5 版本发生变更: 添加了对 `with` 语句的支持。

  在 3.9 版本发生变更: 添加了可选的 timeout 形参。

有三个异常被定义为 `IMAP4` 类的属性:

exception IMAP4.error

  任何错误都将引发该异常。 异常的原因会以字符串的形式传递给构造器。

exception IMAP4.abort

  IMAP4 服务器错误会导致引发该异常。 这是 `IMAP4.error` 的子类。 请注意关闭此实例并实例化一个新实例通常将会允许从该异常中恢复。

exception IMAP4.readonly

  当一个可写邮箱的状态被服务器修改时会引发此异常。 此异常是 `IMAP4.error` 的子类。 某个其他客户端现在会具有写入权限，将需要重新打开该邮箱以重新获得写入权限。

另外还有一个针对安全连接的子类:

class imaplib.IMAP4_SSL(host='', port=IMAP4_SSL_PORT, *, ssl_context=None, timeout=None)

  这是一个派生自 `IMAP4` 的子类，它使用经 SSL 加密的套接字进行连接 (为了使用这个类你需要编译时附带 SSL 支持的 socket 模块)。 如果未指定 host，则会使用 `''` (本地主机)。 如果省略了 port，则会使用标准的 IMAP4-over-SSL 端口 (993)。 ssl_context 是一个 `ssl.SSLContext` 对象，它允许将 SSL 配置选项、证书和私钥打包放入一个单独的 (可以长久存在的) 结构体中。 请阅读 安全考量 以获取最佳实践。

  可选的 timeout 形参指明连接尝试的超时秒数。 如果超时值未给出或为 `None`，则会使用全局默认的套接字超时设置。

  在 3.3 版本发生变更: 增加了 ssl_context 形参。

  在 3.4 版本发生变更: 该类现在支持使用 `ssl.SSLContext.check_hostname` 和 服务器名称提示 (参见 `ssl.HAS_SNI`) 进行主机名检测。

  在 3.9 版本发生变更: 添加了可选的 timeout 形参。

  在 3.12 版本发生变更: 已弃用的 keyfile 和 certfile 形参已被移除。

第二个子类允许由子进程所创建的连接:

class imaplib.IMAP4_stream(command)

  这是一个派生自 `IMAP4` 的子类，它可以连接 `stdin/stdout` 文件描述符，此种文件是通过向 `subprocess.Popen()` 传入 command 来创建的。

定义了下列工具函数:

imaplib.Internaldate2tuple(datestr)

  解析一个 IMAP4 `INTERNALDATE` 字符串并返回对应的本地时间。 返回值是一个 `time.struct_time` 元组或者如果字符串格式错误则为 `None`。

imaplib.Int2AP(num)

  将一个整数转换为使用字符集 [`A` .. `P`] 的字节串表示形式。

imaplib.ParseFlags(flagstr)

  将一个 IMAP4 `FLAGS` 响应转换为包含单独旗标的元组。

imaplib.Time2Internaldate(date_time)

  将 date_time 转换为 IMAP4 `INTERNALDATE` 表示形式。 返回值是以下形式的字符串: `"DD-Mmm-YYYY HH:MM:SS +HHMM"` (包括双引号)。 date_time 参数可以是一个代表距离纪元起始的秒数 (如 `time.time()` 的返回值) 的数字 (整数或浮点数)，一个代表本地时间的 9 元组，一个 `time.struct_time` 实例 (如 `time.localtime()` 的返回值)，一个感知型的 `datetime.datetime` 实例，或一个双引号字符串。 在最后一种情况下，它会被假定已经具有正确的格式。

请注意 IMAP4 消息编号会随邮箱的改变而改变；特别是在使用 `EXPUNGE` 命令执行删除后剩余的消息会被重新编号。 因此高度建议通过 UID 命令来改用 UID。

模块的最后有一段测试，其中包含的用法示例更加广泛。

参见

描述该协议的文档，实现该协议的服务器源代码，由华盛顿大学 IMAP 信息中心提供 (源代码) https://github.com/uw-imap/imap (不再维护)。

## IMAP4 对象

所有 IMAP4rev1 命令都是以相同名称的方法来表示的，可以为大写或小写形式。

命令的所有参数都会被转换为字符串，只有 `AUTHENTICATE` 例外，而 `APPEND` 的最后一个参数会被作为 IMAP4 字面值传入。 如有必要 (字符串包含 IMAP4 协议中的敏感字符并且未加圆括号或双引号) 每个字符串都会被转码。 但是，`LOGIN` 命令的 password 参数总是会被转码。 如果你想让某个参数字符串免于被转码 (例如: `STORE` 的 flags 参数) 则要为该字符串加上圆括号 (例如: `r'(\Deleted)'`)。

大多数命令均返回一个元组: `(type, [data, ...])` 其中 type 通常为 `'OK'` 或 `'NO'`，而 data 为来自命令响应的文本，或为来自命令的规定结果。 每个 data 均为 `bytes` 或者元组。 如果为元组，则其第一部分是响应的标头，而第二部分将包含数据（例如：'literal' 值）。

以下命令的 message_set 选项为指定要操作的一条或多条消息的字符串。 它可以是一个简单的消息编号 (`'1'`)，一段消息编号区间 (`'2:4'`)，或者一组以逗号分隔的非连续区间 (`'1:3,6:9'`)。 区间可以包含一个星号来表示无限的上界 (`'3:*'`)。

`IMAP4` 实例具有下列方法:

IMAP4.append(mailbox, flags, date_time, message)

  将 message 添加到指定的邮箱。

  flags may be `None` or a string of IMAP flag tokens. Multiple flags are separated by spaces, for example `r'\Seen \Answered'`. If flags is not already enclosed in parentheses, parentheses are added automatically.

IMAP4.authenticate(mechanism, authobject)

  认证命令 --- 要求对响应进行处理。

  mechanism 指明要使用哪种认证机制 —— 它应当在实例变量 `capabilities` 中以 `AUTH=mechanism` 的形式出现。

  authobject 必须是一个可调用对象:

      data = authobject(response)

  它将被调用以便处理服务器连续响应；传给它的 response 参数将为 `bytes` 类型。 它应当返回 base64 编码的 `bytes` 数据 并发送给服务器。 或者在客户端中止响应时返回 `None` 并应改为发送 `*`。

  在 3.5 版本发生变更: 字符串形式的用户名和密码现在会被执行 `utf-8` 编码而不限于 ASCII 字符。

IMAP4.check()

  为服务器上的邮箱设置检查点。

IMAP4.close()

  关闭当前选定的邮箱。 已删除的消息会从可写邮箱中被移除。 在 `LOGOUT` 之前建议执行此命令。

IMAP4.copy(message_set, new_mailbox)

  将 message_set 消息拷贝到 new_mailbox 的末尾。

IMAP4.create(mailbox)

  新建名为 mailbox 的新邮箱。

IMAP4.delete(mailbox)

  删除名为 mailbox 的旧邮箱。

IMAP4.deleteacl(mailbox, who)

  删除邮箱上某人的 ACL (移除任何权限)。

IMAP4.enable(capability)

  启用 capability (参见 RFC 5161)。 大多数功能都不需要被启用。 目前只有 `UTF8=ACCEPT` 功能受到支持 (参见 RFC 6855)。

  Added in version 3.5: `enable()` 方法本身，以及 RFC 6855 支持。

IMAP4.expunge()

  从选定的邮箱中永久移除被删除的条目。 为每条被删除的消息各生成一个 `EXPUNGE` 响应。 返回包含按接收时间排序的 `EXPUNGE` 消息编号的列表。

IMAP4.fetch(message_set, message_parts)

  获取消息（的各个部分）。 message_parts 应为加圆括号的消息部分名称字符串，例如: `"(UID BODY[TEXT])"`。 返回的数据是由消息部分封包和数据组成的元组。

IMAP4.getacl(mailbox)

  获取 mailbox 的 `ACL`。 此方法是非标准的，但是被 `Cyrus` 服务器所支持。

IMAP4.getannotation(mailbox, entry, attribute)

  提取 mailbox 的特定 `ANNOTATION`。 此方法是非标准的，但是被 `Cyrus` 服务器所支持。

IMAP4.getquota(root)

  获取 `quota` root 的资源使用和限制。 此方法是 rfc2087 定义的 IMAP4 QUOTA 扩展的组成部分。

IMAP4.getquotaroot(mailbox)

  获取指定 mailbox 的 `quota` `roots` 列表。 此方法是 rfc2087 定义的 IMAP4 QUOTA 扩展的组成部分。

IMAP4.idle(duration=None)

  返回一个 `Idler`: 即实现了在 RFC 2177 中定义的 IMAP4 `IDLE` 命令的可迭代上下文管理器。

  返回的对象将在被 `with` 语句激活时发送 `IDLE` 命令，通过 iterator 协议产生 IMAP 不带标签的响应，并在上下文退出时发送 `DONE`。

  所有在发送 `IDLE` 命令之后到达的无标签响应（包括任何在服务器接受该命令之前已到达的）都可通过迭代来获取。 任何剩余的响应（未在 `with` 上下文中被迭代的）可在 `IDLE` 结束之后，使用 `IMAP4.response()` 以通常的方式来获取。

  响应表示为 `(type, [data, ...])` 元组，如 IMAP4 对象 中所述。

  duration 参数设置了保持空闲的最大持续时间（以秒为单位），在此之后任何正在进行的迭代都将停止。它可以是 `int` 或 `float`，或者没有时间限制的 `None`。 希望避免服务器上的非活动超时的调用者应该最多保持 29 分钟（1740 秒）。 需要一个套接字连接；在 `IMAP4_stream` 连接上，duration 必须为 `None`。

      >>> with M.idle(duration=29 * 60) as idler:
      ...     for typ, data in idler:
      ...         print(typ, data)
      ...
      EXISTS [b'1']
      RECENT [b'1']

  Idler.burst(interval=0.1)

    产生间隔不超过 interval 秒的突发响应 (表示为 `int` 或 `float`)。

    此 generator 是一次迭代一个响应的替代方法，旨在帮助高效的批处理。 它检索下一个响应以及任何立即可用的后续响应。（例如，在批量删除后的一系列快速 `EXPUNGE` 响应。）

    需要一个套接字连接；在 `IMAP4_stream` 连接上不起作用。

        >>> with M.idle() as idler:
        ...     # 在小于0.1秒的时间内获得响应和其他后续
        ...     batch = list(idler.burst())
        ...     print(f'正在处理 {len(batch)} 个响应...')
        ...     print(batch)
        ...
        正在处理 3 个响应...
        [('EXPUNGE', [b'2']), ('EXPUNGE', [b'1']), ('RECENT', [b'0'])]

    小技巧

    当等待突发中的第一个响应时，会遵守传递给 `IMAP4.idle()` 的 `IDLE` 上下文最大持续时间。 因此，一个过期的 `Idler` 将导致此生成器立即返回而不产生任何东西。 如果在循环中使用它，调用者应该考虑这一点。

  备注

  `IMAP4.idle()` 返回的迭代器仅在 `with` 语句中可用。 在该上下文之前或之后，当命令完成时，就会在内部收集未经请求的响应，并且可以使用 `IMAP4.response()` 进行检索。

  备注

  `Idler` 类的名称和结构是内部接口，可能会发生变化。 调用代码可以依赖其上下文管理、迭代和公共方法来保持稳定，但不应该子类化、实例化、比较或以其他方式直接引用该类。

  Added in version 3.14.

IMAP4.list([directory[, pattern]])

  列出 directory 中与 pattern 相匹配的邮箱名称。 directory 默认为最高层级的电邮文件夹，而 pattern 默认为匹配任何文本。 返回的数据包含 `LIST` 响应列表。

IMAP4.login(user, password)

  使用纯文本密码标识客户端。 password 将被转码。

IMAP4.login_cram_md5(user, password)

  在标识用户以保护密码时强制使用 `CRAM-MD5` 认证。 将只在服务器 `CAPABILITY` 响应包含 `AUTH=CRAM-MD5` 短语时才有效。

  在 3.15 版本发生变更: 如果 MD5 支持不可用则会引发 `IMAP4.error`。

IMAP4.logout()

  关闭对服务器的连接。 返回服务器 `BYE` 响应。

  在 3.8 版本发生变更: 此方法不会再静默地忽略任意异常。

IMAP4.lsub(directory='""', pattern='*')

  列出 directory 中已订阅的与 pattern 相匹配的邮箱名称。 directory 默认为最高层级目录而 pattern 默认为匹配任何邮箱。 返回的数据为消息部分封包和数据的元组。

IMAP4.myrights(mailbox)

  显示某个邮箱的本人 ACL (即本人在邮箱中的权限)。

IMAP4.namespace()

  返回 RFC 2342 中定义的 IMAP 命名空间。

IMAP4.noop()

  将 `NOOP` 发送给服务器。

IMAP4.open(host, port, timeout=None)

  打开连接到 host 上 port 的套接字。 可选的 timeout 形参指定连接尝试的超时秒数。 如果超时值未给出或为 `None`，则会使用全局默认的套接字超时。 另外请注意如果 timeout 形参被设为零，它将引发 `ValueError` 以拒绝创建非阻塞套接字。 此方法会由 `IMAP4` 构造器隐式地调用。 此方法所建立的连接对象将在 `IMAP4.read()`, `IMAP4.readline()`, `IMAP4.send()` 和 `IMAP4.shutdown()` 等方法中被使用。 你可以重写此方法。

  引发一个 审计事件 `imaplib.open` 并附带参数 `self`, `host`, `port`。

  在 3.9 版本发生变更: 加入 timeout 参数。

IMAP4.partial(message_num, message_part, start, length)

  获取消息被截断的部分。 返回的数据是由消息部分封包和数据组成的元组。

IMAP4.proxyauth(user)

  作为 user 进行认证。 允许经权限的管理员通过代理进入任意用户的邮箱。

IMAP4.read(size)

  从远程服务器读取 size 字节。 你可以重写此方法。

IMAP4.readline()

  从远程服务器读取一行。 你可以重写此方法。

IMAP4.recent()

  提示服务器进行更新。 如果没有新消息则返回的数据为 `None`，否则为 `RECENT` 响应的值。

IMAP4.rename(oldmailbox, newmailbox)

  将名为 oldmailbox 的邮箱重命名为 newmailbox。

IMAP4.response(code)

  如果收到响应 code 则返回其数据，否则返回 `None`。 返回给定的代码，而不是普通的类型。

IMAP4.search(charset, criterion[, ...])

  在邮箱中搜索匹配的消息。 charset 可以为 `None`，在这种情况下在发给服务器的请求中将不指定 `CHARSET`。 IMAP 协议要求至少指定一个标准；当服务器返回错误时将会引发异常。 charset 为 `None` 对应使用 `enable()` 命令启用了 `UTF8=ACCEPT` 功能的情况。

  示例:

      # M 是一个已连接的 IMAP4 实例...
      typ, msgnums = M.search(None, 'FROM', '"LDJ"')

      # 或者：
      typ, msgnums = M.search(None, '(FROM "LDJ")')

IMAP4.select(mailbox='INBOX', readonly=False)

  选择一个邮箱。 返回的数据是 mailbox 中消息的数量 (`EXISTS` 响应)。 默认的 mailbox 为 `'INBOX'`。 如果设置了 readonly 旗标，则不允许修改该邮箱。

IMAP4.send(data)

  将 `data` 发送给远程服务器。 你可以重写此方法。

  引发一个 审计事件 `imaplib.send` 并附带参数 `self`, `data`。

IMAP4.setacl(mailbox, who, what)

  设置 mailbox 的 `ACL`。 此方法是非标准的，但是被 `Cyrus` 服务器所支持。

IMAP4.setannotation(mailbox, entry, attribute[, ...])

  设置 mailbox 的 `ANNOTATION`。 此方法是非标准的，但是被 `Cyrus` 服务器所支持。

IMAP4.setquota(root, limits)

  设置 `quota` root 的资源限制为 limits。 此方法是 rfc2087 定义的 IMAP4 QUOTA 扩展的组成部分。

IMAP4.shutdown()

  关闭在 `open` 中建立的连接。 此方法会由 `IMAP4.logout()` 隐式地调用。 你可以重写此方法。

IMAP4.socket()

  返回用于连接服务器的套接字实例。

IMAP4.sort(sort_criteria, charset, search_criterion[, ...])

  `sort` 命令是 `search` 的变化形式，带有结果排序语句。 返回的数据包含以空格分隔的匹配消息编号列表。

  sort 命令在 search_criterion 参数之前还有两个参数；一个带圆括号的 sort_criteria 列表，和搜索的 charset。 请注意不同于 `search`，搜索的 charset 参数是强制性的。 还有一个 `uid sort` 命令与 `sort` 对应，如同 `uid search` 与 `search` 对应一样。 `sort` 命令首先在邮箱中搜索匹配给定搜索条件的消息，使用 charset 参数来解读搜索条件中的字符串。 然后它将返回所匹配消息的编号。

  这是一个 `IMAP4rev1` 扩展命令。

IMAP4.starttls(ssl_context=None)

  发送一个 `STARTTLS` 命令。 ssl_context 参数是可选的并且应为一个 `ssl.SSLContext` 对象。 这将在 IMAP 连接上启用加密。 请阅读 安全考量 来了解最佳实践。

  Added in version 3.2.

  在 3.4 版本发生变更: 该方法现在支持使用 `ssl.SSLContext.check_hostname` 和 服务器名称提示 (参见 `ssl.HAS_SNI`) 进行主机名称检测。

IMAP4.status(mailbox, names)

  针对 mailbox 请求指定的状态条件。

IMAP4.store(message_set, command, flag_list)

  改变邮箱中消息的旗标处理。 command 由 RFC 2060 的 6.4.6 小节指明，应为 "FLAGS", "+FLAGS" 或 "-FLAGS" 之一，并可选择附带 ".SILENT" 后缀。

  例如，要在所有消息上设置删除旗标:

      typ, data = M.search(None, 'ALL')
      for num in data[0].split():
         M.store(num, '+FLAGS', '\\Deleted')
      M.expunge()

  备注

  创建包含 ']' (例如: "[test]") 的旗标会违反 RFC 3501 (即 IMAP 协议)。 不过，imaplib 在历史上曾经允许创建这样的标签，并且流行的 IMAP 服务器，如 Gmail，都接受并会产生这样的旗标。 有些非 Python 程序也会创建这样的旗标。 虽然它违反 RFC 并且 IMAP 客户端和服务器应当严格规范，但是 imaplib 出于向下兼容的考虑仍然继续允许创建这样的标签，并且像在 Python 3.6 中一样，会在当其被服务器所发送时处理它们，因为这能提升实际的兼容性。

IMAP4.subscribe(mailbox)

  订阅新邮箱。

IMAP4.thread(threading_algorithm, charset, search_criterion[, ...])

  `thread` 命令是 `search` 的变化形式，带有针对结果的消息串句法。 返回的数据包含以空格分隔的消息串成员列表。

  消息串成员由零个或多个消息编号组成，以空格分隔，标示了连续的上下级关系。

  Thread 命令在 search_criterion 参数之前还有两个参数；一个 threading_algorithm，以及搜索使用的 charset。 请注意不同于 `search`，搜索使用的 charset 参数是强制性的。 还有一个 `uid thread` 命令与 `thread` 对应，如同 `uid search` 与 `search` 对应一样。 `thread` 命令首先在邮箱中搜索匹配给定搜索条件的消息，使用 charset 参数来解读搜索条件中的字符串。 然后它将按照指定的消息串算法返回所匹配的消息串。

  这是一个 `IMAP4rev1` 扩展命令。

IMAP4.uid(command, arg[, ...])

  执行 command arg 并附带用 UID 所标识的消息，而不是用消息编号。 返回与命令对应的响应。 必须至少提供一个参数；如果不提供任何参数，服务器将返回错误并引发异常。

IMAP4.unsubscribe(mailbox)

  取消订阅原有邮箱。

IMAP4.unselect()

  `imaplib.IMAP4.unselect()` 会释放关联到选定邮箱的服务器资源并将服务器返回到已认证状态。 此命令会执行与 `imaplib.IMAP4.close()` 相同的动作，区别在于它不会从当前选定邮箱中永久性地移除消息。

  Added in version 3.9.

IMAP4.xatom(name[, ...])

  允许服务器在 `CAPABILITY` 响应中通知简单的扩展命令。

在 `IMAP4` 的实例上定义了下列属性:

IMAP4.PROTOCOL_VERSION

  在服务器的 `CAPABILITY` 响应中最新的受支持协议。

IMAP4.debug

  控制调试输出的整数值。 初始值会从模块变量 `Debug` 中获取。 大于三的值表示将追踪每一条命令。

IMAP4.utf8_enabled

  通常为 `False` 的布尔值，但也可以被设为 `True`，如果成功地为 `UTF8=ACCEPT` 功能发送了 `enable()` 命令的话。

  Added in version 3.5.

property IMAP4.file

  Internal `BufferedReader` associated with the underlying socket. This property is documented for legacy purposes but not part of the public interface. The caller is responsible to ensure that the current file is closed before changing it.

  从 3.15 版起已弃用，将在 3.19 版中移除.

## IMAP4 示例

以下是一个最短示例（不带错误检查），该示例将打开邮箱，检索并打印所有消息:

    import getpass, imaplib

    M = imaplib.IMAP4(host='example.org')
    M.login(getpass.getuser(), getpass.getpass())
    M.select()
    typ, data = M.search(None, 'ALL')
    for num in data[0].split():
        typ, data = M.fetch(num, '(RFC822)')
        print('Message %s\n%s\n' % (num, data[0][1]))
    M.close()
    M.logout()