Skip to content

Commit a73a1bd

Browse files
committed
chore: wip
1 parent a33eebd commit a73a1bd

3 files changed

Lines changed: 124 additions & 7 deletions

File tree

packages/zig/src/core/fs_compat.zig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,18 @@ pub fn listEmlFiles(allocator: std.mem.Allocator, dir_path: []const u8) ![][]con
336336
}
337337

338338
/// Read entire file contents into an allocated buffer.
339+
pub fn getFileSize(path: []const u8) !u64 {
340+
const path_z = toZ(path) orelse return error.SystemResources;
341+
defer freeZ(path_z, path.len);
342+
const fd = std.c.open(path_z, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, @as(std.c.mode_t, 0));
343+
if (fd < 0) return error.FileNotFound;
344+
defer _ = std.c.close(fd);
345+
// Seek to end to get file size
346+
const size = std.c.lseek(fd, 0, std.c.SEEK.END);
347+
if (size < 0) return error.SystemResources;
348+
return @intCast(size);
349+
}
350+
339351
pub fn readFileAlloc(allocator: std.mem.Allocator, path: []const u8) ![]u8 {
340352
const path_z = toZ(path) orelse return error.SystemResources;
341353
defer freeZ(path_z, path.len);

packages/zig/src/core/protocol.zig

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,38 @@ const ConnectionWrapper = struct {
102102
return to_copy;
103103
}
104104

105+
// Try to decrypt pre-existing ciphertext first (e.g. leftover from STARTTLS handshake)
106+
if (self.ciphertext_len > 0) {
107+
var decrypt_buf: [16384]u8 = undefined;
108+
const dec_result = tls_conn.decrypt(self.ciphertext_accum[0..self.ciphertext_len], &decrypt_buf) catch |err| {
109+
logger.err("TLS decrypt error (pre-existing): {}", .{err});
110+
return error.TlsDecryptFailed;
111+
};
112+
113+
if (dec_result.ciphertext_pos > 0) {
114+
const remaining = self.ciphertext_len - dec_result.ciphertext_pos;
115+
if (remaining > 0) {
116+
std.mem.copyForwards(u8, &self.ciphertext_accum, self.ciphertext_accum[dec_result.ciphertext_pos..self.ciphertext_len]);
117+
}
118+
self.ciphertext_len = remaining;
119+
}
120+
121+
if (dec_result.closed) return 0;
122+
123+
if (dec_result.cleartext.len > 0) {
124+
const to_copy = @min(dec_result.cleartext.len, buffer.len);
125+
@memcpy(buffer[0..to_copy], dec_result.cleartext[0..to_copy]);
126+
if (dec_result.cleartext.len > to_copy) {
127+
const excess = dec_result.cleartext.len - to_copy;
128+
@memcpy(self.cleartext_buf[0..excess], dec_result.cleartext[to_copy..]);
129+
self.cleartext_start = 0;
130+
self.cleartext_end = excess;
131+
}
132+
return to_copy;
133+
}
134+
// Pre-existing data wasn't a complete TLS record, fall through to read more
135+
}
136+
105137
// Need to read and decrypt more data
106138
var recv_buf: [tls.input_buffer_len]u8 = undefined;
107139
const bytes_read = self.tcp_conn.read(recv_buf[0..]) catch |err| {

packages/zig/src/protocol/imap.zig

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -388,10 +388,10 @@ const SequenceIterator = struct {
388388

389389
pub fn next(self: *SequenceIterator) ?u32 {
390390
// Continue yielding from current range
391-
if (self.range_current <= self.range_end) {
391+
while (self.range_current <= self.range_end) {
392392
const val = self.range_current;
393393
self.range_current += 1;
394-
return val;
394+
if (val >= 1 and val <= self.max_seq) return val;
395395
}
396396

397397
// Parse next element from sequence set
@@ -417,12 +417,15 @@ const SequenceIterator = struct {
417417
// Reverse range
418418
self.range_current = end_num + 1;
419419
self.range_end = start_num;
420-
return end_num;
420+
if (end_num >= 1 and end_num <= self.max_seq) return end_num;
421+
return self.next();
421422
}
422-
return start_num;
423+
if (start_num >= 1 and start_num <= self.max_seq) return start_num;
424+
return self.next();
423425
}
424426

425-
return start_num;
427+
if (start_num >= 1 and start_num <= self.max_seq) return start_num;
428+
return self.next();
426429
}
427430

428431
fn parseNum(self: *SequenceIterator) ?u32 {
@@ -1545,10 +1548,41 @@ pub const ImapSession = struct {
15451548
}
15461549
}
15471550

1551+
// Detect if we only need metadata (FLAGS, UID, RFC822.SIZE) — no file content read needed
1552+
const want_rfc822_size = std.mem.indexOf(u8, items_raw, "RFC822.SIZE") != null;
1553+
const metadata_only = !want_full_body and !want_header_only and !want_bodystructure and !want_body_text_only;
1554+
15481555
var seq = start;
15491556
while (seq <= end) : (seq += 1) {
15501557
const filename = files[seq - 1];
15511558
const uid = self.getUidForSeq(seq);
1559+
1560+
// For metadata-only requests (FLAGS, UID, RFC822.SIZE), skip reading file content
1561+
if (metadata_only) {
1562+
const msg_flags = MaildirFlags.fromFilename(filename);
1563+
var flag_str_buf: [256]u8 = undefined;
1564+
const flag_str = msg_flags.toImapString(&flag_str_buf);
1565+
1566+
if (want_rfc822_size) {
1567+
const filepath = try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ dir, filename });
1568+
defer self.allocator.free(filepath);
1569+
const file_size = fs_compat.getFileSize(filepath) catch 0;
1570+
const resp = try std.fmt.allocPrint(self.allocator, "* {d} FETCH (UID {d} FLAGS ({s}) RFC822.SIZE {d})", .{
1571+
seq, uid, flag_str, file_size,
1572+
});
1573+
defer self.allocator.free(resp);
1574+
try self.writeData(resp);
1575+
} else {
1576+
const resp = try std.fmt.allocPrint(self.allocator, "* {d} FETCH (UID {d} FLAGS ({s}))", .{
1577+
seq, uid, flag_str,
1578+
});
1579+
defer self.allocator.free(resp);
1580+
try self.writeData(resp);
1581+
}
1582+
try self.writeData("\r\n");
1583+
continue;
1584+
}
1585+
15521586
const filepath = try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ dir, filename });
15531587
defer self.allocator.free(filepath);
15541588

