forked from thunderbird/thunderbird-ios
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSMTPClient.swift
More file actions
108 lines (100 loc) · 3.38 KB
/
SMTPClient.swift
File metadata and controls
108 lines (100 loc) · 3.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import EmailAddress
import Network
import NIOCore
import NIOExtras
import NIOSSL
import NIOTransportServices
import OSLog
/// Configure `SMTPClient` with a single ``Server`` and send ``Email``.
public struct SMTPClient {
public let server: Server
public init(
_ server: Server,
logger: Logger? = Logger(subsystem: "net.thunderbird", category: "SMTP")
) {
group = NIOTSEventLoopGroup(loopCount: 1, defaultQoS: .utility)
self.server = server
self.logger = logger
}
public func send(_ email: [Email]) async throws {
for email in email {
try await send(email)
}
}
public func send(_ email: Email) async throws {
for recipient in email.allRecipients {
try await send(email, to: recipient)
}
}
func send(_ email: Email, to recipient: EmailAddress) async throws {
do {
let done: EventLoopPromise<Void> = group.next().makePromise()
let bootstrap: NIOClientTCPBootstrap = try .bootstrap(
email: email,
recipient: recipient,
server: server,
group: group,
done: done
)
let connection: EventLoopFuture<any Channel> = bootstrap.connect(host: server.hostname, port: server.port)
connection.cascadeFailure(to: done)
try await withCheckedThrowingContinuation { continuation in
done.futureResult.map {
connection.whenSuccess { $0.close(promise: nil) }
continuation.resume()
}.whenFailure { error in
connection.whenSuccess { $0.close(promise: nil) }
continuation.resume(throwing: error)
}
}
} catch {
throw SMTPError(error)
}
}
private let group: EventLoopGroup
private let logger: Logger?
}
private extension NIOClientTCPBootstrap {
static func bootstrap(
email: Email,
recipient: EmailAddress,
server: Server,
logger: Logger? = nil,
group: EventLoopGroup,
done: EventLoopPromise<Void>
) throws -> Self {
let bootstrap: Self
switch server.connectionSecurity {
case .startTLS:
bootstrap = try Self(
NIOTSConnectionBootstrap(group: group),
tls: NIOSSLClientTLSProvider(context: .ssl, serverHostname: server.hostname)
)
case .tls:
bootstrap = Self(
NIOTSConnectionBootstrap(group: group),
tls: NIOTSClientTLSProvider()
)
bootstrap.enableTLS()
case .none:
bootstrap = Self(
NIOTSConnectionBootstrap(group: group),
tls: NIOTSClientTLSProvider()
)
}
return bootstrap.channelInitializer { channel in
channel.pipeline.addHandlers(
[
LoggingHandler(logger),
MessageHandler(LineBasedFrameDecoder()),
ResponseHandler(),
ByteHandler(RequestEncoder()),
SendHandler(email, recipient: recipient, server: server, done: done)
]
)
}
}
}
private extension NIOSSLContext {
static var ssl: Self { try! Self(configuration: .makeClientConfiguration()) }
}