code wiki / _hdl_build / nx_onsite_api.nx

nx_onsite_api.nx source

↩ module page · 167 lines · 8749 B

1// nx_onsite_api.nx -- the SAME-ORIGIN HTTP face of the reusable onsite search, so <nishi-embed> can actually 2// search in a real browser. Two routes: 3// GET / -> the <nishi-embed> demo page (web_assets/nishi_embed_demo.html) 4// GET /api/onsite?site=&q= -> {"items":[{"url":..,"title":..}]} (what <nishi-embed> consumes) 5// Search REUSES the proven client (/tmp/nx_onsite_search.sov.elf) by fork+exec over the sites registry 6// (knowledge/index/onsite_sites.tsv) -- ZERO search reimplementation; the daemon just wraps it as JSON. Page + 7// API are the SAME origin (the embed's same-origin, credentials-omitted fetch = no cross-origin, no tracking). 8// Usage: nx_onsite_api [port] (default 8096). license_tier: ORIGINAL 9import "nx_syscalls.nx" 10const OA_MAGIC_8096: i64 = 8096 11const OA_MAGIC_16384: i64 = 16384 12const OA_MAGIC_262144: i64 = 262144 13 14const OA_REG: *u8 = "knowledge/index/onsite_sites.tsv" as *u8 15const OA_PAGE: *u8 = "web_assets/nishi_embed_demo.html" as *u8 16 17func oa_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){dst[off+i]=s[i];i=i+1} return off+i } 18func oa_u(dst: *u8, off: i64, v: i64) -> i64 { var m: i64=v; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1}; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}; var o: i64=off; var i: i64=0; while i<k{dst[o]=t[k-1-i];o=o+1;i=i+1} return o } 19func oa_strlen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 20 21// find value of `key` (e.g. "q=") in the request line; copy to out (URL-decode +/%20 -> space); return len. 22func oa_param(req: *u8, rn: i64, key: *u8, out: *u8, cap: i64) -> i64 { 23 let kl: i64=oa_strlen(key) 24 var i: i64=0; var at: i64=0-1; var stop: i64=rn 25 while i<stop { 26 if req[i]==(10 as u8) { stop=i } else { if req[i]==(13 as u8) { stop=i } else { 27 if at<0 { if i+kl<=rn { var j: i64=0; var ok: i64=1; while j<kl { if req[i+j]!=key[j]{ok=0;j=kl} else {j=j+1} } if ok==1 { at=i+kl } } } 28 i=i+1 29 } } 30 } 31 if at<0 { return 0 } 32 var o: i64=0; var p: i64=at 33 while p<rn { 34 let c: i64=req[p] as i64 35 if c==38 { p=rn } else { if c==32 { p=rn } else { if c==10 { p=rn } else { if c==13 { p=rn } else { 36 var ch: i64=c 37 if ch==43 { ch=32 } // + -> space 38 if ch==37 { ch=32; p=p+2 } // %XX -> space (demo) 39 if o<cap-1 { out[o]=ch as u8; o=o+1 } 40 p=p+1 41 } } } } 42 } 43 out[o]=0 as u8 44 return o 45} 46 47// fork the search client (registry, site, q-terms split on space); stdout -> out_path. returns exit. 48func oa_search(site: *u8, q: *u8, out_path: *u8) -> i64 { 49 let pid: i64=sys_fork() 50 if pid==0 { 51 let o: i64=sys_openat_wr(out_path, 0x1a4); if o>=0 { sys_dup3(o,1,0) } 52 let argv: *i64=sys_mmap(8*16) as *i64 53 argv[0]="/tmp/nx_onsite_search.sov.elf" as *u8 as i64 54 argv[1]=OA_REG as i64 55 argv[2]=site as i64 56 // split q on spaces into argv[3..]; in-place NUL 57 var ai: i64=3; var ls: i64=0; var i: i64=0; let n: i64=oa_strlen(q) 58 while i<=n { 59 var sp: i64=0 60 if i==n { sp=1 } else { if q[i]==(32 as u8) { sp=1 } } 61 if sp==1 { if i>ls { if ai<14 { q[i]=0 as u8; argv[ai]=(q as i64)+ls; ai=ai+1 } } ls=i+1 } 62 i=i+1 63 } 64 argv[ai]=0 65 let envp: *i64=sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 66 sys_execve_clean("/tmp/nx_onsite_search.sov.elf" as *u8, argv, envp); sys_exit(127) 67 } 68 let st: *i64=sys_mmap(16) as *i64; sys_wait4(pid, st, 0); return (st[0]>>8)&0xff 69} 70 71// parse client output (" #N score=.. <url> <title>") -> JSON items into body; return new offset. 72func oa_to_json(body: *u8, bo0: i64, out: *u8, n: i64) -> i64 { 73 var bo: i64=oa_cat(body, bo0, "{\"items\":[" as *u8) 74 var first: i64=1 75 var ls: i64=0; var i: i64=0 76 while i<=n { 77 var eol: i64=0 78 if i==n { eol=1 } else { if out[i]==(10 as u8) { eol=1 } } 79 if eol==1 { 80 // a result line starts with " #" 81 if i-ls>4 { if out[ls]==(32 as u8) { if out[ls+1]==(35 as u8) { 82 // whitespace-split tokens; token1="#N" token2="score=.." token3=url token4..=title 83 var toks: i64=0; var ts: i64=ls; var urls: i64=0-1; var urle: i64=0-1; var tts: i64=0-1 84 var k: i64=ls 85 while k<=i { 86 var w: i64=0 87 if k==i { w=1 } else { if out[k]==(32 as u8) { w=1 } } 88 if w==1 { if k>ts { toks=toks+1; if toks==3 { urls=ts; urle=k } if toks==4 { tts=ts } } ts=k+1 } 89 k=k+1 90 } 91 if urls>=0 { 92 if first==0 { bo=oa_cat(body, bo, "," as *u8) } 93 first=0 94 bo=oa_cat(body, bo, "{\"url\":\"" as *u8) 95 var a: i64=urls; while a<urle { body[bo]=out[a]; bo=bo+1; a=a+1 } 96 bo=oa_cat(body, bo, "\",\"title\":\"" as *u8) 97 if tts>=0 { var b: i64=tts; while b<i { if out[b]!=(34 as u8) { if out[b]!=(92 as u8) { body[bo]=out[b]; bo=bo+1 } } b=b+1 } } 98 bo=oa_cat(body, bo, "\"}" as *u8) 99 } 100 } } } 101 ls=i+1 102 } 103 i=i+1 104 } 105 bo=oa_cat(body, bo, "]}" as *u8) 106 return bo 107} 108 109func oa_find(req: *u8, rn: i64, pat: *u8) -> i64 { 110 let pl: i64=oa_strlen(pat); var i: i64=0 111 while i+pl<=rn { var j: i64=0; var ok: i64=1; while j<pl { if req[i+j]!=pat[j]{ok=0;j=pl} else {j=j+1} } if ok==1 { return 1 } i=i+1 } 112 return 0 113} 114 115func main(argc: i64, argv: *i64) -> i64 { 116 var port: i64=OA_MAGIC_8096 117 if argc>=2 { var v: i64=0; var i: i64=0; let p: *u8=argv[1] as *u8; while p[i]!=(0 as u8){ if p[i]>=(48 as u8){ if p[i]<=(57 as u8){ v=v*10+((p[i] as i64)-48) } } i=i+1 } if v>0 { port=v } } 118 let addr: *u8=sys_mmap(16) 119 addr[0]=2 as u8; addr[1]=0 as u8; addr[2]=((port>>8)&0xff) as u8; addr[3]=(port&0xff) as u8 120 addr[4]=127 as u8; addr[5]=0 as u8; addr[6]=0 as u8; addr[7]=1 as u8 121 let lfd: i64=sys_socket(2,1,0); if lfd<0 { return 10 } 122 let optval: *u8=sys_mmap(4); optval[0]=1 as u8 123 sys_setsockopt(lfd, 1, 2, optval, 4) 124 if sys_bind(lfd, addr, 16)<0 { return 20 } 125 if sys_listen(lfd, 64)<0 { return 30 } 126 let m: *u8="nx_onsite_api: serving / and /api/onsite?site=&q= (same-origin search for <nishi-embed>)\n" as *u8 127 sys_write(2, m, oa_strlen(m)) 128 let req: *u8=sys_mmap(OA_MAGIC_16384) 129 while 1==1 { 130 let cfd: i64=sys_accept(lfd) 131 if cfd<0 { continue } 132 let pid: i64=sys_fork() 133 if pid==0 { 134 sys_close(lfd) 135 let rn: i64=sys_read(cfd, req, OA_MAGIC_16384) 136 if oa_find(req, rn, "GET /api/onsite" as *u8)==1 { 137 let site: *u8=sys_mmap(256); oa_param(req, rn, "site=" as *u8, site, 256) 138 let q: *u8=sys_mmap(512); oa_param(req, rn, "q=" as *u8, q, 512) 139 oa_search(site, q, "/tmp/oa_out.txt" as *u8) 140 let szp: *i64=sys_mmap(16) as *i64 141 let sb: *u8=sys_read_file("/tmp/oa_out.txt" as *u8, szp) 142 let body: *u8=sys_mmap(OA_MAGIC_262144) 143 var bo: i64=0 144 if (sb as i64)!=0 { bo=oa_to_json(body, 0, sb, szp[0]) } else { bo=oa_cat(body, 0, "{\"items\":[]}" as *u8) } 145 let rbuf: *u8=sys_mmap(OA_MAGIC_262144); var o: i64=0 146 o=oa_cat(rbuf, o, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nAccess-Control-Allow-Origin: *\r\nCache-Control: no-store\r\nConnection: close\r\nContent-Length: " as *u8) 147 o=oa_u(rbuf, o, bo); o=oa_cat(rbuf, o, "\r\n\r\n" as *u8) 148 var i: i64=0; while i<bo { rbuf[o]=body[i]; o=o+1; i=i+1 } 149 sys_write(cfd, rbuf, o) 150 } else { 151 // serve the embed page 152 let szp: *i64=sys_mmap(16) as *i64 153 let pg: *u8=sys_read_file(OA_PAGE, szp) 154 let rbuf: *u8=sys_mmap(OA_MAGIC_262144); var o: i64=0 155 if (pg as i64)==0 { o=oa_cat(rbuf, 0, "HTTP/1.1 404 Not Found\r\nContent-Length: 9\r\nConnection: close\r\n\r\nno embed " as *u8) } else { 156 o=oa_cat(rbuf, o, "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\nContent-Length: " as *u8) 157 o=oa_u(rbuf, o, szp[0]); o=oa_cat(rbuf, o, "\r\n\r\n" as *u8) 158 var i: i64=0; while i<szp[0] { rbuf[o]=pg[i]; o=o+1; i=i+1 } 159 } 160 sys_write(cfd, rbuf, o) 161 } 162 sys_close(cfd); sys_exit(0) 163 } 164 sys_close(cfd) 165 } 166 return 0 167}