diff options
-rw-r--r-- | src/main.zig | 59 |
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. |