code wiki / (root) / nx_http_date.nx

nx_http_date.nx source

↩ module page · 195 lines · 6497 B

1// http_date.nx -- format + parse RFC 7231 IMF-fixdate. 2// 3// HTTP's native date format: 4// Sun, 06 Nov 1994 08:49:37 GMT 5// ^^^ ^^ ^^^ ^^^^ ^^ ^^ ^^ ^^^ 6// wkd dd mon yyyy hh mm ss tz 7// 8// Used in: Date, Last-Modified, Expires, If-Modified-Since, 9// If-Unmodified-Since headers. RFC 7231 §7.1.1 strongly prefers 10// this form over the older rfc850 / asctime variants; we only 11// produce fixdate and accept fixdate on parse. 12// 13// Always emitted with UTC / "GMT" suffix. 14// 15// Composes civil_date.nx. Built from the same primitives as 16// iso8601.nx but with English weekday + month names. 17// 18// Invariants: 19// HD1 Output is exactly 29 bytes. 20// HD2 Parser rejects non-fixdate forms (no rfc850 21// "Sun-day, 06-Nov-94" or asctime "Sun Nov 6 ..." -- 22// per RFC 7231 those are deprecated; we log instead of 23// silently accept). 24// HD3 Round-trip exact for the fixdate form we emit. 25 26// nx_safety_envelope: 27// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 28// sil_target: SIL1 29// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 30// verdict: NOT_YET_EVALUATED 31 32import "nx_syscalls.nx" 33import "nx_civil_date.nx" 34const HD_MAGIC_1994: i64 = 1994 35 36const HD_ERR_FORMAT: i64 = -1 37const HD_ERR_SHORT: i64 = -2 38 39// 3-letter weekday names -- Sunday..Saturday. 9 bytes each slot 40// padded to 4, stored as a flat byte array so index = day * 4. 41func hd_weekday_bytes() -> *u8 { 42 return "Sun Mon Tue Wed Thu Fri Sat " 43} 44 45// 3-letter month names -- month 1..12; index 0 unused. 46func hd_month_bytes() -> *u8 { 47 return " Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec " 48} 49 50// Copy 3 bytes from tbl at index * 4 to out[off]. 51func hd_put3(out: *u8, off: i64, tbl: *u8, idx: i64) -> i64 { 52 out[off] = tbl[idx * 4] 53 out[off + 1] = tbl[idx * 4 + 1] 54 out[off + 2] = tbl[idx * 4 + 2] 55 return off + 3 56} 57 58// 2-digit zero-padded integer. 59func hd_put_2d(out: *u8, off: i64, v: i64) -> i64 { 60 out[off] = 0x30 + ((v / 10) % 10) 61 out[off + 1] = 0x30 + (v % 10) 62 return off + 2 63} 64 65// 4-digit zero-padded year. 66func hd_put_4d(out: *u8, off: i64, v: i64) -> i64 { 67 out[off] = 0x30 + ((v / 1000) % 10) 68 out[off + 1] = 0x30 + ((v / 100) % 10) 69 out[off + 2] = 0x30 + ((v / 10) % 10) 70 out[off + 3] = 0x30 + (v % 10) 71 return off + 4 72} 73 74// Format a CivilDate as IMF-fixdate. Output length = 29 bytes. 75func http_date_format(out: *u8, cap: i64, cd: *CivilDate) -> i64 { 76 if cap < 29 { return HD_ERR_SHORT } 77 var off: i64 = 0 78 off = hd_put3(out, off, hd_weekday_bytes(), cd.weekday) 79 out[off] = 0x2C; off = off + 1 // ',' 80 out[off] = 0x20; off = off + 1 // ' ' 81 off = hd_put_2d(out, off, cd.day) 82 out[off] = 0x20; off = off + 1 83 off = hd_put3(out, off, hd_month_bytes(), cd.month) 84 out[off] = 0x20; off = off + 1 85 off = hd_put_4d(out, off, cd.year) 86 out[off] = 0x20; off = off + 1 87 off = hd_put_2d(out, off, cd.hour) 88 out[off] = 0x3A; off = off + 1 // ':' 89 off = hd_put_2d(out, off, cd.minute) 90 out[off] = 0x3A; off = off + 1 91 off = hd_put_2d(out, off, cd.second) 92 out[off] = 0x20; off = off + 1 93 out[off] = 0x47 // 'G' 94 out[off + 1] = 0x4D // 'M' 95 out[off + 2] = 0x54 // 'T' 96 return 29 97} 98 99// Convenience: format unix seconds directly. 100func http_date_format_unix(out: *u8, cap: i64, unix_sec: i64) -> i64 { 101 let cd_raw: *u8 = sys_mmap(128) 102 let cd: *CivilDate = cd_raw as *CivilDate 103 civil_from_unix(unix_sec, cd) 104 return http_date_format(out, cap, cd) 105} 106 107// Parse 2 decimal digits. 108func hd_parse_2d(buf: *u8, off: i64) -> i64 { 109 let a: i64 = buf[off] 110 let b: i64 = buf[off + 1] 111 if a < 0x30 { return -1 } 112 if a > 0x39 { return -1 } 113 if b < 0x30 { return -1 } 114 if b > 0x39 { return -1 } 115 return (a - 0x30) * 10 + (b - 0x30) 116} 117 118// Parse 4 decimal digits. 119func hd_parse_4d(buf: *u8, off: i64) -> i64 { 120 let hi: i64 = hd_parse_2d(buf, off) 121 if hi < 0 { return -1 } 122 let lo: i64 = hd_parse_2d(buf, off + 2) 123 if lo < 0 { return -1 } 124 return hi * 100 + lo 125} 126 127// Match a 3-byte month name at buf[off]. Returns 1..12 or -1. 128func hd_parse_month(buf: *u8, off: i64) -> i64 { 129 let tbl: *u8 = hd_month_bytes() 130 var i: i64 = 1 131 while i <= 12 { 132 if buf[off] == tbl[i*4] { 133 if buf[off + 1] == tbl[i*4+1] { 134 if buf[off + 2] == tbl[i*4+2] { 135 return i 136 } 137 } 138 } 139 i = i + 1 140 } 141 return -1 142} 143 144// Parse an IMF-fixdate string into *unix_sec_out. 145func http_date_parse(buf: *u8, n: i64, unix_sec_out: *i64) -> i64 { 146 if n < 29 { return HD_ERR_SHORT } 147 // Skip weekday + ", " (5 bytes). 148 let d: i64 = hd_parse_2d(buf, 5) 149 if d < 0 { return HD_ERR_FORMAT } 150 if buf[7] != 0x20 { return HD_ERR_FORMAT } 151 let mo: i64 = hd_parse_month(buf, 8) 152 if mo < 0 { return HD_ERR_FORMAT } 153 if buf[11] != 0x20 { return HD_ERR_FORMAT } 154 let y: i64 = hd_parse_4d(buf, 12) 155 if y < 0 { return HD_ERR_FORMAT } 156 if buf[16] != 0x20 { return HD_ERR_FORMAT } 157 let h: i64 = hd_parse_2d(buf, 17) 158 if h < 0 { return HD_ERR_FORMAT } 159 if buf[19] != 0x3A { return HD_ERR_FORMAT } 160 let mi: i64 = hd_parse_2d(buf, 20) 161 if mi < 0 { return HD_ERR_FORMAT } 162 if buf[22] != 0x3A { return HD_ERR_FORMAT } 163 let s: i64 = hd_parse_2d(buf, 23) 164 if s < 0 { return HD_ERR_FORMAT } 165 if buf[25] != 0x20 { return HD_ERR_FORMAT } 166 if buf[26] != 0x47 { return HD_ERR_FORMAT } // 'G' 167 if buf[27] != 0x4D { return HD_ERR_FORMAT } // 'M' 168 if buf[28] != 0x54 { return HD_ERR_FORMAT } // 'T' 169 170 *unix_sec_out = unix_from_civil(y, mo, d, h, mi, s) 171 return 0 172} 173 174// Compile-only smoke. 175func main() -> i64 { 176 // 1994-11-06 08:49:37 UTC = Sunday. From RFC 7231 §7.1.1. 177 let u: i64 = unix_from_civil(HD_MAGIC_1994, 11, 6, 8, 49, 37) 178 179 let out: *u8 = sys_mmap(64) 180 if http_date_format_unix(out, 64, u) != 29 { return 1 } 181 // Expected: "Sun, 06 Nov 1994 08:49:37 GMT" 182 let expected: *u8 = "Sun, 06 Nov 1994 08:49:37 GMT" 183 var i: i64 = 0 184 while i < 29 { 185 if out[i] != expected[i] { return 10 + i } 186 i = i + 1 187 } 188 189 // Round-trip. 190 let parsed: *i64 = (sys_mmap(16)) as *i64 191 if http_date_parse(out, 29, parsed) != 0 { return 2 } 192 if *parsed != u { return 3 } 193 194 return 0 195}