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}