aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main.zig59
1 files changed, 29 insertions, 30 deletions
diff --git a/src/main.zig b/src/main.zig
index 7e48a92..a3f7e90 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -12,9 +12,9 @@ const log = std.log.scoped(.smtp);
/// auth is authentication details for the mail server, may be null if server doesn't require auth.
/// from is sender's email address
/// to is list of recipient email addresses
-/// msg is the actual, interesting message.
+/// msg is the actual, interesting message.
/// Note this library makes no effort to ensure your mail is actually a valid email, it should conform to rfc5322 otherwise servers may reject it.
-pub fn send_mail(allocator: std.mem.Allocator, server: []const u8, auth: ?Auth, from: []const u8, to: []const []const u8, msg: [] const u8) !void {
+pub fn send_mail(allocator: std.mem.Allocator, server: []const u8, auth: ?Auth, from: []const u8, to: []const []const u8, msg: []const u8) !void {
var client = try Client.init(allocator, server, auth);
defer client.deinit();
try client.send_mail(from, to, msg);
@@ -27,7 +27,7 @@ pub const Auth = struct {
};
/// A Client represents a single client connection to an SMTP server.
-/// which could be used for sending many mails
+/// which could be used for sending many mails
pub const Client = struct {
const AuthCaps = struct {
plain: bool = false,
@@ -42,16 +42,16 @@ pub const Client = struct {
var caps = Capabilities{};
var spl = std.mem.splitSequence(u8, ehlo_response, "\r\n");
var nxt = spl.next(); // First line of EHLO response is just hi
- nxt = spl.next();
- while (nxt != null): (nxt = spl.next()) {
+ nxt = spl.next();
+ while (nxt != null) : (nxt = spl.next()) {
if (std.mem.eql(u8, nxt.?, "STARTTLS")) {
caps.starttls = true;
- } if (std.mem.startsWith(u8, nxt.?, "AUTH")) {
+ } else if (std.mem.startsWith(u8, nxt.?, "AUTH")) {
caps.auth = AuthCaps{};
var spl2 = std.mem.splitScalar(u8, nxt.?, ' ');
var nxt2 = spl2.next();
nxt2 = spl2.next(); // skip the AUTH part
- while (nxt2 != null): (nxt2 = spl2.next()) {
+ while (nxt2 != null) : (nxt2 = spl2.next()) {
if (std.mem.eql(u8, nxt2.?, "PLAIN")) {
caps.auth.?.plain = true;
} else if (std.mem.eql(u8, nxt2.?, "LOGIN")) {
@@ -60,7 +60,6 @@ pub const Client = struct {
log.warn("unrecognised auth mechanism {s}", .{nxt2.?});
}
}
-
} else {
log.warn("unrecognised capability {s}", .{nxt.?});
}
@@ -84,7 +83,7 @@ pub const Client = struct {
}
}
- const ReadError = error {} || std.net.Stream.ReadError || TlsError;
+ const ReadError = error{} || std.net.Stream.ReadError || TlsError;
fn reader(self: *Client) std.io.Reader(*Client, ReadError, read) {
return .{
@@ -107,7 +106,7 @@ pub const Client = struct {
use_tls: bool = false,
tls_stream: ?std.crypto.tls.Client = null,
tls_cert_bundle: ?std.crypto.Certificate.Bundle = null,
-
+
/// Creates a new SMTP client
/// 'server' must be "hostname:port"
pub fn init(allocator: std.mem.Allocator, server: []const u8, maybe_auth: ?Auth) !Client {
@@ -152,7 +151,7 @@ pub const Client = struct {
if (auth_caps.plain) {
// TODO there has to be a nicer way than this surely
// TODO support identity as well as user+pass
- const z = try std.fmt.allocPrint(allocator, "{s}\x00{s}\x00{s}", .{"", auth.user, auth.pass});
+ const z = try std.fmt.allocPrint(allocator, "{s}\x00{s}\x00{s}", .{ "", auth.user, auth.pass });
defer allocator.free(z);
const enc = std.base64.standard.Encoder;
var zz = try allocator.alloc(u8, enc.calcSize(z.len));
@@ -235,7 +234,7 @@ pub const Client = struct {
}
return try res.toOwnedSlice();
}
-
+
fn write_line(self: *Client, line: []const u8) !void {
log.debug("> {s}", .{line});
var wtr = self.writer();
@@ -264,7 +263,7 @@ pub const Client = struct {
self.allocator.free(try self.read_expect_code(354));
var buf: [1024]u8 = undefined;
var r = try rdr.read(&buf);
- while (r > 0): (r = try rdr.read(&buf)) {
+ while (r > 0) : (r = try rdr.read(&buf)) {
// TODO proper dot encoding, ensure any existing sequences of \r\n.\r\n are escaped I guess
log.debug("> {s}", .{buf[0..r]});
try self.writer().writeAll(buf[0..r]);
@@ -283,7 +282,7 @@ pub const Client = struct {
self.allocator.free(try self.read_expect_code(221));
}
- pub fn send_mail(self: *Client, from: []const u8, to:[]const []const u8, msg: []const u8) !void {
+ pub fn send_mail(self: *Client, from: []const u8, to: []const []const u8, msg: []const u8) !void {
try self.rset();
try self.mail(from);
for (to) |t| {
@@ -293,11 +292,11 @@ pub const Client = struct {
try self.data(fbs.reader());
}
- const Error = error{Malformatted, InvalidResponseCode};
+ const Error = error{ Malformatted, InvalidResponseCode };
};
// TODO why doesn't tls module define an error set?
-const TlsError = error{
+const TlsError = error{
Overflow,
TlsAlertUnexpectedMessage,
TlsAlertBadRecordMac,
@@ -335,28 +334,28 @@ const TlsError = error{
};
test "send email" {
- const mail =
- \\Subject: test
- \\From: <martin@mfashby.net>
- \\To: <martin@ashbysoft.com>
- \\
- \\This is a test message
+ const mail =
+ \\Subject: test
+ \\From: <martin@mfashby.net>
+ \\To: <martin@mfashby.net>
+ \\
+ \\This is a test message
;
const user = std.os.getenv("SMTP_USERNAME").?;
const pass = std.os.getenv("SMTP_PASSWORD").?;
- var client = try Client.init(std.testing.allocator, "mail.mfashby.net:587", .{.user = user, .pass = pass});
+ var client = try Client.init(std.testing.allocator, "mail.mfashby.net:587", .{ .user = user, .pass = pass });
defer client.deinit();
try client.send_mail("martin@mfashby.net", &[_][]const u8{"martin@mfashby.net"}, mail);
- const mail2 =
- \\Subject: test2
- \\From: <martin@mfashby.net>
- \\To: <martin@ashbysoft.com>
- \\
- \\This is another test message
+ const mail2 =
+ \\Subject: test2
+ \\From: <martin@mfashby.net>
+ \\To: <martin@mfashby.net>
+ \\
+ \\This is another test message
;
try client.send_mail("martin@mfashby.net", &[_][]const u8{"martin@mfashby.net"}, mail2);
try client.quit(); // Be nice (but we don't have to)
}
// TODO lots more tests.
-// TODO use mock SMTP server to integration test local only. \ No newline at end of file
+// TODO use mock SMTP server to integration test local only.