code wiki / _hdl_build / nx_vault_gateway.nx
nx_vault_gateway.nx source
↩ module page · 456 lines · 30214 B
1// nx_vault_gateway.nx -- OPAQUE-gated /vault media browser (M0 product surface) per the family-suite spec.
2// Reuses the gallery opaque realm (argv keys/store) so existing creds log in. Renders the SPEC'd shallow-
3// faceted VISUAL browse: MAJOR=gen/real, GROUP=type, SOURCE=badge, thumbnails served from the record `ref`
4// (DSM layout /volume1/vault/<class>/<type>/<shard>/<cid>_<source>.<ext>). Gated: no valid session -> 401,
5// never a public byte (private-media cardinal rule). Cookie-auth (ngs, HttpOnly, Path=/vault).
6// argv: [1]=port [2]=keys_path [3]=store_path [4]=budget [5]=allow_register [6]=m [7]=t [8]=p [9]=ttl
7// license_tier: ORIGINAL No hw writes (Rule 26).
8import "nx_opaque_login.nx"
9import "nx_http_form.nx"
10import "nx_registry.nx"
11import "nx_mvault_record.nx"
12import "nx_mvault_coll.nx" // site/album/source groupings -- the browse axis the operator asked for
13const VGW_MAGIC_262144: i64 = 262144
14const VGW_MAGIC_2048: i64 = 2048
15const VGW_MAGIC_8388608: i64 = 8388608
16const VGW_MAGIC_16777216: i64 = 16777216
17const VGW_MAGIC_131072: i64 = 131072
18const VGW_MAGIC_131071: i64 = 131071
19const VGW_MAGIC_8192: i64 = 8192
20
21const VGW_PROD_M: i64 = 65536
22const VGW_SESSION_TTL: i64 = 86400
23const VGW_MVAULT_STORE: *u8 = "/volume1/homes/elderwesto/nishihost/knowledge/store/mvault-" as *u8
24const VGW_MAX_ITEMS: i64 = 3000
25const VGW_MEDIA_CAP: i64 = 25165824 // 24 MiB serve cap -- OOM guard; images/thumbnails only
26// The record's `ref` uses the logical DSM root /volume1/vault (14 chars). That shared folder needs
27// root/DSM to create, so serving remaps that prefix to this CONFIGURABLE writable root (Rule 11/17:
28// the path was hardcoded -- a latent bug). When the /volume1/vault shared folder exists, set this to
29// "/volume1/vault" for spec-exact DSM layout. NOTE: serving from a MIRROR copies bytes -> proof-scale
30// only; the no-copy scale answer is an `orig` field in the record + serve-in-place (surfaced to owner).
31const VGW_MEDIA_ROOT: *u8 = "/volume1/homes/elderwesto/vaultfs" as *u8
32const VGW_REF_ROOT_LEN: i64 = 14 // len("/volume1/vault")
33
34const VGW_COLLBUF: i64 = 1048576 // buffer for the collection id index feeding the groupings facet
35func gw_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
36func gw_atoi(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while s[i]!=(0 as u8){ let c: i64=s[i] as i64; if c<48{return v} if c>57{return v} v=v*10+(c-48); i=i+1 } return v }
37func gw_starts(buf: *u8, n: i64, pre: *u8) -> i64 { var i: i64=0; while pre[i]!=(0 as u8){ if i>=n {return 0} if buf[i]!=pre[i]{return 0} i=i+1 } return 1 }
38func gw_find(buf: *u8, n: i64, needle: *u8, nl: i64) -> i64 {
39 if nl==0 { return 0 }
40 var i: i64=0
41 while i+nl<=n { var j: i64=0; var ok: i64=1; while j<nl { if buf[i+j]!=needle[j]{ok=0; j=nl} else {j=j+1} } if ok==1 {return i} i=i+1 }
42 return 0-1
43}
44func gw_cat(dst: *u8, off: i64, s: *u8) -> i64 { var o: i64=off; var i: i64=0; while s[i]!=(0 as u8){dst[o]=s[i]; o=o+1; i=i+1} return o }
45func gw_catb(dst: *u8, off: i64, src: *u8, n: i64) -> i64 { var o: i64=off; var i: i64=0; while i<n {dst[o]=src[i]; o=o+1; i=i+1} return o }
46func gw_itoa(dst: *u8, off: i64, v: i64) -> i64 { let t: *u8=sys_mmap(28); var m: i64=v; 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 q: i64=k-1; while q>=0{dst[o]=t[q];o=o+1;q=q-1} return o }
47
48func gw_send(cfd: i64, status: *u8, ctype: *u8, body: *u8, blen: i64) -> i64 {
49 let buf: *u8 = sys_mmap(VGW_MAGIC_262144); var o: i64 = 0
50 o = gw_cat(buf, o, "HTTP/1.1 " as *u8); o = gw_cat(buf, o, status)
51 o = gw_cat(buf, o, "\r\nContent-Type: " as *u8); o = gw_cat(buf, o, ctype)
52 o = gw_cat(buf, o, "\r\nContent-Length: " as *u8); o = gw_itoa(buf, o, blen)
53 o = gw_cat(buf, o, "\r\nConnection: close\r\nCache-Control: no-store\r\n\r\n" as *u8)
54 o = gw_catb(buf, o, body, blen)
55 sys_write(cfd, buf, o); return 0
56}
57func gw_send_big(cfd: i64, body: *u8, blen: i64) -> i64 {
58 let hdr: *u8 = sys_mmap(512); var o: i64 = 0
59 o = gw_cat(hdr, o, "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: " as *u8)
60 o = gw_itoa(hdr, o, blen)
61 o = gw_cat(hdr, o, "\r\nConnection: close\r\nCache-Control: no-store\r\n\r\n" as *u8)
62 sys_write(cfd, hdr, o); sys_write(cfd, body, blen); return 0
63}
64func gw_401(cfd: i64) -> i64 {
65 let b: *u8 = "{\"error\":\"login required\"}" as *u8
66 gw_send(cfd, "401 Unauthorized" as *u8, "application/json" as *u8, b, gw_slen(b)); return 0
67}
68func gw_is_nav(req: *u8, n: i64) -> i64 { if gw_find(req, n, "Sec-Fetch-Mode: navigate" as *u8, 24) >= 0 { return 1 } return 0 }
69func gw_302_login(cfd: i64) -> i64 {
70 let b: *u8 = "HTTP/1.1 302 Found\r\nLocation: /vault/login\r\nContent-Length: 0\r\nCache-Control: no-store\r\nConnection: close\r\n\r\n" as *u8
71 sys_write(cfd, b, gw_slen(b)); return 0
72}
73func gw_send_ck(cfd: i64, ctype: *u8, body: *u8, blen: i64, ckval: *u8, ckvallen: i64, ttl: i64) -> i64 {
74 let buf: *u8 = sys_mmap(VGW_MAGIC_262144); var o: i64 = 0
75 o = gw_cat(buf, o, "HTTP/1.1 200 OK\r\nContent-Type: " as *u8); o = gw_cat(buf, o, ctype)
76 o = gw_cat(buf, o, "\r\nSet-Cookie: ngs=" as *u8); o = gw_catb(buf, o, ckval, ckvallen)
77 o = gw_cat(buf, o, "; HttpOnly; Secure; SameSite=Strict; Path=/vault; Max-Age=" as *u8); o = gw_itoa(buf, o, ttl)
78 o = gw_cat(buf, o, "\r\nContent-Length: " as *u8); o = gw_itoa(buf, o, blen)
79 o = gw_cat(buf, o, "\r\nConnection: close\r\nCache-Control: no-store\r\n\r\n" as *u8)
80 o = gw_catb(buf, o, body, blen)
81 sys_write(cfd, buf, o); return 0
82}
83func gw_cookie_val(req: *u8, hend: i64, out: *u8, cap: i64) -> i64 {
84 let p: i64 = gw_find(req, hend, "ngs=" as *u8, 4)
85 if p < 0 { out[0]=0 as u8; return 0 }
86 var i: i64 = p + 4; var o: i64 = 0
87 while i < hend { let c: u8 = req[i]; if c==(59 as u8){i=hend} else { if c==(13 as u8){i=hend} else { if c==(10 as u8){i=hend} else { if c==(32 as u8){i=hend} else { if o<cap-1 {out[o]=c; o=o+1} i=i+1 } } } } }
88 out[o]=0 as u8; return o
89}
90func gw_hdr_val(req: *u8, hend: i64, name: *u8, nl: i64, out: *u8, cap: i64) -> i64 {
91 let p: i64 = gw_find(req, hend, name, nl)
92 if p < 0 { out[0]=0 as u8; return 0 }
93 var i: i64 = p + nl
94 if i < hend { if req[i]==(32 as u8) { i=i+1 } }
95 var o: i64 = 0
96 while i < hend { let c: u8 = req[i]; if c==(13 as u8){i=hend} else { if c==(10 as u8){i=hend} else { if o<cap-1 {out[o]=c; o=o+1} i=i+1 } } }
97 out[o]=0 as u8; return o
98}
99func gw_reqpath(req: *u8, rn: i64, out: *u8, cap: i64) -> i64 {
100 var s1: i64 = 0-1; var i: i64 = 0
101 while i < rn { if req[i]==(32 as u8) { s1=i; i=rn } else { i=i+1 } }
102 if s1 < 0 { out[0]=0 as u8; return 0 }
103 var p: i64 = s1+1; var o: i64 = 0
104 while p < rn { let c: u8 = req[p]; if c==(32 as u8) { p=rn } else { if o<cap-1 { out[o]=c; o=o+1 } p=p+1 } }
105 out[o]=0 as u8; return o
106}
107func gw_read_full(cfd: i64, req: *u8, cap: i64) -> i64 {
108 var total: i64 = 0
109 var he: i64 = 0 - 1
110 while he < 0 {
111 if total >= cap { return total }
112 let r: i64 = sys_read(cfd, ((req as i64) + total) as *u8, cap - total)
113 if r <= 0 { return total }
114 total = total + r
115 he = gw_find(req, total, "\r\n\r\n" as *u8, 4)
116 }
117 let clbuf: *u8 = sys_mmap(32)
118 let cln: i64 = gw_hdr_val(req, he, "\r\nContent-Length:" as *u8, 17, clbuf, 32)
119 var need: i64 = he + 4
120 if cln > 0 { need = he + 4 + gw_atoi(clbuf) }
121 while total < need {
122 if total >= cap { return total }
123 let r2: i64 = sys_read(cfd, ((req as i64) + total) as *u8, cap - total)
124 if r2 <= 0 { return total }
125 total = total + r2
126 }
127 return total
128}
129
130const VGW_LOGIN_HTML: *u8 = "<!doctype html><html><head><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Nishi Vault</title><style>body{font-family:system-ui,sans-serif;max-width:420px;margin:8vh auto;padding:0 18px;color:#cdd7e6;background:#0b1019}h1{font-size:1.2rem;color:#e8eef7}p{color:#7c8aa5;font-size:.86rem}input{width:100%;padding:9px;margin:5px 0;box-sizing:border-box;border:1px solid #2a3550;border-radius:5px;background:#121a28;color:#e8eef7}button{padding:9px 16px;margin:6px 6px 0 0;background:#2d6cdf;color:#fff;border:0;border-radius:5px;cursor:pointer}#m{margin:14px 0;padding:12px;background:#121a28;border-left:3px solid #2d6cdf;color:#cdd7e6;min-height:1.2em}</style></head><body><h1>Nishi Vault — private</h1><p>OPAQUE aPAKE (RFC 9807). Same credentials as the gallery. The session rides an HttpOnly cookie, path-scoped to /vault.</p><div id=m>Log in to view the vault.</div><input id=h placeholder=handle autocomplete=username><input id=p type=password placeholder=passphrase autocomplete=current-password><button onclick=login()>Login</button><script>function $(i){return document.getElementById(i)}function M(t){$('m').textContent=t}async function login(){M('Logging in...');try{var r=await fetch('/vault/auth/login',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'handle='+encodeURIComponent($('h').value)+'&pw='+encodeURIComponent($('p').value)});var j=await r.json();if(!r.ok){M('Login failed: '+(j.error||r.status));return}M('Logged in. Opening vault...');location.href='/vault/'}catch(e){M('error: '+e)}}</script></body></html>" as *u8
131
132func vgw_nl(buf: *u8, from: i64, n: i64) -> i64 { var i: i64=from; var r: i64=n; while i<n { if buf[i]==(10 as u8) { r=i; i=n } else { i=i+1 } } return r }
133// append NXR1 field value (by key) to dst at o0; returns new offset (o0 if absent)
134func vgw_field(rec: *u8, rl: i64, key: *u8, kl: i64, dst: *u8, o0: i64) -> i64 {
135 let vp: *i64 = sys_mmap(16) as *i64
136 let vl: *i64 = sys_mmap(16) as *i64
137 if mv_rec_field(rec, rl, key, kl, vp, vl) == 1 {
138 let src: *u8 = vp[0] as *u8
139 var o: i64 = o0; var i: i64 = 0
140 while i < vl[0] { dst[o]=src[i]; o=o+1; i=i+1 }
141 return o
142 }
143 return o0
144}
145// resolve the DSM ref (path) for a cid into out (NUL-term); returns len or 0
146func vgw_ref_of(cid: *u8, out: *u8, cap: i64) -> i64 {
147 let recp: *i64 = sys_mmap(16) as *i64
148 let recl: *i64 = sys_mmap(16) as *i64
149 if mv_store_get(VGW_MVAULT_STORE, cid, recp, recl) != 1 { out[0]=0 as u8; return 0 }
150 let vp: *i64 = sys_mmap(16) as *i64
151 let vl: *i64 = sys_mmap(16) as *i64
152 if mv_rec_field(recp[0] as *u8, recl[0], "ref" as *u8, 3, vp, vl) != 1 { out[0]=0 as u8; return 0 }
153 let src: *u8 = vp[0] as *u8
154 var o: i64 = 0
155 while o < vl[0] { if o < cap-1 { out[o]=src[o] } o=o+1 }
156 out[vl[0]]=0 as u8
157 return vl[0]
158}
159// path-jail: 1 iff p starts with /volume1/vault/ and contains no ".."
160func vgw_jail_ok(p: *u8) -> i64 {
161 let pre: *u8 = "/volume1/vault/" as *u8
162 var i: i64 = 0
163 while pre[i]!=(0 as u8) { if p[i]!=pre[i] { return 0 } i=i+1 }
164 var j: i64 = 0
165 while p[j]!=(0 as u8) { if p[j]==(46 as u8) { if p[j+1]==(46 as u8) { return 0 } } j=j+1 }
166 return 1
167}
168func vgw_ctype_for(p: *u8) -> *u8 {
169 let n: i64 = gw_slen(p)
170 var d: i64 = 0-1; var i: i64 = 0
171 while i < n { if p[i]==(46 as u8) { d=i } i=i+1 }
172 if d < 0 { return "application/octet-stream" as *u8 }
173 let e: *u8 = ((p as i64)+d+1) as *u8
174 let el: i64 = n-d-1
175 if gw_starts(e, el, "jpg" as *u8)==1 { return "image/jpeg" as *u8 }
176 if gw_starts(e, el, "jpeg" as *u8)==1 { return "image/jpeg" as *u8 }
177 if gw_starts(e, el, "png" as *u8)==1 { return "image/png" as *u8 }
178 if gw_starts(e, el, "gif" as *u8)==1 { return "image/gif" as *u8 }
179 if gw_starts(e, el, "webp" as *u8)==1 { return "image/webp" as *u8 }
180 return "application/octet-stream" as *u8
181}
182// GATED media serve: /vault/media?cid=X -> file at record.ref (path-jailed, size-capped)
183func vgw_serve_media(cfd: i64, path: *u8, plen: i64) -> i64 {
184 let qp: i64 = gw_find(path, plen, "cid=" as *u8, 4)
185 if qp < 0 { gw_send(cfd, "400 Bad Request" as *u8, "text/plain" as *u8, "no cid" as *u8, 6); return 0 }
186 let cid: *u8 = sys_mmap(128)
187 var i: i64 = qp+4; var o: i64 = 0
188 while i < plen { let c: u8 = path[i]; if c==(38 as u8) { i=plen } else { if o<127 { cid[o]=c; o=o+1 } i=i+1 } }
189 cid[o]=0 as u8
190 let ref: *u8 = sys_mmap(VGW_MAGIC_2048)
191 if vgw_ref_of(cid, ref, VGW_MAGIC_2048) == 0 { gw_send(cfd, "404 Not Found" as *u8, "text/plain" as *u8, "no rec" as *u8, 6); return 0 }
192 if vgw_jail_ok(ref) == 0 { gw_send(cfd, "403 Forbidden" as *u8, "text/plain" as *u8, "jail" as *u8, 4); return 0 }
193 // remap the logical ref root (/volume1/vault) to the configurable writable media root
194 let realp: *u8 = sys_mmap(VGW_MAGIC_2048)
195 var rro: i64 = gw_cat(realp, 0, VGW_MEDIA_ROOT)
196 var rri: i64 = VGW_REF_ROOT_LEN
197 while ref[rri]!=(0 as u8) { realp[rro]=ref[rri]; rro=rro+1; rri=rri+1 }
198 realp[rro]=0 as u8
199 let fd: i64 = sys_openat_rd(realp)
200 if fd < 0 { gw_send(cfd, "404 Not Found" as *u8, "text/plain" as *u8, "no file" as *u8, 7); return 0 }
201 let sz: i64 = sys_lseek(fd, 0, 2)
202 sys_lseek(fd, 0, 0)
203 if sz > VGW_MEDIA_CAP { sys_close(fd); gw_send(cfd, "413 Payload Too Large" as *u8, "text/plain" as *u8, "too big" as *u8, 7); return 0 }
204 let buf: *u8 = sys_mmap(VGW_MEDIA_CAP)
205 var total: i64 = 0; var go: i64 = 1
206 while go==1 { let r: i64 = sys_read(fd, ((buf as i64)+total) as *u8, VGW_MEDIA_CAP-total); if r<=0 {go=0} else { total=total+r; if total>=VGW_MEDIA_CAP {go=0} } }
207 sys_close(fd)
208 let hdr: *u8 = sys_mmap(512); var ho: i64 = 0
209 ho = gw_cat(hdr, ho, "HTTP/1.1 200 OK\r\nContent-Type: " as *u8); ho = gw_cat(hdr, ho, vgw_ctype_for(ref))
210 ho = gw_cat(hdr, ho, "\r\nContent-Length: " as *u8); ho = gw_itoa(hdr, ho, total)
211 ho = gw_cat(hdr, ho, "\r\nConnection: close\r\nCache-Control: private, max-age=300\r\n\r\n" as *u8)
212 sys_write(cfd, hdr, ho); sys_write(cfd, buf, total)
213 return 0
214}
215// GATED faceted visual browse: grid of cards (thumbnail + gen/real + type + source badges) + facet filters
216func vgw_serve_catalog(cfd: i64) -> i64 {
217 let store: *u8 = VGW_MVAULT_STORE
218 // ZERO-COPY (2026-07-31): this copied the whole id index into a fixed 8 MiB
219 // mapping. The live index had already passed 10 MiB, so the BROWSE PAGE was
220 // writing ~3 MiB past the end of its own buffer on every request -- it was
221 // past its cliff, not approaching it. Borrowing the store's mapping removes
222 // the failure mode by construction instead of raising the cap and waiting.
223 let vipo: *i64 = sys_mmap(16) as *i64
224 let vilo: *i64 = sys_mmap(16) as *i64
225 var ln: i64 = 0
226 var idxbuf: *u8 = 0 as *u8
227 if reg_index_open(store, "mv:ids" as *u8, vipo, vilo) == 1 { idxbuf = vipo[0] as *u8; ln = vilo[0] }
228 let html: *u8 = sys_mmap(VGW_MAGIC_16777216)
229 var o: i64 = 0
230 o = gw_cat(html, o, "<!doctype html><html><head><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Nishi Vault</title><style>body{background:#0b0f14;color:#d4dae3;font:14px system-ui,sans-serif;margin:0;padding:18px}h1{color:#fff;margin:0 0 2px;font-size:20px}.m{color:#8a93a3;font-size:12px}#f{margin:10px 0}#f button{background:#161b22;color:#adbac7;border:1px solid #2a3550;border-radius:14px;padding:3px 11px;margin:2px;font-size:12px;cursor:pointer}#f button.on{background:#2d6cdf;color:#fff;border-color:#2d6cdf}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px;margin-top:12px}.card{background:#11161d;border:1px solid #1c2230;border-radius:8px;overflow:hidden}.card img{width:100%;height:150px;object-fit:cover;display:block;background:#0d1117}.ph{width:100%;height:150px;display:flex;align-items:center;justify-content:center;font-size:34px;color:#3a4658;background:#0d1117}.meta{padding:5px 7px;font-size:11px}.b{display:inline-block;font-size:10px;padding:1px 6px;border-radius:8px;margin-right:3px}.gen{background:#3a1d5c;color:#c79bff}.real{background:#173a2a;color:#6ee7a8}.src{background:#1c2230;color:#8a93a3}</style></head><body><h1>Nishi Vault</h1>" as *u8)
231 var items: i64 = 0
232 var ci: i64 = 0
233 while ci < ln { if idxbuf[ci]==(10 as u8) { items=items+1 } ci=ci+1 }
234 o = gw_cat(html, o, "<p class=m>" as *u8); o = gw_itoa(html, o, items); o = gw_cat(html, o, " items · gated · faceted (gen/real x type, source-tagged, CID-deduped)</p>" as *u8)
235 o = gw_cat(html, o, "<div id=f><button class=on data-f=all onclick=\"F(this,'all','')\">all</button><button onclick=\"F(this,'c','gen')\">gen</button><button onclick=\"F(this,'c','real')\">real</button><button onclick=\"F(this,'t','image')\">image</button><button onclick=\"F(this,'t','video')\">video</button><button onclick=\"F(this,'t','book')\">book</button></div>" as *u8)
236 // GROUPINGS FACET (site / album / source) -- driven by the live collection axis.
237 // The operator asked to browse by site/album/original-source; the axis existed and
238 // gated 10/10 but no surface ever read it.
239 let cbuf: *u8 = sys_mmap(VGW_COLLBUF)
240 let cn: i64 = mvc_list(cbuf, VGW_COLLBUF)
241 if cn > 0 {
242 o = gw_cat(html, o, "<div id=cf style=\"margin:6px 0 2px\"><span class=m>groupings · </span>" as *u8)
243 var cp: i64 = 0
244 while cp < cn {
245 let ce: i64 = vgw_nl(cbuf, cp, cn)
246 let cl2: i64 = ce - cp
247 if cl2 > 0 {
248 let colid: *u8 = sys_mmap(160)
249 var cq: i64 = 0
250 while cq < cl2 { if cq < 159 { colid[cq] = cbuf[cp+cq] } cq = cq + 1 }
251 colid[cl2] = 0 as u8
252 let gp: *i64 = sys_mmap(16) as *i64
253 let gl: *i64 = sys_mmap(16) as *i64
254 mvc_get(colid, gp, gl)
255 if gl[0] > 0 {
256 let grec: *u8 = gp[0] as *u8
257 let nmb: *u8 = sys_mmap(256)
258 let nmo: i64 = vgw_field(grec, gl[0], "name" as *u8, 4, nmb, 0)
259 let kdb: *u8 = sys_mmap(64)
260 let kdo: i64 = vgw_field(grec, gl[0], "kind" as *u8, 4, kdb, 0)
261 let ccount: i64 = mvc_count(colid)
262 o = gw_cat(html, o, "<span class=\"b src\">" as *u8)
263 o = gw_catb(html, o, nmb, nmo)
264 o = gw_cat(html, o, " · " as *u8)
265 o = gw_catb(html, o, kdb, kdo)
266 o = gw_cat(html, o, " · " as *u8)
267 o = gw_itoa(html, o, ccount)
268 o = gw_cat(html, o, "</span>" as *u8)
269 }
270 }
271 cp = ce + 1
272 }
273 o = gw_cat(html, o, "</div>" as *u8)
274 }
275 o = gw_cat(html, o, "<div class=grid id=g>" as *u8)
276 var i: i64 = 0
277 var shown: i64 = 0
278 while i < ln {
279 let e: i64 = vgw_nl(idxbuf, i, ln)
280 let clen: i64 = e - i
281 if clen > 0 {
282 if shown < VGW_MAX_ITEMS {
283 let cid: *u8 = sys_mmap(128)
284 var k: i64 = 0
285 while k < clen { if k<127 { cid[k]=idxbuf[i+k] } k=k+1 }
286 cid[clen]=0 as u8
287 let recp: *i64 = sys_mmap(16) as *i64
288 let recl: *i64 = sys_mmap(16) as *i64
289 if mv_store_get(store, cid, recp, recl) == 1 {
290 let rec: *u8 = recp[0] as *u8
291 let rl: i64 = recl[0]
292 let cls: *u8 = sys_mmap(32); let clo: i64 = vgw_field(rec, rl, "class" as *u8, 5, cls, 0); cls[clo]=0 as u8
293 let typ: *u8 = sys_mmap(32); let tyo: i64 = vgw_field(rec, rl, "type" as *u8, 4, typ, 0); typ[tyo]=0 as u8
294 o = gw_cat(html, o, "<div class=card data-c=" as *u8); o = gw_catb(html, o, cls, clo); o = gw_cat(html, o, " data-t=" as *u8); o = gw_catb(html, o, typ, tyo); o = gw_cat(html, o, ">" as *u8)
295 if gw_starts(typ, tyo, "image" as *u8)==1 {
296 o = gw_cat(html, o, "<img loading=lazy src=/vault/media?cid=" as *u8); o = gw_catb(html, o, cid, clen); o = gw_cat(html, o, ">" as *u8)
297 } else { if gw_starts(typ, tyo, "gif" as *u8)==1 {
298 o = gw_cat(html, o, "<img loading=lazy src=/vault/media?cid=" as *u8); o = gw_catb(html, o, cid, clen); o = gw_cat(html, o, ">" as *u8)
299 } else {
300 o = gw_cat(html, o, "<div class=ph>" as *u8)
301 if gw_starts(typ, tyo, "video" as *u8)==1 { o = gw_cat(html, o, "▶" as *u8) } else { if gw_starts(typ, tyo, "book" as *u8)==1 { o = gw_cat(html, o, "📖" as *u8) } else { o = gw_cat(html, o, "◉" as *u8) } }
302 o = gw_cat(html, o, "</div>" as *u8)
303 } }
304 o = gw_cat(html, o, "<div class=meta><span class=\"b " as *u8)
305 if gw_starts(cls, clo, "gen" as *u8)==1 { o = gw_cat(html, o, "gen\">gen" as *u8) } else { o = gw_cat(html, o, "real\">real" as *u8) }
306 o = gw_cat(html, o, "</span><span class=\"b src\">" as *u8); o = vgw_field(rec, rl, "source" as *u8, 6, html, o); o = gw_cat(html, o, "</span> " as *u8); o = gw_catb(html, o, typ, tyo)
307 o = gw_cat(html, o, "</div></div>" as *u8)
308 shown = shown + 1
309 }
310 }
311 }
312 i = e + 1
313 }
314 o = gw_cat(html, o, "</div>" as *u8)
315 if items > shown { o = gw_cat(html, o, "<p class=m>(showing " as *u8); o = gw_itoa(html, o, shown); o = gw_cat(html, o, " of " as *u8); o = gw_itoa(html, o, items); o = gw_cat(html, o, ")</p>" as *u8) }
316 o = gw_cat(html, o, "<script>function F(b,k,v){var bs=document.querySelectorAll('#f button');for(var i=0;i<bs.length;i++)bs[i].classList.remove('on');b.classList.add('on');var cs=document.querySelectorAll('.card');for(var j=0;j<cs.length;j++){var c=cs[j];var s=(k=='all')||(k=='c'&&c.dataset.c==v)||(k=='t'&&c.dataset.t==v);c.style.display=s?'':'none'}}</script></body></html>" as *u8)
317 gw_send_big(cfd, html, o)
318 return 0
319}
320
321func main(argc: i64, argv: *i64) -> i64 {
322 if argc < 5 {
323 sys_write(1, "usage: nx_vault_gateway <port> <keys> <store> <budget> [allow_register] [m] [t] [p] [ttl]\n" as *u8, 88)
324 sys_exit(2); return 2
325 }
326 let port: i64 = gw_atoi(argv[1] as *u8)
327 let keys_path: *u8 = argv[2] as *u8
328 let store_path: *u8 = argv[3] as *u8
329 let budget: i64 = gw_atoi(argv[4] as *u8)
330 var allow_reg: i64 = 0
331 if argc > 5 { allow_reg = gw_atoi(argv[5] as *u8) }
332 var m_cost: i64 = VGW_PROD_M
333 if argc > 6 { m_cost = gw_atoi(argv[6] as *u8) }
334 var t_cost: i64 = 3
335 if argc > 7 { t_cost = gw_atoi(argv[7] as *u8) }
336 var p_cost: i64 = 4
337 if argc > 8 { p_cost = gw_atoi(argv[8] as *u8) }
338 var session_ttl: i64 = VGW_SESSION_TTL
339 if argc > 9 { session_ttl = gw_atoi(argv[9] as *u8) }
340
341 let ctx: *NxAuthContext = sys_mmap(256) as *NxAuthContext
342 if olg_ctx_setup_ttl(ctx, keys_path, store_path, "nishi_gallery" as *u8, 13, "Nishi Vault" as *u8, 11, session_ttl, m_cost, t_cost, p_cost) != 0 {
343 sys_write(1, "CTX-INIT-FAIL\n" as *u8, 14); sys_exit(1); return 1
344 }
345
346 let addr: *u8 = sys_mmap(16)
347 addr[0]=2 as u8; addr[1]=0 as u8
348 addr[2]=((port>>8)&0xff) as u8; addr[3]=(port&0xff) as u8
349 addr[4]=0 as u8; addr[5]=0 as u8; addr[6]=0 as u8; addr[7]=0 as u8
350 var zi: i64=8; while zi<16 { addr[zi]=0 as u8; zi=zi+1 }
351 let lfd: i64 = sys_socket(2, 1, 0)
352 if lfd < 0 { sys_write(1, "SOCKET-FAIL\n" as *u8, 12); sys_exit(1); return 1 }
353 let optv: *u8 = sys_mmap(4); optv[0]=1 as u8
354 sys_setsockopt(lfd, 1, 2, optv, 4)
355 if sys_bind(lfd, addr, 16) < 0 { sys_write(1, "BIND-FAIL\n" as *u8, 10); sys_exit(1); return 1 }
356 if sys_listen(lfd, 16) < 0 { sys_write(1, "LISTEN-FAIL\n" as *u8, 12); sys_exit(1); return 1 }
357 sys_write(1, "VAULT-GATEWAY-UP\n" as *u8, 17)
358
359 let st: *i64 = sys_mmap(16) as *i64
360 var served: i64 = 0
361 while served < budget {
362 let cfd: i64 = sys_accept(lfd)
363 if cfd >= 0 {
364 let pid: i64 = sys_fork()
365 if pid == 0 {
366 sys_close(lfd)
367 sys_set_socket_timeout(cfd, 20)
368 let req: *u8 = sys_mmap(VGW_MAGIC_131072)
369 let rn: i64 = gw_read_full(cfd, req, VGW_MAGIC_131071)
370 if rn > 0 {
371 let he: i64 = gw_find(req, rn, "\r\n\r\n" as *u8, 4)
372 var body: *u8 = req; var bn: i64 = 0
373 if he >= 0 { body = ((req as i64) + he + 4) as *u8; bn = rn - he - 4 }
374 let now: i64 = sys_now_realtime_sec()
375 let path: *u8 = sys_mmap(VGW_MAGIC_8192)
376 let plen: i64 = gw_reqpath(req, rn, path, VGW_MAGIC_8192)
377 let resp: *u8 = sys_mmap(VGW_MAGIC_8192)
378
379 if gw_starts(req, rn, "POST /vault/auth/register" as *u8) == 1 {
380 if allow_reg == 1 {
381 let hbuf: *u8 = sys_mmap(128); let hl: *i64 = sys_mmap(16) as *i64
382 let pbuf: *u8 = sys_mmap(320); let pl: *i64 = sys_mmap(16) as *i64
383 nx_http_form_get_field(body, bn, "handle" as *u8, 6, hbuf, 127, hl)
384 nx_http_form_get_field(body, bn, "pw" as *u8, 2, pbuf, 319, pl)
385 let mn: *u8 = sys_mmap(512); let mnn: *i64 = sys_mmap(16) as *i64
386 if olg_register(ctx, hbuf, hl[0], pbuf, pl[0], mn, 512, mnn) == NX_MAUTH_OK {
387 var oo: i64 = gw_cat(resp, 0, "{\"mnemonic\":\"" as *u8); oo = gw_catb(resp, oo, mn, mnn[0]); oo = gw_cat(resp, oo, "\"}" as *u8)
388 gw_send(cfd, "200 OK" as *u8, "application/json" as *u8, resp, oo)
389 } else {
390 let oo: i64 = gw_cat(resp, 0, "{\"error\":\"register failed\"}" as *u8)
391 gw_send(cfd, "400 Bad Request" as *u8, "application/json" as *u8, resp, oo)
392 }
393 } else {
394 let oo: i64 = gw_cat(resp, 0, "{\"error\":\"registration closed\"}" as *u8)
395 gw_send(cfd, "403 Forbidden" as *u8, "application/json" as *u8, resp, oo)
396 }
397 } else { if gw_starts(req, rn, "POST /vault/auth/login" as *u8) == 1 {
398 let hbuf: *u8 = sys_mmap(128); let hl: *i64 = sys_mmap(16) as *i64
399 let pbuf: *u8 = sys_mmap(320); let pl: *i64 = sys_mmap(16) as *i64
400 nx_http_form_get_field(body, bn, "handle" as *u8, 6, hbuf, 127, hl)
401 nx_http_form_get_field(body, bn, "pw" as *u8, 2, pbuf, 319, pl)
402 let b64: *u8 = sys_mmap(512); let b64n: *i64 = sys_mmap(16) as *i64
403 if olg_login(ctx, hbuf, hl[0], pbuf, pl[0], b64, 512, b64n) == NX_MAUTH_OK {
404 var oo: i64 = gw_cat(resp, 0, "{\"token\":\"" as *u8); oo = gw_catb(resp, oo, b64, b64n[0]); oo = gw_cat(resp, oo, "\"}" as *u8)
405 gw_send_ck(cfd, "application/json" as *u8, resp, oo, b64, b64n[0], session_ttl)
406 } else {
407 let oo: i64 = gw_cat(resp, 0, "{\"error\":\"invalid credentials\"}" as *u8)
408 gw_send(cfd, "401 Unauthorized" as *u8, "application/json" as *u8, resp, oo)
409 }
410 } else { if gw_starts(req, rn, "GET /vault/auth/whoami" as *u8) == 1 {
411 let tb: *u8 = sys_mmap(512)
412 var tl: i64 = gw_hdr_val(req, he, "X-Nishi-Session:" as *u8, 16, tb, 512)
413 if tl == 0 { tl = gw_cookie_val(req, he, tb, 512) }
414 let uh: *u8 = sys_mmap(64); let uhn: *i64 = sys_mmap(16) as *i64
415 if olg_whoami(ctx, tb, tl, now, uh, 64, uhn) == NX_MAUTH_OK {
416 let oo: i64 = gw_cat(resp, 0, "{\"ok\":1}" as *u8)
417 gw_send(cfd, "200 OK" as *u8, "application/json" as *u8, resp, oo)
418 } else { gw_401(cfd) }
419 } else { if gw_starts(req, rn, "GET /vault/login" as *u8) == 1 {
420 gw_send(cfd, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, VGW_LOGIN_HTML, gw_slen(VGW_LOGIN_HTML))
421 } else { if gw_starts(req, rn, "GET /vault/media" as *u8) == 1 {
422 let tb: *u8 = sys_mmap(512)
423 var tl: i64 = gw_hdr_val(req, he, "X-Nishi-Session:" as *u8, 16, tb, 512)
424 if tl == 0 { tl = gw_cookie_val(req, he, tb, 512) }
425 let uh: *u8 = sys_mmap(64); let uhn: *i64 = sys_mmap(16) as *i64
426 if olg_whoami(ctx, tb, tl, now, uh, 64, uhn) == NX_MAUTH_OK {
427 vgw_serve_media(cfd, path, plen)
428 } else { gw_401(cfd) }
429 } else {
430 if gw_starts(path, plen, "/vault" as *u8) == 1 {
431 let tb: *u8 = sys_mmap(512)
432 var tl: i64 = gw_hdr_val(req, he, "X-Nishi-Session:" as *u8, 16, tb, 512)
433 if tl == 0 { tl = gw_cookie_val(req, he, tb, 512) }
434 let uh: *u8 = sys_mmap(64); let uhn: *i64 = sys_mmap(16) as *i64
435 if olg_whoami(ctx, tb, tl, now, uh, 64, uhn) == NX_MAUTH_OK {
436 vgw_serve_catalog(cfd)
437 } else {
438 if gw_is_nav(req, rn) == 1 { gw_302_login(cfd) } else { gw_401(cfd) }
439 }
440 } else {
441 let oo: i64 = gw_cat(resp, 0, "not found" as *u8)
442 gw_send(cfd, "404 Not Found" as *u8, "text/plain" as *u8, resp, oo)
443 }
444 } } } } }
445 }
446 sys_close(cfd)
447 sys_exit(0)
448 }
449 sys_close(cfd)
450 var reaped: i64 = 1
451 while reaped > 0 { reaped = sys_wait4(0 - 1, st, 1) }
452 }
453 served = served + 1
454 }
455 sys_close(lfd); sys_exit(0); return 0
456}