Something went wrong. Try again.
SMTP client for Zig forked from karlseguin.tngl.sh/smtp_client.zig
Something went wrong. Try again.
5.4 kB · 193 lines
Zig
at dev
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194// Internal helpers used by this library// If you're looking for helpers to help you mock/testconst std = @import("std");
const mem = std.mem;const ArrayList = std.ArrayList;const Config = @import("smtp.zig").Config;
pub const allocator = std.testing.allocator;
// std.testing.expectEqual won't coerce expected to actual, which is a problem// when expected is frequently a comptime.// https://github.com/ziglang/zig/issues/4437pub fn expectEqual(expected: anytype, actual: anytype) !void { try std.testing.expectEqual(@as(@TypeOf(actual), expected), actual);}
pub const expectError = std.testing.expectError;pub const expectString = std.testing.expectEqualStrings;
// Dummy net.Stream, lets us setup data to be read and capture data that is written.pub const Stream = struct { closed: bool, handle: c_int = 0, _read_index: usize, _arena: std.heap.ArenaAllocator, _random: std.Random.DefaultPrng, _to_read: std.ArrayList(u8), _received: std.ArrayList([]const u8),
pub fn init() Stream { var seed: u64 = undefined; std.posix.getrandom(std.mem.asBytes(&seed)) catch unreachable;
return .{ .closed = false, ._read_index = 0, ._arena = std.heap.ArenaAllocator.init(allocator), ._random = std.Random.DefaultPrng.init(seed), ._to_read = .empty, ._received = .empty, }; }
pub fn deinit(self: *Stream) void { self._to_read.deinit(allocator); self._received.deinit(allocator); self._arena.deinit(); }
pub fn reset(self: *Stream) void { self._read_index = 0; self._to_read.clearRetainingCapacity(); self._received.clearRetainingCapacity(); }
pub fn received(self: *Stream) [][]const u8 { return self._received.items; }
pub fn readTimeout(_: *Stream, _: []const u8) !void { // noop }
pub fn toTLS(_: *Stream, _: *const Config) !void { // noop }
pub fn add(self: *Stream, value: []const u8) void { self._to_read.appendSlice(allocator, value) catch unreachable; }
pub fn read(self: *Stream, buf: []u8) !usize { std.debug.assert(!self.closed);
const read_index = self._read_index; const items = self._to_read.items;
if (read_index == items.len) { return 0; } if (buf.len == 0) { return 0; }
// let's fragment this message const left_to_read = items.len - read_index; const max_can_read = if (buf.len < left_to_read) buf.len else left_to_read; var r = self._random.random(); const to_read = r.uintAtMost(usize, max_can_read - 1) + 1;
var data = items[read_index..(read_index + to_read)]; if (data.len > buf.len) { // we have more data than we have space in buf (our target) // we'll give it when it can take data = data[0..buf.len]; } self._read_index = read_index + data.len;
for (data, 0..) |b, i| { buf[i] = b; }
return data.len; }
// store messages that are written to the stream pub fn writeAll(self: *Stream, data: []const u8) !void { return self.directWrite(data); }
pub fn directWrite(self: *Stream, data: []const u8) !void { const d = self._arena.allocator().dupe(u8, data) catch unreachable; self._received.append(allocator, d) catch unreachable; }
pub fn close(self: *Stream) void { self.closed = true; }};
pub const MockServer = struct { index: ?usize, req_res: []const ReqRes,
pub fn init(req_res: []const ReqRes) MockServer { return .{ .index = null, .req_res = req_res, }; }
pub fn toTLS(_: *MockServer, _: *const Config) !void { // noop }
pub fn readTimeout(_: *MockServer, _: []const u8) !void { // noop }
pub fn read(self: *MockServer, buf: []u8) !usize { const index = self.index orelse { // in SMTP, the server sends the initial data. @memcpy(buf[0..5], "220\r\n"); self.index = 0; return 5; };
const rr = self.req_res[index]; if (rr.res) |res| { @memcpy(buf[0..res.len], res); self.index = index + 1; return res.len; } @panic("unexpected read"); }
pub fn writeAll(self: *MockServer, data: []const u8) !void { return self.directWrite(data); }
pub fn flush(_: *MockServer) !void { @panic("not implemented"); }
pub fn writeByte(_: *MockServer, _: u8) !void { @panic("not implemented"); }
pub fn directWrite(self: *MockServer, data: []const u8) !void { const index = self.index orelse { @panic("received data before server initiated connection"); };
try expectString(data, self.req_res[index].req); }
const ReqRes = struct { req: []const u8, res: ?[]const u8, };};
pub fn random() std.Random { return .{ .ptr = @constCast(@ptrCast(&{})), .fillFn = struct { pub fn fill(_: *anyopaque, buf: []u8) void { @memset(buf, 0); } }.fill, };}