@@ -1773,15 +1807,55 @@ pub const ImapSession = struct {
17731807
std.ascii.indexOfIgnoreCase(criteria, "UNANSWERED") == null;
17741808
const want_unanswered = std.ascii.indexOfIgnoreCase(criteria, "UNANSWERED") != null;
17751809

1810+
// Parse UID range criteria (e.g. "UID 4:*", "UID 1,3,5")
1811+
var uid_range_start: i64 = 0;
1812+
var uid_range_end: i64 = std.math.maxInt(i64);
1813+
var has_uid_range = false;
1814+
if (std.ascii.indexOfIgnoreCase(criteria, "UID")) |uid_pos| {
1815+
// Make sure this is the UID keyword, not part of another word
1816+
const after_uid = uid_pos + 3;
1817+
if (after_uid < criteria.len) {
1818+
const rest = std.mem.trim(u8, criteria[after_uid..], " \t");
1819+
if (rest.len > 0) {
1820+
has_uid_range = true;
1821+
if (std.mem.indexOf(u8, rest, ":")) |colon| {
1822+
uid_range_start = std.fmt.parseInt(i64, rest[0..colon], 10) catch 1;
1823+
const end_part = rest[colon + 1 ..];
1824+
// End part might have trailing spaces or other criteria
1825+
const end_token = if (std.mem.indexOfAny(u8, end_part, " \t")) |sp| end_part[0..sp] else end_part;
1826+
if (std.mem.eql(u8, end_token, "*")) {
1827+
uid_range_end = std.math.maxInt(i64);
1828+
} else {
1829+
uid_range_end = std.fmt.parseInt(i64, end_token, 10) catch std.math.maxInt(i64);
1830+
}
1831+
} else {
1832+
// Single UID value
1833+
const uid_token = if (std.mem.indexOfAny(u8, rest, " \t")) |sp| rest[0..sp] else rest;
1834+
const single_uid = std.fmt.parseInt(i64, uid_token, 10) catch 0;
1835+
if (single_uid > 0) {
1836+
uid_range_start = single_uid;
1837+
uid_range_end = single_uid;
1838+
}
1839+
}
1840+
}
1841+
}
1842+
}
1843+
17761844
var buf: [8192]u8 = undefined;
17771845
var fbs = io_compat.fixedBufferStream(&buf);
17781846
const writer = fbs.writer();
17791847
writer.writeAll("SEARCH") catch {};
17801848

17811849
for (files, 0..) |filename, i| {
17821850
const seq = i + 1;
1851+
const uid = self.getUidForSeq(seq);
17831852
const flags = MaildirFlags.fromFilename(filename);
17841853

1854+
// Filter by UID range if specified
1855+
if (has_uid_range) {
1856+
if (uid < uid_range_start or uid > uid_range_end) continue;
1857+
}
1858+
17851859
// Evaluate flag-based criteria
17861860
if (!is_all) {
17871861
if (want_seen and !flags.seen) continue;
@@ -1813,7 +1887,6 @@ pub const ImapSession = struct {
18131887
const after = criteria[from_pos + 4 ..];
18141888
const term = extractQuotedArg(after);
18151889
if (term.len > 0) {
1816-
// Check if From: header contains the term
18171890
if (std.ascii.indexOfIgnoreCase(hdr, "From:")) |fp| {
18181891
const from_line_end = std.mem.indexOfScalarPos(u8, hdr, fp, '\n') orelse hdr.len;
18191892
const from_line = hdr[fp..from_line_end];
@@ -1841,7 +1914,7 @@ pub const ImapSession = struct {
18411914
}
18421915
}
18431916

1844-
const result_id: i64 = if (is_uid) self.getUidForSeq(seq) else @intCast(seq);
1917+
const result_id: i64 = if (is_uid) uid else @intCast(seq);
18451918
writer.print(" {d}", .{result_id}) catch break;
18461919
}
18471920

0 commit comments

Comments
 (0)