code wiki / (root) / nx_browse_text.nx

nx_browse_text.nx source

↩ module page · 184 lines · 6465 B

1// nx_browse_text.nx -- production-shaped browser fetch entry point. 2// 3// Compose: TCP connect → send HTTP GET → drain response → HTTP-parse 4// → HTML→text → caller-owned buffer. This is the actual callable 5// API the sovereign browser exposes for text-mode page fetch. 6// 7// Distinct from nx_https_get (HTTPS path is gated on the X.509 8// chain-validation arc, queued separately). This module ships the 9// PLAINTEXT HTTP/1.1 path because every piece it depends on is 10// already proven (TCP loopback works, nx_http_response_parse passes 11// 6 KATs, nx_html_to_text passes 6 KATs). 12// 13// CAPABILITY_COMPLETENESS: PARTIAL. MISSING_CAPABILITIES: 14// - TLS (separate nx_https_get arc, depends on X.509 chain 15// validation) 16// - Redirect following (3xx Location header) 17// - Chunked transfer dechunking before render 18// - gzip / deflate Content-Encoding 19// - HTTP/2 + HTTP/3 20// 21// Per [[feedback-no-skip-paths-as-error-codes]] these are named 22// follow-on arcs, NOT silent gaps. 23// 24// Built per F7 post-order DFS: callees defined before callers. 25// expect_exit: 0 26// license_tier: ORIGINAL 27 28import "nx_syscalls.nx" 29import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host 30import "nx_http_response_parse.nx" 31import "nx_html_to_text.nx" 32const NX_MAGIC_4096: i64 = 4096 33 34// ===== Verdicts =================================================== 35 36const NX_BR_OK: i64 = 0 37const NX_BR_SOCKET_FAIL: i64 = 1 38const NX_BR_CONNECT_FAIL: i64 = 2 39const NX_BR_SEND_FAIL: i64 = 3 40const NX_BR_RECV_FAIL: i64 = 4 41const NX_BR_PARSE_FAIL: i64 = 5 42const NX_BR_STATUS_FAIL: i64 = 6 43const NX_BR_OVERFLOW: i64 = 7 44 45// ===== LEAF: address + I/O ======================================= 46 47func br_addr_ipv4(out: *u8, a: i64, b: i64, c: i64, d: i64, port: i64) -> i64 { 48 out[0] = 2; out[1] = 0 49 out[2] = (port >> 8) & 0xff 50 out[3] = port & 0xff 51 out[4] = a; out[5] = b; out[6] = c; out[7] = d 52 var i: i64 = 8 53 while i < 16 { out[i] = 0; i = i + 1 } 54 return 16 55} 56 57func br_drain(fd: i64, buf: *u8, cap: i64) -> i64 { 58 var off: i64 = 0 59 var keep: i64 = 1 60 while keep == 1 { 61 if off >= cap { keep = 0 } 62 if keep == 1 { 63 let r: i64 = sys_read(fd, buf + off, cap - off) 64 if r <= 0 { keep = 0 } else { off = off + r } 65 } 66 } 67 return off 68} 69 70// Append "GET <path> HTTP/1.1\r\nHost: <host>\r\nConnection: close\r\n\r\n" 71// into req. Returns request length. 72func br_build_get( 73 path: *u8, path_len: i64, 74 host: *u8, host_len: i64, 75 req: *u8 76) -> i64 { 77 var p: i64 = 0 78 req[p]=0x47; p=p+1 // G 79 req[p]=0x45; p=p+1 // E 80 req[p]=0x54; p=p+1 // T 81 req[p]=0x20; p=p+1 // ' ' 82 var i: i64 = 0 83 while i < path_len { req[p]=path[i]; p=p+1; i=i+1 } 84 req[p]=0x20; p=p+1 85 req[p]=0x48; p=p+1 // H 86 req[p]=0x54; p=p+1 // T 87 req[p]=0x54; p=p+1 // T 88 req[p]=0x50; p=p+1 // P 89 req[p]=0x2f; p=p+1 // / 90 req[p]=0x31; p=p+1 // 1 91 req[p]=0x2e; p=p+1 // . 92 req[p]=0x31; p=p+1 // 1 93 req[p]=0x0d; p=p+1 94 req[p]=0x0a; p=p+1 95 req[p]=0x48; p=p+1 // H 96 req[p]=0x6f; p=p+1 // o 97 req[p]=0x73; p=p+1 // s 98 req[p]=0x74; p=p+1 // t 99 req[p]=0x3a; p=p+1 // : 100 req[p]=0x20; p=p+1 101 var j: i64 = 0 102 while j < host_len { req[p]=host[j]; p=p+1; j=j+1 } 103 req[p]=0x0d; p=p+1 104 req[p]=0x0a; p=p+1 105 // "Connection: close\r\n" 106 req[p]=0x43; p=p+1; req[p]=0x6f; p=p+1; req[p]=0x6e; p=p+1 107 req[p]=0x6e; p=p+1; req[p]=0x65; p=p+1; req[p]=0x63; p=p+1 108 req[p]=0x74; p=p+1; req[p]=0x69; p=p+1; req[p]=0x6f; p=p+1 109 req[p]=0x6e; p=p+1; req[p]=0x3a; p=p+1; req[p]=0x20; p=p+1 110 req[p]=0x63; p=p+1; req[p]=0x6c; p=p+1; req[p]=0x6f; p=p+1 111 req[p]=0x73; p=p+1; req[p]=0x65; p=p+1 112 req[p]=0x0d; p=p+1; req[p]=0x0a; p=p+1 113 req[p]=0x0d; p=p+1; req[p]=0x0a; p=p+1 114 return p 115} 116 117// ===== Mid: fetch + parse + render =============================== 118 119// Connect, send GET, drain. Returns bytes received or negative 120// verdict. *socket_out gets the closed fd (informational). 121func br_fetch_raw( 122 addr: *u8, 123 path: *u8, path_len: i64, 124 host: *u8, host_len: i64, 125 out: *u8, out_cap: i64, 126 verdict_out: *i64 127) -> i64 { 128 verdict_out[0] = NX_BR_OK 129 let fd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0) 130 if fd < 0 { verdict_out[0] = NX_BR_SOCKET_FAIL; return 0 - 1 } 131 if nx_connect_bounded(fd, addr, 16, NX_CONN_DEFAULT_MS) < 0 { 132 sys_close(fd); verdict_out[0] = NX_BR_CONNECT_FAIL; return 0 - 1 133 } 134 let req: *u8 = sys_mmap(NX_MAGIC_4096) 135 let req_len: i64 = br_build_get(path, path_len, host, host_len, req) 136 let wr: i64 = sys_write(fd, req, req_len) 137 if wr != req_len { 138 sys_close(fd); verdict_out[0] = NX_BR_SEND_FAIL; return 0 - 1 139 } 140 let got: i64 = br_drain(fd, out, out_cap) 141 sys_close(fd) 142 if got <= 0 { verdict_out[0] = NX_BR_RECV_FAIL; return 0 - 1 } 143 return got 144} 145 146// Parse response in resp[0..resp_len], render body to out, return 147// rendered length or negative verdict. *status_out gets HTTP code. 148func br_render_response( 149 resp: *u8, resp_len: i64, 150 out: *u8, out_cap: i64, 151 status_out: *i64, verdict_out: *i64 152) -> i64 { 153 verdict_out[0] = NX_BR_OK 154 status_out[0] = 0 155 let r: *i64 = nx_http_resp_alloc() 156 let v: i64 = nx_http_response_parse(resp, resp_len, r) 157 if v != NX_HTTP_RESP_OK { verdict_out[0] = NX_BR_PARSE_FAIL; return 0 - 1 } 158 status_out[0] = r[1] 159 if r[7] <= 0 { return 0 } // empty body OK; render nothing 160 let m: i64 = nx_html_to_text(resp + r[6], r[7], out, out_cap) 161 if m < 0 { verdict_out[0] = NX_BR_OVERFLOW; return 0 - 1 } 162 return m 163} 164 165// ===== High: nx_browse_text ====================================== 166 167// Top-level: fetch + render in one call. Returns rendered length or 168// negative verdict. Caller owns out and resp_scratch buffers. 169// 170// scratch must be at least 4096 bytes (raw HTTP response cap). 171// out is the rendered-text destination. 172func nx_browse_text( 173 addr: *u8, 174 path: *u8, path_len: i64, 175 host: *u8, host_len: i64, 176 scratch: *u8, scratch_cap: i64, 177 out: *u8, out_cap: i64, 178 status_out: *i64, verdict_out: *i64 179) -> i64 { 180 let got: i64 = br_fetch_raw(addr, path, path_len, host, host_len, 181 scratch, scratch_cap, verdict_out) 182 if got < 0 { return 0 - 1 } 183 return br_render_response(scratch, got, out, out_cap, status_out, verdict_out) 184}