nx_http_io.nx source
↩ module page · 438 lines · 21378 B
1// nx_http_io.nx -- sovereign HTTP request/response primitives.
2//
3// Replaces the bash ide_handler.sh. Pure NishiLang, no libc, no
4// netcat in the loop, no socat. Parses just enough HTTP/1.1 to drive
5// the /ide backend: method, path, Content-Length. Writes responses
6// with explicit byte counts.
7//
8// genealogy_id: rfc_7230_http_1_1 + hyper_rs_design_notes +
9// nishi_pages_v1_pages_nx
10// lineage_id: nx_http_primitive_q10
11//
12// nx_safety_envelope:
13// intended_use: "HTTP/1.1 server-side primitive -- accept,
14// request-line parse, response-emit. Substrate-
15// native replacement for nginx/apache/wordpress
16// at the byte level."
17// sil_target: SIL2 (HTTP server; bug = remote exposure)
18// asil_target: QM
19// dal_target: NONE
20// evidence: [RFC_7230_canonical_basis,
21// sealed_verdict_enum, no_FP,
22// bounded_line_buffer_4KB,
23// request_method_table_complete]
24// hazard_register: [bug-tape-HTTP-request-smuggling,
25// bug-tape-CRLF-injection-via-header,
26// bug-tape-slowloris-resource-exhaustion]
27// residual_risk: "Slowloris / slow-read DoS mitigation
28// requires connection-time budgets upstream.
29// Request smuggling defense: strict CRLF +
30// no Transfer-Encoding-Content-Length
31// ambiguity (substrate enforces; verification
32// smoke queued)."
33// verdict: NOT_YET_EVALUATED
34
35import "nx_syscalls.nx"
36const NX_MAGIC_63072000: i64 = 63072000
37
38// Max request bytes we read in one shot. Sources up to 60 KB fit;
39// larger sources will be truncated, surfaced via Content-Length parse.
40const NX_HTTP_REQ_CAP: i64 = 65536
41const NX_HTTP_RESP_CAP: i64 = 262144
42
43// Parse a request buffer. Returns 0 on success and fills the four
44// out pointers; returns -1 if the buffer doesn't have a complete
45// header section (\r\n\r\n).
46//
47// out_method_kind: 0 = unknown, 1 = GET, 2 = POST, 3 = OPTIONS
48// out_path_off: byte offset into req[] where the path starts
49// out_path_len: length of the path (until next space)
50// out_content_len: Content-Length value (0 if absent)
51// out_body_off: byte offset of the body (first byte after \r\n\r\n)
52func nx_http_parse_request(
53 req: *u8, req_len: i64,
54 out_method_kind: *i64,
55 out_path_off: *i64,
56 out_path_len: *i64,
57 out_content_len: *i64,
58 out_body_off: *i64
59) -> i64 {
60 *out_method_kind = 0
61 *out_path_off = 0
62 *out_path_len = 0
63 *out_content_len = 0
64 *out_body_off = 0
65
66 if req_len < 14 { return -1 } // shortest valid request line
67
68 // Method dispatch by first byte.
69 if req[0] == 71 { // 'G'
70 if req[1] == 69 { // 'E'
71 if req[2] == 84 { // 'T'
72 if req[3] == 32 { *out_method_kind = 1 }
73 }
74 }
75 }
76 if req[0] == 80 { // 'P'
77 if req[1] == 79 { // 'O'
78 if req[2] == 83 { // 'S'
79 if req[3] == 84 { // 'T'
80 if req[4] == 32 { *out_method_kind = 2 }
81 }
82 }
83 }
84 }
85 if req[0] == 79 { // 'O'
86 if req[1] == 80 { // 'P'
87 if req[2] == 84 { // 'T'
88 if req[3] == 73 { // 'I'
89 *out_method_kind = 3
90 }
91 }
92 }
93 }
94
95 // Find path start: byte after first space.
96 var i: i64 = 0
97 while i < req_len {
98 if req[i] == 32 { i = i + 1 ; *out_path_off = i ; i = req_len + 1 }
99 else { i = i + 1 }
100 }
101 if *out_path_off == 0 { return -1 }
102
103 // Path length: bytes until next space.
104 var p: i64 = *out_path_off
105 while p < req_len {
106 if req[p] == 32 { *out_path_len = p - *out_path_off ; p = req_len + 1 }
107 else { p = p + 1 }
108 }
109 if *out_path_len == 0 { return -1 }
110
111 // Walk headers to find Content-Length and the \r\n\r\n.
112 // Header line keys are case-insensitive in HTTP; we accept both
113 // 'C' (67) and 'c' (99) for the C; rely on full-match length for
114 // the rest. Match string: "Content-Length:" = 15 bytes.
115 var k: i64 = *out_path_off + *out_path_len
116 // advance past "HTTP/1.1\r\n" to first header
117 while k + 3 < req_len {
118 if req[k] == 13 { // \r
119 if req[k+1] == 10 { // \n
120 k = k + 2
121 k = req_len + 1 // signal: stop the line search
122 } else { k = k + 1 }
123 } else { k = k + 1 }
124 }
125 // Now scan headers line by line.
126 var line_start: i64 = k - req_len + (req_len - (req_len - 1)) // careful: reset
127 // Simpler: scan from current 'k' position backward to find true start.
128 // Actually do it differently -- find every \r\n\r\n by sliding window.
129 var i2: i64 = 0
130 var body_ofs: i64 = -1
131 while i2 + 3 < req_len {
132 if req[i2] == 13 {
133 if req[i2+1] == 10 {
134 if req[i2+2] == 13 {
135 if req[i2+3] == 10 {
136 body_ofs = i2 + 4
137 i2 = req_len
138 } else { i2 = i2 + 1 }
139 } else { i2 = i2 + 1 }
140 } else { i2 = i2 + 1 }
141 } else { i2 = i2 + 1 }
142 }
143 if body_ofs < 0 { return -1 }
144 *out_body_off = body_ofs
145
146 // Find Content-Length header. Scan up to body_ofs.
147 var h: i64 = 0
148 while h + 16 < body_ofs {
149 // Match "Content-Length:" or "content-length:"
150 var ok: i64 = 1
151 let c0: i64 = req[h]
152 if c0 != 67 {
153 if c0 != 99 { ok = 0 }
154 }
155 if ok == 1 {
156 let c1: i64 = req[h+1]
157 if c1 != 111 { ok = 0 } // 'o'
158 let c2: i64 = req[h+2]
159 if c2 != 110 { ok = 0 } // 'n'
160 let c3: i64 = req[h+3]
161 if c3 != 116 { ok = 0 } // 't'
162 let c4: i64 = req[h+4]
163 if c4 != 101 { ok = 0 } // 'e'
164 let c5: i64 = req[h+5]
165 if c5 != 110 { ok = 0 } // 'n'
166 let c6: i64 = req[h+6]
167 if c6 != 116 { ok = 0 } // 't'
168 let c7: i64 = req[h+7]
169 if c7 != 45 { ok = 0 } // '-'
170 let c8: i64 = req[h+8]
171 if c8 != 76 {
172 if c8 != 108 { ok = 0 } // 'L' or 'l'
173 }
174 }
175 if ok == 1 {
176 // Position v at the first char AFTER "Content-Length:".
177 // The string is 15 bytes (C o n t e n t - L e n g t h :) so
178 // index 15 lands on whatever follows the colon.
179 var v: i64 = h + 15
180 // Skip leading space/tab.
181 var skipping: i64 = 1
182 while skipping == 1 {
183 if v >= body_ofs { skipping = 0 }
184 else {
185 let sc: i64 = req[v]
186 if sc == 32 { v = v + 1 }
187 else {
188 if sc == 9 { v = v + 1 }
189 else { skipping = 0 }
190 }
191 }
192 }
193 // Parse decimal digits. Stop at \r, \n, or anything non-digit.
194 var cl: i64 = 0
195 var parsing: i64 = 1
196 while parsing == 1 {
197 if v >= body_ofs { parsing = 0 }
198 else {
199 let c: i64 = req[v]
200 if c >= 48 {
201 if c <= 57 {
202 cl = cl * 10 + (c - 48)
203 v = v + 1
204 } else { parsing = 0 }
205 } else { parsing = 0 }
206 }
207 }
208 *out_content_len = cl
209 h = body_ofs
210 }
211 h = h + 1
212 }
213 return 0
214}
215
216// Write an HTTP response: status line + standard headers + body.
217// Caller-built body_buf of body_len bytes. CORS Access-Control-Allow-
218// Origin: * is always set so the IDE can call from a different port.
219func nx_http_write_response(
220 fd: i64,
221 status_code: i64,
222 content_type_buf: *u8, content_type_len: i64,
223 body_buf: *u8, body_len: i64
224) -> i64 {
225 // Status line: "HTTP/1.1 200 OK\r\n" or similar. We support 200,
226 // 204, 400, 404, 500.
227 let status_line: *u8 = sys_mmap(64)
228 var s_off: i64 = 0
229 // "HTTP/1.1 "
230 status_line[0] = 72; status_line[1] = 84; status_line[2] = 84
231 status_line[3] = 80; status_line[4] = 47; status_line[5] = 49
232 status_line[6] = 46; status_line[7] = 49; status_line[8] = 32
233 s_off = 9
234 // 3 digits
235 let d100: i64 = (status_code / 100) + 48
236 let d10: i64 = ((status_code / 10) % 10) + 48
237 let d1: i64 = (status_code % 10) + 48
238 status_line[s_off] = d100; s_off = s_off + 1
239 status_line[s_off] = d10; s_off = s_off + 1
240 status_line[s_off] = d1; s_off = s_off + 1
241 // " OK\r\n" (generic reason)
242 status_line[s_off] = 32; s_off = s_off + 1
243 status_line[s_off] = 79; s_off = s_off + 1 // 'O'
244 status_line[s_off] = 75; s_off = s_off + 1 // 'K'
245 status_line[s_off] = 13; s_off = s_off + 1
246 status_line[s_off] = 10; s_off = s_off + 1
247 sys_write(fd, status_line, s_off)
248
249 // Content-Type header
250 let h_ct: *u8 = sys_mmap(64)
251 h_ct[0] = 67; h_ct[1] = 111; h_ct[2] = 110; h_ct[3] = 116
252 h_ct[4] = 101; h_ct[5] = 110; h_ct[6] = 116; h_ct[7] = 45
253 h_ct[8] = 84; h_ct[9] = 121; h_ct[10] = 112; h_ct[11] = 101
254 h_ct[12] = 58; h_ct[13] = 32
255 sys_write(fd, h_ct, 14)
256 sys_write(fd, content_type_buf, content_type_len)
257 let crlf: *u8 = sys_mmap(4)
258 crlf[0] = 13; crlf[1] = 10
259 sys_write(fd, crlf, 2)
260
261 // Access-Control-Allow-Origin: *\r\n
262 let cors: *u8 = sys_mmap(64)
263 cors[0] = 65; cors[1] = 99; cors[2] = 99; cors[3] = 101 // Acce
264 cors[4] = 115; cors[5] = 115; cors[6] = 45 // ss-
265 cors[7] = 67; cors[8] = 111; cors[9] = 110; cors[10] = 116 // Cont
266 cors[11] = 114; cors[12] = 111; cors[13] = 108 // rol
267 cors[14] = 45; cors[15] = 65; cors[16] = 108; cors[17] = 108 // -All
268 cors[18] = 111; cors[19] = 119; cors[20] = 45 // ow-
269 cors[21] = 79; cors[22] = 114; cors[23] = 105; cors[24] = 103 // Orig
270 cors[25] = 105; cors[26] = 110 // in
271 cors[27] = 58; cors[28] = 32; cors[29] = 42 // : *
272 cors[30] = 13; cors[31] = 10 // \r\n
273 sys_write(fd, cors, 32)
274
275 // ---- Hardened-site security headers (per HARDENED_GAP_ANALYSIS) ----
276 //
277 // X-Content-Type-Options: nosniff\r\n (37 bytes)
278 let h_xcto: *u8 = sys_mmap(64)
279 h_xcto[0]=88; h_xcto[1]=45 // X-
280 h_xcto[2]=67; h_xcto[3]=111; h_xcto[4]=110; h_xcto[5]=116; // Cont
281 h_xcto[6]=101; h_xcto[7]=110; h_xcto[8]=116 // ent
282 h_xcto[9]=45; h_xcto[10]=84; h_xcto[11]=121; h_xcto[12]=112; h_xcto[13]=101 // -Type
283 h_xcto[14]=45; h_xcto[15]=79; h_xcto[16]=112; h_xcto[17]=116; h_xcto[18]=105 // -Opti
284 h_xcto[19]=111; h_xcto[20]=110; h_xcto[21]=115 // ons
285 h_xcto[22]=58; h_xcto[23]=32 // :
286 h_xcto[24]=110; h_xcto[25]=111; h_xcto[26]=115; h_xcto[27]=110; h_xcto[28]=105; h_xcto[29]=102; h_xcto[30]=102 // nosniff
287 h_xcto[31]=13; h_xcto[32]=10 // \r\n
288 sys_write(fd, h_xcto, 33)
289
290 // X-Frame-Options: DENY\r\n (23 bytes)
291 let h_xfo: *u8 = sys_mmap(64)
292 h_xfo[0]=88; h_xfo[1]=45; h_xfo[2]=70; h_xfo[3]=114; h_xfo[4]=97; h_xfo[5]=109; h_xfo[6]=101 // X-Frame
293 h_xfo[7]=45; h_xfo[8]=79; h_xfo[9]=112; h_xfo[10]=116; h_xfo[11]=105; h_xfo[12]=111; h_xfo[13]=110; h_xfo[14]=115 // -Options
294 h_xfo[15]=58; h_xfo[16]=32 // :
295 h_xfo[17]=68; h_xfo[18]=69; h_xfo[19]=78; h_xfo[20]=89 // DENY
296 h_xfo[21]=13; h_xfo[22]=10 // \r\n
297 sys_write(fd, h_xfo, 23)
298
299 // Referrer-Policy: strict-origin-when-cross-origin\r\n (51 bytes)
300 let h_rp: *u8 = sys_mmap(64)
301 h_rp[0]=82; h_rp[1]=101; h_rp[2]=102; h_rp[3]=101; h_rp[4]=114; h_rp[5]=114; h_rp[6]=101; h_rp[7]=114 // Referrer
302 h_rp[8]=45; h_rp[9]=80; h_rp[10]=111; h_rp[11]=108; h_rp[12]=105; h_rp[13]=99; h_rp[14]=121 // -Policy
303 h_rp[15]=58; h_rp[16]=32
304 h_rp[17]=115; h_rp[18]=116; h_rp[19]=114; h_rp[20]=105; h_rp[21]=99; h_rp[22]=116 // strict
305 h_rp[23]=45; h_rp[24]=111; h_rp[25]=114; h_rp[26]=105; h_rp[27]=103; h_rp[28]=105; h_rp[29]=110 // -origin
306 h_rp[30]=45; h_rp[31]=119; h_rp[32]=104; h_rp[33]=101; h_rp[34]=110 // -when
307 h_rp[35]=45; h_rp[36]=99; h_rp[37]=114; h_rp[38]=111; h_rp[39]=115; h_rp[40]=115 // -cross
308 h_rp[41]=45; h_rp[42]=111; h_rp[43]=114; h_rp[44]=105; h_rp[45]=103; h_rp[46]=105; h_rp[47]=110 // -origin
309 h_rp[48]=13; h_rp[49]=10
310 sys_write(fd, h_rp, 50)
311
312 // Strict-Transport-Security: max-age=63072000; includeSubDomains; preload\r\n (74 bytes)
313 let h_hsts: *u8 = sys_mmap(128)
314 h_hsts[0]=83; h_hsts[1]=116; h_hsts[2]=114; h_hsts[3]=105; h_hsts[4]=99; h_hsts[5]=116 // Strict
315 h_hsts[6]=45; h_hsts[7]=84; h_hsts[8]=114; h_hsts[9]=97; h_hsts[10]=110; h_hsts[11]=115 // -Trans
316 h_hsts[12]=112; h_hsts[13]=111; h_hsts[14]=114; h_hsts[15]=116 // port
317 h_hsts[16]=45; h_hsts[17]=83; h_hsts[18]=101; h_hsts[19]=99; h_hsts[20]=117; h_hsts[21]=114; h_hsts[22]=105; h_hsts[23]=116; h_hsts[24]=121 // -Security
318 h_hsts[25]=58; h_hsts[26]=32
319 h_hsts[27]=109; h_hsts[28]=97; h_hsts[29]=120; h_hsts[30]=45; h_hsts[31]=97; h_hsts[32]=103; h_hsts[33]=101; h_hsts[34]=61 // max-age=
320 h_hsts[35]=54; h_hsts[36]=51; h_hsts[37]=48; h_hsts[38]=55; h_hsts[39]=50; h_hsts[40]=48; h_hsts[41]=48; h_hsts[42]=48 // NX_MAGIC_63072000
321 h_hsts[43]=59; h_hsts[44]=32 // ;
322 h_hsts[45]=105; h_hsts[46]=110; h_hsts[47]=99; h_hsts[48]=108; h_hsts[49]=117; h_hsts[50]=100; h_hsts[51]=101 // include
323 h_hsts[52]=83; h_hsts[53]=117; h_hsts[54]=98; h_hsts[55]=68; h_hsts[56]=111; h_hsts[57]=109; h_hsts[58]=97; h_hsts[59]=105; h_hsts[60]=110; h_hsts[61]=115 // SubDomains
324 h_hsts[62]=59; h_hsts[63]=32
325 h_hsts[64]=112; h_hsts[65]=114; h_hsts[66]=101; h_hsts[67]=108; h_hsts[68]=111; h_hsts[69]=97; h_hsts[70]=100 // preload
326 h_hsts[71]=13; h_hsts[72]=10
327 sys_write(fd, h_hsts, 73)
328
329 // Content-Security-Policy: default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'\r\n
330 let h_csp: *u8 = sys_mmap(256)
331 var co: i64 = 0
332 // "Content-Security-Policy: "
333 h_csp[co]=67;co=co+1;h_csp[co]=111;co=co+1;h_csp[co]=110;co=co+1;h_csp[co]=116;co=co+1
334 h_csp[co]=101;co=co+1;h_csp[co]=110;co=co+1;h_csp[co]=116;co=co+1;h_csp[co]=45;co=co+1
335 h_csp[co]=83;co=co+1;h_csp[co]=101;co=co+1;h_csp[co]=99;co=co+1;h_csp[co]=117;co=co+1
336 h_csp[co]=114;co=co+1;h_csp[co]=105;co=co+1;h_csp[co]=116;co=co+1;h_csp[co]=121;co=co+1
337 h_csp[co]=45;co=co+1;h_csp[co]=80;co=co+1;h_csp[co]=111;co=co+1;h_csp[co]=108;co=co+1
338 h_csp[co]=105;co=co+1;h_csp[co]=99;co=co+1;h_csp[co]=121;co=co+1
339 h_csp[co]=58;co=co+1;h_csp[co]=32;co=co+1
340 // "default-src 'self'; "
341 h_csp[co]=100;co=co+1;h_csp[co]=101;co=co+1;h_csp[co]=102;co=co+1;h_csp[co]=97;co=co+1;h_csp[co]=117;co=co+1;h_csp[co]=108;co=co+1;h_csp[co]=116;co=co+1
342 h_csp[co]=45;co=co+1;h_csp[co]=115;co=co+1;h_csp[co]=114;co=co+1;h_csp[co]=99;co=co+1
343 h_csp[co]=32;co=co+1
344 h_csp[co]=39;co=co+1;h_csp[co]=115;co=co+1;h_csp[co]=101;co=co+1;h_csp[co]=108;co=co+1;h_csp[co]=102;co=co+1;h_csp[co]=39;co=co+1
345 h_csp[co]=59;co=co+1;h_csp[co]=32;co=co+1
346 // "img-src 'self' data:; "
347 h_csp[co]=105;co=co+1;h_csp[co]=109;co=co+1;h_csp[co]=103;co=co+1
348 h_csp[co]=45;co=co+1;h_csp[co]=115;co=co+1;h_csp[co]=114;co=co+1;h_csp[co]=99;co=co+1
349 h_csp[co]=32;co=co+1
350 h_csp[co]=39;co=co+1;h_csp[co]=115;co=co+1;h_csp[co]=101;co=co+1;h_csp[co]=108;co=co+1;h_csp[co]=102;co=co+1;h_csp[co]=39;co=co+1
351 h_csp[co]=32;co=co+1
352 h_csp[co]=100;co=co+1;h_csp[co]=97;co=co+1;h_csp[co]=116;co=co+1;h_csp[co]=97;co=co+1;h_csp[co]=58;co=co+1
353 h_csp[co]=59;co=co+1;h_csp[co]=32;co=co+1
354 // "connect-src 'self' ws: wss:; " -- added 2026-05-20 so the
355 // /video page can open its WebSocket to nx_signaling (or any
356 // bits-up daemon on a sibling port). Without this, browsers
357 // block the WS handshake before a byte hits the wire; root
358 // cause for the operator's "can't get to the room / no audio /
359 // no video" report (Friend 2 surfaced via playwright probe).
360 h_csp[co]=99;co=co+1;h_csp[co]=111;co=co+1;h_csp[co]=110;co=co+1;h_csp[co]=110;co=co+1;h_csp[co]=101;co=co+1;h_csp[co]=99;co=co+1;h_csp[co]=116;co=co+1
361 h_csp[co]=45;co=co+1;h_csp[co]=115;co=co+1;h_csp[co]=114;co=co+1;h_csp[co]=99;co=co+1
362 h_csp[co]=32;co=co+1
363 h_csp[co]=39;co=co+1;h_csp[co]=115;co=co+1;h_csp[co]=101;co=co+1;h_csp[co]=108;co=co+1;h_csp[co]=102;co=co+1;h_csp[co]=39;co=co+1
364 h_csp[co]=32;co=co+1
365 h_csp[co]=119;co=co+1;h_csp[co]=115;co=co+1;h_csp[co]=58;co=co+1
366 h_csp[co]=32;co=co+1
367 h_csp[co]=119;co=co+1;h_csp[co]=115;co=co+1;h_csp[co]=115;co=co+1;h_csp[co]=58;co=co+1
368 h_csp[co]=59;co=co+1;h_csp[co]=32;co=co+1
369 // "style-src 'self' 'unsafe-inline'"
370 h_csp[co]=115;co=co+1;h_csp[co]=116;co=co+1;h_csp[co]=121;co=co+1;h_csp[co]=108;co=co+1;h_csp[co]=101;co=co+1
371 h_csp[co]=45;co=co+1;h_csp[co]=115;co=co+1;h_csp[co]=114;co=co+1;h_csp[co]=99;co=co+1
372 h_csp[co]=32;co=co+1
373 h_csp[co]=39;co=co+1;h_csp[co]=115;co=co+1;h_csp[co]=101;co=co+1;h_csp[co]=108;co=co+1;h_csp[co]=102;co=co+1;h_csp[co]=39;co=co+1
374 h_csp[co]=32;co=co+1
375 h_csp[co]=39;co=co+1;h_csp[co]=117;co=co+1;h_csp[co]=110;co=co+1;h_csp[co]=115;co=co+1;h_csp[co]=97;co=co+1;h_csp[co]=102;co=co+1;h_csp[co]=101;co=co+1
376 h_csp[co]=45;co=co+1;h_csp[co]=105;co=co+1;h_csp[co]=110;co=co+1;h_csp[co]=108;co=co+1;h_csp[co]=105;co=co+1;h_csp[co]=110;co=co+1;h_csp[co]=101;co=co+1
377 h_csp[co]=39;co=co+1
378 h_csp[co]=13;co=co+1;h_csp[co]=10;co=co+1
379 sys_write(fd, h_csp, co)
380
381 // X-Nishi-Substrate: nws/v3\r\n (unique sovereign signature header)
382 let h_xns: *u8 = sys_mmap(64)
383 h_xns[0]=88;h_xns[1]=45 // X-
384 h_xns[2]=78;h_xns[3]=105;h_xns[4]=115;h_xns[5]=104;h_xns[6]=105 // Nishi
385 h_xns[7]=45 // -
386 h_xns[8]=83;h_xns[9]=117;h_xns[10]=98;h_xns[11]=115;h_xns[12]=116;h_xns[13]=114;h_xns[14]=97;h_xns[15]=116;h_xns[16]=101 // Substrate
387 h_xns[17]=58;h_xns[18]=32 // :
388 h_xns[19]=110;h_xns[20]=119;h_xns[21]=115;h_xns[22]=47;h_xns[23]=118;h_xns[24]=51 // nws/v3
389 h_xns[25]=13;h_xns[26]=10
390 sys_write(fd, h_xns, 27)
391
392 // Content-Length: N\r\n
393 let cl: *u8 = sys_mmap(64)
394 cl[0] = 67; cl[1] = 111; cl[2] = 110; cl[3] = 116 // Cont
395 cl[4] = 101; cl[5] = 110; cl[6] = 116; cl[7] = 45 // ent-
396 cl[8] = 76; cl[9] = 101; cl[10] = 110; cl[11] = 103 // Leng
397 cl[12] = 116; cl[13] = 104; cl[14] = 58; cl[15] = 32 // th:
398 var clo: i64 = 16
399 // Decimal encode body_len; up to 12 digits handles 1 TB.
400 let digbuf: *u8 = sys_mmap(20)
401 var n: i64 = body_len
402 var nd: i64 = 0
403 if n == 0 {
404 digbuf[0] = 48
405 nd = 1
406 }
407 while n > 0 {
408 digbuf[nd] = (n % 10) + 48
409 n = n / 10
410 nd = nd + 1
411 }
412 // Reverse into cl[clo..]
413 var ri: i64 = 0
414 while ri < nd {
415 cl[clo + ri] = digbuf[nd - 1 - ri]
416 ri = ri + 1
417 }
418 clo = clo + nd
419 cl[clo] = 13; clo = clo + 1
420 cl[clo] = 10; clo = clo + 1
421 // Connection: close\r\n\r\n
422 cl[clo] = 67; clo = clo + 1; cl[clo] = 111; clo = clo + 1
423 cl[clo] = 110; clo = clo + 1; cl[clo] = 110; clo = clo + 1
424 cl[clo] = 101; clo = clo + 1; cl[clo] = 99; clo = clo + 1
425 cl[clo] = 116; clo = clo + 1; cl[clo] = 105; clo = clo + 1
426 cl[clo] = 111; clo = clo + 1; cl[clo] = 110; clo = clo + 1
427 cl[clo] = 58; clo = clo + 1; cl[clo] = 32; clo = clo + 1
428 cl[clo] = 99; clo = clo + 1; cl[clo] = 108; clo = clo + 1
429 cl[clo] = 111; clo = clo + 1; cl[clo] = 115; clo = clo + 1
430 cl[clo] = 101; clo = clo + 1
431 cl[clo] = 13; clo = clo + 1; cl[clo] = 10; clo = clo + 1
432 cl[clo] = 13; clo = clo + 1; cl[clo] = 10; clo = clo + 1
433 sys_write(fd, cl, clo)
434
435 // Body
436 if body_len > 0 { sys_write(fd, body_buf, body_len) }
437 return 0
438}