code wiki / (root) / nx_email_retrieve.nx

nx_email_retrieve.nx source

↩ module page · 173 lines · 7280 B

1// nx_email_retrieve.nx -- EMAIL RUNG R4: POP3 + IMAP retrieval codec. 2// 3// module: nishi-core.email.retrieve 4// depends: (none -- pure RFC 1939 / RFC 3501 line codec) 5// capability: CORE_EMAIL 6// 7// The client side of FETCHING mail (the receive counterpart to R1's 8// SMTP send). Pure parse/build over caller buffers -- no syscalls, so 9// it gates offline against scripted server transcripts; the live socket 10// wiring is a later nx_email_retrieve_io.nx (the nx_dns/nx_dns_io split). 11// 12// POP3 (RFC 1939): "+OK"/"-ERR" status, STAT count/octets, a generic 13// command builder, and multi-line handling -- de-dot-stuffing (the 14// inverse of R1's SMTP dot-stuffing: a body "." line is sent as 15// ".." and a lone "." terminates) plus terminator detection. 16// IMAP4rev1 (RFC 3501): tagged command builder, tagged-completion 17// status (OK/NO/BAD) that correctly SKIPS untagged "*" data lines 18// (the classic "* OK ..." vs "A001 OK ..." confusion), and literal 19// "{n}" octet-count parse. 20// 21// license_tier: INDEPENDENT_REDERIVE 22// genealogy_id: international-research-sources/ietf/rfc_1939 + rfc_3501 23// lineage_id: nishi_email_retrieve_r4 24// 25// nx_safety_envelope: 26// intended_use: "POP3/IMAP client response parse + command 27// build. Mail retrieval substrate." 28// sil_target: SIL2 (mis-parse drops/duplicates mail) 29// evidence: [RFC_1939_3501_basis, pop3_dedot_inverse, 30// imap_tagged_vs_untagged, literal_size] 31// hazard_register: [bug-tape-pop3-dot-unstuff, bug-tape-imap-untagged-confusion] 32// residual_risk: "IMAP literal continuation streaming + UIDPLUS/ 33// CONDSTORE extensions are later sub-rungs." 34// verdict: NOT_YET_EVALUATED 35 36func er_match(a: *u8, b: *u8, len: i64) -> i64 { 37 var i: i64 = 0 38 while i < len { if (a[i] & 0xff) != (b[i] & 0xff) { return 0 } i = i + 1 } 39 return 1 40} 41func er_cat(out: *u8, oi: i64, s: *u8) -> i64 { 42 var k: i64 = 0 43 while s[k] != (0 as u8) { out[oi] = s[k]; oi = oi + 1; k = k + 1 } 44 return oi 45} 46func er_catn(out: *u8, oi: i64, s: *u8, len: i64) -> i64 { 47 var k: i64 = 0 48 while k < len { out[oi] = s[k]; oi = oi + 1; k = k + 1 } 49 return oi 50} 51 52// ---- POP3 (RFC 1939) ------------------------------------------------- 53 54// +OK -> 1, -ERR -> 0, neither -> -1. 55func nx_pop3_reply_ok(line: *u8, n: i64) -> i64 { 56 if n >= 3 && (line[0] & 0xff) == 43 && (line[1] & 0xff) == 79 && (line[2] & 0xff) == 75 { return 1 } 57 if n >= 4 && (line[0] & 0xff) == 45 && (line[1] & 0xff) == 69 && (line[2] & 0xff) == 82 && (line[3] & 0xff) == 82 { return 0 } 58 return 0 - 1 59} 60 61// Parse "+OK <count> <octets>" -> *oc, *oo. 1 ok / 0 malformed. 62func nx_pop3_parse_stat(line: *u8, n: i64, oc: *i64, oo: *i64) -> i64 { 63 if nx_pop3_reply_ok(line, n) != 1 { return 0 } 64 var i: i64 = 3 65 while i < n && (line[i] & 0xff) == 32 { i = i + 1 } 66 var c: i64 = 0; var g1: i64 = 0 67 while i < n && (line[i] & 0xff) >= 48 && (line[i] & 0xff) <= 57 { c = c * 10 + ((line[i] & 0xff) - 48); i = i + 1; g1 = 1 } 68 if g1 == 0 { return 0 } 69 while i < n && (line[i] & 0xff) == 32 { i = i + 1 } 70 var o: i64 = 0; var g2: i64 = 0 71 while i < n && (line[i] & 0xff) >= 48 && (line[i] & 0xff) <= 57 { o = o * 10 + ((line[i] & 0xff) - 48); i = i + 1; g2 = 1 } 72 if g2 == 0 { return 0 } 73 *oc = c; *oo = o 74 return 1 75} 76 77// Build "VERB[ arg]\r\n". Returns length. 78func nx_pop3_build(out: *u8, cap: i64, verb: *u8, arg: *u8, alen: i64) -> i64 { 79 var oi: i64 = er_cat(out, 0, verb) 80 if alen > 0 { out[oi] = 32 as u8; oi = oi + 1; oi = er_catn(out, oi, arg, alen) } 81 out[oi] = 13 as u8; out[oi + 1] = 10 as u8 82 return oi + 2 83} 84 85// De-dot-stuff a POP3 multi-line response body (inverse of SMTP dot- 86// stuffing). A leading ".." becomes "."; a lone "." line terminates. 87// Returns the recovered body length (terminator excluded). 88func nx_pop3_dedot(src: *u8, n: i64, out: *u8, cap: i64) -> i64 { 89 var oi: i64 = 0 90 var at_ls: i64 = 1 91 var i: i64 = 0 92 var done: i64 = 0 93 while i < n && done == 0 { 94 let c: i64 = src[i] & 0xff 95 if at_ls == 1 && c == 46 { 96 if i + 1 >= n { 97 done = 1 98 } else { 99 let c1: i64 = src[i + 1] & 0xff 100 if c1 == 13 || c1 == 10 { 101 done = 1 102 } else { 103 i = i + 1; at_ls = 0 // drop the stuffed dot 104 } 105 } 106 } else { 107 if oi < cap { out[oi] = c as u8; oi = oi + 1 } 108 if c == 10 { at_ls = 1 } else { at_ls = 0 } 109 i = i + 1 110 } 111 } 112 return oi 113} 114 115// 1 iff buf holds a complete POP3 multi-line response (ends "\r\n.\r\n" 116// or is the empty ".\r\n"). 117func nx_pop3_multiline_complete(buf: *u8, n: i64) -> i64 { 118 if n >= 5 && (buf[n - 5] & 0xff) == 13 && (buf[n - 4] & 0xff) == 10 && (buf[n - 3] & 0xff) == 46 && (buf[n - 2] & 0xff) == 13 && (buf[n - 1] & 0xff) == 10 { return 1 } 119 if n >= 3 && (buf[0] & 0xff) == 46 && (buf[1] & 0xff) == 13 && (buf[2] & 0xff) == 10 { return 1 } 120 return 0 121} 122 123// ---- IMAP4rev1 (RFC 3501) -------------------------------------------- 124 125// Build "<tag> <cmd>\r\n". 126func nx_imap_build_tagged(out: *u8, cap: i64, tag: *u8, tl: i64, cmd: *u8, cl: i64) -> i64 { 127 var oi: i64 = er_catn(out, 0, tag, tl) 128 out[oi] = 32 as u8; oi = oi + 1 129 oi = er_catn(out, oi, cmd, cl) 130 out[oi] = 13 as u8; out[oi + 1] = 10 as u8 131 return oi + 2 132} 133 134// Scan a response for the TAGGED completion line "<tag> OK|NO|BAD". 135// Untagged "*" data lines are skipped. *out_status: 0 OK / 1 NO / 2 BAD 136// / -1 unknown. Returns 1 if a tagged completion is present, 0 if the 137// response is still only untagged data (incomplete). 138func nx_imap_reply_status(buf: *u8, n: i64, tag: *u8, tl: i64, out_status: *i64) -> i64 { 139 var i: i64 = 0 140 while i < n { 141 let ls: i64 = i 142 var j: i64 = i 143 while j < n && (buf[j] & 0xff) != 10 { j = j + 1 } 144 let le: i64 = j 145 if le - ls > tl && er_match(buf + ls, tag, tl) == 1 && (buf[ls + tl] & 0xff) == 32 { 146 let ss: i64 = ls + tl + 1 147 if ss + 2 <= le && (buf[ss] & 0xff) == 79 && (buf[ss + 1] & 0xff) == 75 { *out_status = 0; return 1 } 148 if ss + 2 <= le && (buf[ss] & 0xff) == 78 && (buf[ss + 1] & 0xff) == 79 { *out_status = 1; return 1 } 149 if ss + 3 <= le && (buf[ss] & 0xff) == 66 && (buf[ss + 1] & 0xff) == 65 && (buf[ss + 2] & 0xff) == 68 { *out_status = 2; return 1 } 150 *out_status = 0 - 1; return 1 151 } 152 i = le + 1 153 } 154 return 0 155} 156 157// Parse an IMAP literal "{n}" octet count from a line. 1 ok / 0 none. 158func nx_imap_literal_size(line: *u8, n: i64, out: *i64) -> i64 { 159 var i: i64 = 0 160 var found: i64 = 0 161 while i < n { 162 if (line[i] & 0xff) == 123 { 163 var k: i64 = i + 1 164 var v: i64 = 0 165 var got: i64 = 0 166 while k < n && (line[k] & 0xff) >= 48 && (line[k] & 0xff) <= 57 { v = v * 10 + ((line[k] & 0xff) - 48); k = k + 1; got = 1 } 167 if k < n && (line[k] & 0xff) == 125 && got == 1 { *out = v; found = 1 } 168 } 169 i = i + 1 170 } 171 if found == 1 { return 1 } 172 return 0 173}