Skip to content

Commit

Permalink
Cleanup (apple#705)
Browse files Browse the repository at this point in the history
  • Loading branch information
danieleggert committed Aug 29, 2022
1 parent d459719 commit ef20269
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 29 deletions.
5 changes: 5 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Code of Conduct

The code of conduct for this project can be found at https://swift.org/code-of-conduct.

<!-- Copyright (c) 2021 Apple Inc and the Swift Project authors. All Rights Reserved. -->
6 changes: 3 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ let package = Package(
.package(url: "https://github.com/apple/swift-nio", from: "2.40.0"),
.package(url: "https://github.com/apple/swift-nio-ssl", from: "2.20.0"),
.package(url: "https://github.com/apple/swift-log", from: "1.4.0"),
.package(url: "https://github.com/nicklockwood/SwiftFormat", exact: "0.49.11"),
.package(url: "https://github.com/apple/swift-standard-library-preview.git", exact: "0.0.3"),
.package(url: "https://github.com/apple/swift-collections.git", .upToNextMajor(from: "1.0.2")),
.package(url: "https://github.com/apple/swift-standard-library-preview.git", from: "0.0.3"),
.package(url: "https://github.com/apple/swift-collections.git", "1.0.2" ..< "2.0.0"),
.package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.49.11"),
],
targets: [
.executableTarget(
Expand Down
76 changes: 50 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,69 @@

A Swift project that provides an implementation of the IMAP4rev1 protocol, built upon SwiftNIO.

### Introduction and Usage
This project implements:
* Parsing IMAPv4 wire-format to type-safe Swift data structures
* Encoding these Swift data structures to the IMAPv4 wire format
* Extensive support for common IMAP extensions
* Integration with SwiftNIO

`swift-nio-imap` implements the IMAP4rev1 protocol described in RFC 3501 and related RFCs. It is intended as a building block to build mail clients and/or servers. It is built upon SwiftNIO v2.x.
For a list of IMAP extensions, see [EXTENSIONS.md](EXTENSIONS.md).

To use the framework use `import NIOIMAP`. NIOIMAP supports a variety of IMAP extensions, check `EXTENSIONS.md` for full details.
*Note:* This is still a prototype and not quite production ready, yet.

### Commands
### Introduction and Usage

Commands are what an IMAP client sends to a server.
`swift-nio-imap` implements the IMAP4rev1 protocol described in RFC 3501 and related RFCs. It is intended as a building block to build mail clients and/or servers. It is built upon SwiftNIO v2.x.

A command consists of a `Tag` and a `Command`.
To use the framework use `import NIOIMAP`. NIOIMAP supports a variety of IMAP extensions, check `EXTENSIONS.md` for full details.

#### Examples
`Command("tag1", .noop)` => `tag1 NOOP`
`Command("tag2", .capability)` => `tag1 CAPABILITY`
`Command("tag3", .login("[email protected]", "password"))` => `tag3 LOGIN "[email protected]" "password"`
### Example Exchange

To send a command we recommend using a `MessageToByteHandler` with `CommandEncoder` as the encoder:
As a quick example, here’s part of the the exchange listed in RFC 3501 section 8, where lines starting with S: and C: are from the server and client respectively:

```
ClientBootstrap(group: context.eventLoop).channelInitializer { channel in
channel.addHandler(MessageToByteHandler(CommandEncoder()))
}
```text
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```
```
Alternatively, you can write a command manually to a `ByteBuffer` manually like this:
The first 3 lines would correspond to the following in SwiftNIO IMAP:
```swift
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"))))
```
let command = ...
var buffer = ...
let writtenSize = buffer.writeCommand(command)

Next, up is the SELECT command and its responses, which are more interesting:
```swift
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"))))
```

### Sample applications
#### Proxy
We provide a simple proxy that can be placed between some mail client and server. The mail server *must* support TLS.
There’s more going on here than this example shows. But this gives a general idea of how the types look and feel.

`swift run Proxy <local_address> <local_port> <server_address> <server_port>`
### Integration with SwiftNIO

#### CLI
The CLI allows you (the user) to connect to a mail server and enter commands. The mail server *must* support TLS. The CLI will always attempt to connect to the server on port 993.
SwiftNIO IMAP provides a pair of ChannelHandler objects that can be integrated into a SwiftNIO ChannelPipeline. This allows sending IMAP commands using NIO Channels.

`swift run CLI`
The two handlers SwiftNIO IMAP provides are IMAPClientHandler and IMAPServerHandler. Each of these can be inserted into the ChannelPipeline. They can then be used to encode and decode messages. For example:
```swift
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)
```

0 comments on commit ef20269

Please sign in to comment.