code wiki / (root) / http_date.nx

http_date.nx source

↩ module page · 188 lines · 6387 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 26import "syscalls.nx" 27import "civil_date.nx" 28 29const HD_ERR_FORMAT: i64 = -1 30const HD_ERR_SHORT: i64 = -2 31 32// 3-letter weekday names -- Sunday..Saturday. 9 bytes each slot 33// padded to 4, stored as a flat byte array so index = day * 4. 34func hd_weekday_bytes() -> *u8 { 35 return "Sun Mon Tue Wed Thu Fri Sat " 36} 37 38// 3-letter month names -- month 1..12; index 0 unused. 39func hd_month_bytes() -> *u8 { 40 return " Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec " 41} 42 43// Copy 3 bytes from tbl at index * 4 to out[off]. 44func hd_put3(out: *u8, off: i64, tbl: *u8, idx: i64) -> i64 { 45 out[off] = tbl[idx * 4] 46 out[off + 1] = tbl[idx * 4 + 1] 47 out[off + 2] = tbl[idx * 4 + 2] 48 return off + 3 49} 50 51// 2-digit zero-padded integer. 52func hd_put_2d(out: *u8, off: i64, v: i64) -> i64 { 53 out[off] = 0x30 + ((v / 10) % 10) 54 out[off + 1] = 0x30 + (v % 10) 55 return off + 2 56} 57 58// 4-digit zero-padded year. 59func hd_put_4d(out: *u8, off: i64, v: i64) -> i64 { 60 out[off] = 0x30 + ((v / 1000) % 10) 61 out[off + 1] = 0x30 + ((v / 100) % 10) 62 out[off + 2] = 0x30 + ((v / 10) % 10) 63 out[off + 3] = 0x30 + (v % 10) 64 return off + 4 65} 66 67// Format a CivilDate as IMF-fixdate. Output length = 29 bytes. 68func http_date_format(out: *u8, cap: i64, cd: *CivilDate) -> i64 { 69 if cap < 29 { return HD_ERR_SHORT } 70 var off: i64 = 0 71 off = hd_put3(out, off, hd_weekday_bytes(), cd.weekday) 72 out[off] = 0x2C; off = off + 1 // ',' 73 out[off] = 0x20; off = off + 1 // ' ' 74 off = hd_put_2d(out, off, cd.day) 75 out[off] = 0x20; off = off + 1 76 off = hd_put3(out, off, hd_month_bytes(), cd.month) 77 out[off] = 0x20; off = off + 1 78 off = hd_put_4d(out, off, cd.year) 79 out[off] = 0x20; off = off + 1 80 off = hd_put_2d(out, off, cd.hour) 81 out[off] = 0x3A; off = off + 1 // ':' 82 off = hd_put_2d(out, off, cd.minute) 83 out[off] = 0x3A; off = off + 1 84 off = hd_put_2d(out, off, cd.second) 85 out[off] = 0x20; off = off + 1 86 out[off] = 0x47 // 'G' 87 out[off + 1] = 0x4D // 'M' 88 out[off + 2] = 0x54 // 'T' 89 return 29 90} 91 92// Convenience: format unix seconds directly. 93func http_date_format_unix(out: *u8, cap: i64, unix_sec: i64) -> i64 { 94 let cd_raw: *u8 = sys_mmap(128) 95 let cd: *CivilDate = cd_raw as *CivilDate 96 civil_from_unix(unix_sec, cd) 97 return http_date_format(out, cap, cd) 98} 99 100// Parse 2 decimal digits. 101func hd_parse_2d(buf: *u8, off: i64) -> i64 { 102 let a: i64 = buf[off] 103 let b: i64 = buf[off + 1] 104 if a < 0x30 { return -1 } 105 if a > 0x39 { return -1 } 106 if b < 0x30 { return -1 } 107 if b > 0x39 { return -1 } 108 return (a - 0x30) * 10 + (b - 0x30) 109} 110 111// Parse 4 decimal digits. 112func hd_parse_4d(buf: *u8, off: i64) -> i64 { 113 let hi: i64 = hd_parse_2d(buf, off) 114 if hi < 0 { return -1 } 115 let lo: i64 = hd_parse_2d(buf, off + 2) 116 if lo < 0 { return -1 } 117 return hi * 100 + lo 118} 119 120// Match a 3-byte month name at buf[off]. Returns 1..12 or -1. 121func hd_parse_month(buf: *u8, off: i64) -> i64 { 122 let tbl: *u8 = hd_month_bytes() 123 var i: i64 = 1 124 while i <= 12 { 125 if buf[off] == tbl[i*4] { 126 if buf[off + 1] == tbl[i*4+1] { 127 if buf[off + 2] == tbl[i*4+2] { 128 return i 129 } 130 } 131 } 132 i = i + 1 133 } 134 return -1 135} 136 137// Parse an IMF-fixdate string into *unix_sec_out. 138func http_date_parse(buf: *u8, n: i64, unix_sec_out: *i64) -> i64 { 139 if n < 29 { return HD_ERR_SHORT } 140 // Skip weekday + ", " (5 bytes). 141 let d: i64 = hd_parse_2d(buf, 5) 142 if d < 0 { return HD_ERR_FORMAT } 143 if buf[7] != 0x20 { return HD_ERR_FORMAT } 144 let mo: i64 = hd_parse_month(buf, 8) 145 if mo < 0 { return HD_ERR_FORMAT } 146 if buf[11] != 0x20 { return HD_ERR_FORMAT } 147 let y: i64 = hd_parse_4d(buf, 12) 148 if y < 0 { return HD_ERR_FORMAT } 149 if buf[16] != 0x20 { return HD_ERR_FORMAT } 150 let h: i64 = hd_parse_2d(buf, 17) 151 if h < 0 { return HD_ERR_FORMAT } 152 if buf[19] != 0x3A { return HD_ERR_FORMAT } 153 let mi: i64 = hd_parse_2d(buf, 20) 154 if mi < 0 { return HD_ERR_FORMAT } 155 if buf[22] != 0x3A { return HD_ERR_FORMAT } 156 let s: i64 = hd_parse_2d(buf, 23) 157 if s < 0 { return HD_ERR_FORMAT } 158 if buf[25] != 0x20 { return HD_ERR_FORMAT } 159 if buf[26] != 0x47 { return HD_ERR_FORMAT } // 'G' 160 if buf[27] != 0x4D { return HD_ERR_FORMAT } // 'M' 161 if buf[28] != 0x54 { return HD_ERR_FORMAT } // 'T' 162 163 *unix_sec_out = unix_from_civil(y, mo, d, h, mi, s) 164 return 0 165} 166 167// Compile-only smoke. 168func main() -> i64 { 169 // 1994-11-06 08:49:37 UTC = Sunday. From RFC 7231 §7.1.1. 170 let u: i64 = unix_from_civil(1994, 11, 6, 8, 49, 37) 171 172 let out: *u8 = sys_mmap(64) 173 if http_date_format_unix(out, 64, u) != 29 { return 1 } 174 // Expected: "Sun, 06 Nov 1994 08:49:37 GMT" 175 let expected: *u8 = "Sun, 06 Nov 1994 08:49:37 GMT" 176 var i: i64 = 0 177 while i < 29 { 178 if out[i] != expected[i] { return 10 + i } 179 i = i + 1 180 } 181 182 // Round-trip. 183 let parsed: *i64 = (sys_mmap(16)) as *i64 184 if http_date_parse(out, 29, parsed) != 0 { return 2 } 185 if *parsed != u { return 3 } 186 187 return 0 188}