SwiftNIO IMAP 发布

作为扩展服务器端 Swift 生态系统的一部分,我们很高兴地宣布发布新的 IMAPv4 解析器和编码器:SwiftNIO IMAP。

此软件包实现了

动机

电子邮件作为互联网不可或缺的一部分已有 40 多年历史,并且如今在许多产品和服务中无处不在。

互联网消息访问协议 (IMAP) 是最广泛使用的开放标准,用于检索电子邮件消息。它已经存在了几十年,并通过各种 RFC 在多年中得到了显著发展。

正确解析和编码 IMAP 是出了名的困难。SwiftNIO IMAP 通过处理 IMAP 编码和解析的许多细微之处来降低这种难度,从而比以往任何时候都更容易使用服务器端 Swift 编写丰富而强大的电子邮件集成。

此软件包专注于解析和编码 IMAP,同时也提供一些与核心 IMAP 类型相关的常用便捷方法。它不实现任何与 IMAP 相关的业务逻辑。

简要导览

SwiftNIO IMAP 的目标是 RFC 3501,IMAP 版本 4rev1,并且还支持来自 20 多个 RFC 的扩展:RFC 2087, 2177, 2221, 2342, 2971, 3348, 3501, 3502, 3516, 3691, 4315, 4467, 4469, 4731, 4959, 5032, 5161, 5182, 5258, 5464, 5819, 6154, 6851, 7162, 7377, 7888, 和 8438。

IMAP 使用基于文本的“人类可读”线格式,而 SwiftNIO IMAP 将其桥接到使用现代 Swift 数据结构的类型安全世界。该协议是“不对称的”:从服务器发送的消息遵循与客户端发送的消息不同的模式。

示例交换

作为一个快速示例,以下是 RFC 3501 第 8 节中列出的交换的一部分,其中以 S:C: 开头的行分别来自服务器和客户端

S:   * OK IMAP4rev1 Service Ready
C:   a001 login mrc secret
S:   a001 OK LOGIN completed
C:   a002 select inbox
S:   * 18 EXISTS
S:   * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
S:   * 2 RECENT
S:   * OK [UNSEEN 17] Message 17 is the first unseen message
S:   * OK [UIDVALIDITY 3857529045] UIDs valid
S:   a002 OK [READ-WRITE] SELECT completed

前 3 行在 SwiftNIO IMAP 中对应于以下内容

Response.untagged(.conditionalState(.ok(ResponseText(text: "IMAP4rev1 Service Ready"))))
CommandStreamPart.tagged(TaggedCommand(tag: "a001", command: .login(username: "mrc", password: "secret")))
Response.tagged(.init(tag: "a001", state: .ok(ResponseText(text: "LOGIN completed"))))

接下来是 SELECT 命令及其响应,这更有趣

CommandStreamPart.tagged(TaggedCommand(tag: "a002", command: .select(MailboxName("box1"), [])))
Response.untagged(.mailboxData(.exists(18)))
Response.untagged(.mailboxData(.flags([.answered, .flagged, .deleted, .seen, .draft])))
Response.untagged(.mailboxData(.recent(2)))
Response.untagged(.conditionalState(.ok(ResponseText(code: .unseen(17), text: "Message 17 is the first unseen message"))))
Response.untagged(.conditionalState(.ok(ResponseText(code: .uidValidity(3857529045), text: "UIDs valid"))))
Response.tagged(.init(tag: "a002", state: .ok(ResponseText(code: .readWrite, text: "SELECT completed"))))

这里发生的事情比此示例显示的要多。但这大致说明了事物的外观和感觉。

常用值

IMAP 中使用的一些非常常见的值是 UID、消息序列号和邮箱名称。

SwiftNIO IMAP 具有 UIDSequenceNumber 类型,以及相关的类型,例如 UIDRangeUIDSetSequenceRangeSequenceSet。这两种集合类型都符合 SetAlgebra。所有这些都具有用于常见操作的便捷方法。

邮箱由“modified UTF-7”编码的字符串标识。MailboxNameMailboxPath 类型支持解码和编码这些字符串,同时允许往返有时在野外发现的错误编码的字节字符串。

透明字面量支持

SwiftNIO IMAP 可以透明地编码和解码客户端消息,同时使用来自基本 RFC 3501 和 RFC 7888 扩展的同步和非同步字面量编码。

这些变体是透明处理的 - 对于客户端和服务器都是如此

RFC 3501 “带引号的”字符串

C: A001 LOGIN "FRED FOOBAR" "fat man"
S: A001 OK LOGIN completed

RFC 3501 命令延续

C: A001 LOGIN {11}
S: + Ready for additional command text
C: FRED FOOBAR {7}
S: + Ready for additional command text
C: fat man
S: A001 OK LOGIN completed

RFC 7888 非同步字面量

C: A001 LOGIN {11+}
C: FRED FOOBAR {7+}
C: fat man
S: A001 OK LOGIN completed

所谓的 LITERAL+ / LITERAL- 支持可以通过使用来自服务器的 CAPABILITY 响应来启用,或者通过显式设置编码选项来启用。

与 SwiftNIO 集成

SwiftNIO IMAP 提供了一对 ChannelHandler 对象,可以集成到 SwiftNIO ChannelPipeline 中。这允许使用 NIO Channel 发送 IMAP 命令。

SwiftNIO IMAP 提供的两个处理程序是 IMAPClientHandlerIMAPServerHandler。这些处理程序中的每一个都可以插入到 ChannelPipeline 中。然后可以使用它们来编码和解码消息。例如

let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let channel = try await ClientBootstrap(group).channelInitializer { channel in
    channel.pipeline.addHandler(IMAPClientHandler())
}.connect(host: example.com, port: 143).get()

try await channel.writeAndFlush(CommandStreamPart.tagged(TaggedCommand(tag: "a001", command: .login(username: "mrc", password: "secret"))), promise: nil)

ChannelHandler 支持透明字面量、IMAP 功能以及 SwiftNIO IMAP 的所有其他功能。它们是服务器和客户端上 IMAP 应用程序的强大构建块。

接下来是什么

我们今天发布的 SwiftNIO IMAP 版本仍然是一个原型。我们希望征求社区的反馈。

我们已经对项目代码进行了广泛的测试,并且我们相信它已经接近“生产就绪”状态。但是我们很乐意在 Swift 论坛上就缺少什么以及哪些部分可以改进进行设计讨论。

参与进来

非常欢迎您的反馈、经验和贡献!