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}