nx_vault_capture.nx source
↩ module page · 1074 lines · 42799 B
1// nx_vault_capture.nx -- SAVE, as a first-class sovereign verb. (v2: ref-root/phys-root split)
2//
3// OPERATOR 2026-08-06: "saving an image doesn't just go to a download folder --
4// you have the ability to save the media or the whole site for future
5// consumption if you choose, from the browser to the OS."
6//
7// WHAT THIS DOES: one verb, three scopes, one destination.
8// media <url> the asset alone
9// page <url> the document AS AN ITEM + every image it references
10// site <url> a bounded same-host crawl, every page captured as above
11// Each captured byte becomes a content-addressed vault item (CID = identity, so
12// re-saving is free and duplicates collapse) carrying its provenance at ingest.
13//
14// ONE CHOKEPOINT (vc_ingest_file): the album downloader (mvf_fetch_album_pfx)
15// filed downloads into collections but wrote NO item record, and does not even
16// import the ingest module -- so it structurally COULD NOT. A capture path that
17// does its own recording can forget to record. So there is exactly one way in,
18// and the gate asserts the invariant it holds: EVERY COLLECTION MEMBER RESOLVES
19// TO A RECORD (T6), with a negative control (T7).
20//
21// REF-ROOT vs PHYS-ROOT (v2, the fix for the v1 15/15 live failure). The
22// record's `ref` uses the LOGICAL DSM root /volume1/vault -- a shared folder
23// that needs root/DSM to create and DOES NOT EXIST as a plain dir. All existing
24// records carry that logical root, and nx_vault_gateway SERVES by stripping its
25// 14-char prefix and prepending a writable PHYSICAL root (~/vaultfs). v1 used
26// ONE root for both the ref and the bytes, so every write under the nonexistent
27// /volume1/vault failed (a page + 14 assets = 15/15). v2 keeps the LOGICAL ref
28// in the record (consistent with existing records + the gateway remap) and
29// writes bytes to the PHYSICAL root. Both roots are data-driven
30// (knowledge/status/vault_roots.conf), rule 11/17.
31//
32// ORDERING (nx_mvault_coll): collections are declared BEFORE the bytes land.
33// Composes only proven organs. license_tier: ORIGINAL
34
35import "nx_syscalls.nx"
36import "nx_canon_cid.nx"
37import "nx_registry.nx"
38import "nx_media_pool.nx"
39import "nx_media_sniff.nx"
40import "nx_mvault_ingest.nx"
41import "nx_mvault_context.nx"
42import "nx_mvault_record.nx"
43import "nx_mvault_layout.nx"
44import "nx_mvault_reclaim.nx"
45import "nx_mvault_reindex.nx"
46import "nx_mvault_tag.nx"
47import "nx_mvault_coll.nx"
48import "nx_mvault_fetch.nx"
49import "nx_https_fetch_lib.nx"
50import "nx_paced_fetch.nx" // R0 (/compare/mediaingest): the ONE pacing hook around every capture fetch -- see vc_pace_hook
51import "nx_ingest_admit.nx"
52import "nx_url_safety_lib.nx" // R11: the ONE SSRF url ruler, shared with nx_clean_serve_daemon and nx_cleanview // R2 (/compare/mediaingest): disk-floor admission at the chokepoint -- refuse a torn write on a full volume
53const VC_MAGIC_4096: i64 = 4096
54const VC_DISK_REFUSE: i64 = 0 - 2 // vc_ingest_file's distinct return for a disk-floor refusal (callers read <0 as fail, correctly)
55const VC_EXIT_UNSAFE_URL: i64 = 9 // R11: the edge verb refused an SSRF-unsafe url; distinct from a fetch failure (6) so a caller can tell a REFUSAL from a FAILURE
56const VC_MAGIC_1024: i64 = 1024
57const VC_MAGIC_65536: i64 = 65536
58const VC_MAGIC_8192: i64 = 8192
59
60const VC_URL_CAP: i64 = 2048
61const VC_PATH_CAP: i64 = 2048
62const VC_PAGE_CAP: i64 = 4194304
63const VC_LINKS_CAP: i64 = 1048576
64const VC_ITEMS_CAP: i64 = 262144
65const VC_VISIT_CAP: i64 = 262144
66const VC_HEAD_CAP: i64 = 8192
67const VC_REC_CAP: i64 = 4096
68const VC_REPORT_CAP: i64 = 65536
69const VC_EXT_CAP: i64 = 64
70const VC_HOST_CAP: i64 = 512
71const VC_CID_CAP: i64 = 128
72const VC_ROOT_CAP: i64 = 512
73
74const VC_DEF_ASSETS: i64 = 64
75const VC_DEF_PAGES: i64 = 16
76const VC_MAX_ASSETS: i64 = 512
77const VC_MAX_PAGES: i64 = 256
78
79// Compiled defaults; overridable from knowledge/status/vault_roots.conf.
80const VC_REF_ROOT: *u8 = "/volume1/vault" as *u8
81const VC_PHYS_ROOT: *u8 = "/volume1/homes/elderwesto/vaultfs" as *u8
82const VC_ROOTS_CONF: *u8 = "knowledge/status/vault_roots.conf" as *u8
83
84const VC_ST_FOUND: i64 = 0
85const VC_ST_NEW: i64 = 1
86const VC_ST_DUP: i64 = 2
87const VC_ST_FAIL: i64 = 3
88const VC_ST_PAGE: i64 = 4
89const VC_ST_PAGES: i64 = 5
90const VC_ST_N: i64 = 6
91
92func vc_strlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
93
94func vc_cat(dst: *u8, off: i64, s: *u8) -> i64 {
95 var i: i64 = 0
96 while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 }
97 return off + i
98}
99
100func vc_eq(a: *u8, b: *u8) -> i64 {
101 var i: i64 = 0
102 while 1 == 1 {
103 if a[i] != b[i] { return 0 }
104 if a[i] == (0 as u8) { return 1 }
105 i = i + 1
106 }
107 return 0
108}
109
110func vc_w(s: *u8) -> i64 { sys_write(1, s, vc_strlen(s)); return 0 }
111func vc_werr(s: *u8) -> i64 { sys_write(2, s, vc_strlen(s)); return 0 }
112
113func vc_n(v: i64) -> i64 {
114 let d: *u8 = sys_mmap(32)
115 let dl: i64 = mv_u_dec(d, 0, v)
116 sys_write(1, d, dl)
117 sys_munmap(d, 32)
118 return 0
119}
120// stderr twin of vc_n -- a refused capture announces its numbers on the error channel (R2)
121func vc_werr_n(v: i64) -> i64 {
122 let d: *u8 = sys_mmap(32)
123 let dl: i64 = mv_u_dec(d, 0, v)
124 sys_write(2, d, dl)
125 sys_munmap(d, 32)
126 return 0
127}
128
129func vc_lower(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c }
130
131func vc_has_pfx(buf: *u8, pfx: *u8) -> i64 {
132 var i: i64 = 0
133 while pfx[i] != (0 as u8) {
134 if buf[i] != pfx[i] { return 0 }
135 i = i + 1
136 }
137 return 1
138}
139
140func vc_phys_of(ref: *u8, refroot: *u8, physroot: *u8, out: *u8, cap: i64) -> i64 {
141 if vc_has_pfx(ref, refroot) == 0 {
142 let d: i64 = vc_cat(out, 0, ref)
143 out[d] = 0 as u8
144 return d
145 }
146 let rl: i64 = vc_strlen(refroot)
147 var o: i64 = vc_cat(out, 0, physroot)
148 var i: i64 = rl
149 while ref[i] != (0 as u8) { out[o] = ref[i]; o = o + 1; i = i + 1 }
150 out[o] = 0 as u8
151 return o
152}
153
154func vc_conf_field(buf: *u8, n: i64, key: *u8, out: *u8, cap: i64) -> i64 {
155 var ls: i64 = 0
156 var i: i64 = 0
157 while i <= n {
158 var eol: i64 = 0
159 if i == n { eol = 1 } else { if buf[i] == (10 as u8) { eol = 1 } }
160 if eol == 1 {
161 var m: i64 = 1
162 var k: i64 = 0
163 while key[k] != (0 as u8) {
164 if ls + k >= i { m = 0 } else { if buf[ls + k] != key[k] { m = 0 } }
165 k = k + 1
166 }
167 if m == 1 {
168 var o: i64 = 0
169 var j: i64 = ls + k
170 while j < i {
171 if buf[j] != (13 as u8) { if o < cap - 1 { out[o] = buf[j]; o = o + 1 } }
172 j = j + 1
173 }
174 out[o] = 0 as u8
175 if o > 0 { return o }
176 }
177 ls = i + 1
178 }
179 i = i + 1
180 }
181 return 0
182}
183
184func vc_load_roots(ref_out: *u8, phys_out: *u8) -> i64 {
185 vc_cat(ref_out, 0, VC_REF_ROOT); ref_out[vc_strlen(VC_REF_ROOT)] = 0 as u8
186 vc_cat(phys_out, 0, VC_PHYS_ROOT); phys_out[vc_strlen(VC_PHYS_ROOT)] = 0 as u8
187 let szbox: *i64 = sys_mmap(16) as *i64
188 let buf: *u8 = sys_read_file(VC_ROOTS_CONF, szbox)
189 if (buf as i64) != 0 {
190 if szbox[0] > 0 {
191 vc_conf_field(buf, szbox[0], "ref_root=" as *u8, ref_out, VC_ROOT_CAP)
192 vc_conf_field(buf, szbox[0], "phys_root=" as *u8, phys_out, VC_ROOT_CAP)
193 }
194 }
195 sys_munmap(szbox as *u8, 16)
196 return 1
197}
198
199func vc_mkdirp(path: *u8) -> i64 {
200 let tmp: *u8 = sys_mmap(VC_PATH_CAP)
201 var n: i64 = 0
202 while path[n] != (0 as u8) {
203 if n < VC_PATH_CAP - 2 { tmp[n] = path[n] }
204 n = n + 1
205 }
206 if n >= VC_PATH_CAP - 2 { sys_munmap(tmp, VC_PATH_CAP); return 0 - 1 }
207 tmp[n] = 0 as u8
208
209 var last: i64 = 0 - 1
210 var i: i64 = 0
211 while i < n { if tmp[i] == (47 as u8) { last = i } i = i + 1 }
212 if last <= 0 { sys_munmap(tmp, VC_PATH_CAP); return 0 }
213
214 var j: i64 = 1
215 while j < last {
216 if tmp[j] == (47 as u8) {
217 tmp[j] = 0 as u8
218 sys_mkdir(tmp, 493)
219 tmp[j] = 47 as u8
220 }
221 j = j + 1
222 }
223 tmp[last] = 0 as u8
224 sys_mkdir(tmp, 493)
225 sys_munmap(tmp, VC_PATH_CAP)
226 return 1
227}
228
229func vc_ext_of(url: *u8, deflt: *u8, out: *u8, cap: i64) -> i64 {
230 let base: *u8 = sys_mmap(VC_URL_CAP)
231 let have: i64 = mvf_basename(url, base, VC_URL_CAP)
232 if have == 0 {
233 sys_munmap(base, VC_URL_CAP)
234 let d: i64 = vc_cat(out, 0, deflt)
235 out[d] = 0 as u8
236 return d
237 }
238 var n: i64 = 0
239 var cut: i64 = 0 - 1
240 while base[n] != (0 as u8) {
241 if cut < 0 {
242 if base[n] == (63 as u8) { cut = n }
243 if base[n] == (35 as u8) { cut = n }
244 }
245 n = n + 1
246 }
247 if cut >= 0 { base[cut] = 0 as u8; n = cut }
248
249 var dot: i64 = 0 - 1
250 var i: i64 = 0
251 while i < n { if base[i] == (46 as u8) { dot = i } i = i + 1 }
252
253 var ok: i64 = 1
254 if dot < 0 { ok = 0 }
255 if dot + 1 >= n { ok = 0 }
256 if n - dot - 1 >= cap { ok = 0 }
257 if n - dot - 1 > 12 { ok = 0 }
258
259 if ok == 1 {
260 var k: i64 = dot + 1
261 while k < n {
262 let c: i64 = base[k] as i64
263 var good: i64 = 0
264 if c >= 48 { if c <= 57 { good = 1 } }
265 if c >= 65 { if c <= 90 { good = 1 } }
266 if c >= 97 { if c <= 122 { good = 1 } }
267 if good == 0 { ok = 0 }
268 k = k + 1
269 }
270 }
271
272 if ok == 0 {
273 sys_munmap(base, VC_URL_CAP)
274 let d2: i64 = vc_cat(out, 0, deflt)
275 out[d2] = 0 as u8
276 return d2
277 }
278 var o: i64 = 0
279 var j: i64 = dot + 1
280 while j < n { out[o] = vc_lower(base[j] as i64) as u8; o = o + 1; j = j + 1 }
281 out[o] = 0 as u8
282 sys_munmap(base, VC_URL_CAP)
283 return o
284}
285
286func vc_ingest_file(store: *u8, ref_root: *u8, phys_root: *u8, tmppath: *u8,
287 source: *u8, ext: *u8, class_code: i64,
288 cidout: *u8, refout: *u8) -> i64 {
289 // R2 (/compare/mediaingest): disk-floor admission BEFORE this item is hashed and committed. The vault volume
290 // (phys_root) must hold at least the configured floor free; below it, or if the disk is UNMEASURABLE, REFUSE
291 // rather than grow the corpus onto a full volume. The refusal ANNOUNCES the two numbers -- a refusal that does
292 // not say how much was free and how much was needed is not actionable. The already-fetched tmp is unlinked so a
293 // refused capture leaves nothing behind. Callers read a negative return as a failure, which is correct.
294 let da: *i64 = sys_mmap(16) as *i64
295 let df: *i64 = sys_mmap(16) as *i64
296 let dv: i64 = ig_disk_floor(phys_root, da, df)
297 if dv != IG_ADMIT {
298 vc_werr("nx_vault_capture: ingest REFUSED -- " as *u8); vc_werr(ig_verdict_str(dv))
299 vc_werr(" free=" as *u8); vc_werr_n(da[0]); vc_werr(" floor=" as *u8); vc_werr_n(df[0]); vc_werr("\n" as *u8)
300 sys_unlinkat(tmppath)
301 sys_munmap(da as *u8, 16)
302 sys_munmap(df as *u8, 16)
303 return VC_DISK_REFUSE
304 }
305 sys_munmap(da as *u8, 16)
306 sys_munmap(df as *u8, 16)
307 let szbox: *i64 = sys_mmap(16) as *i64
308 szbox[0] = 0
309 if cid_of_file_chunk(tmppath, CID_FILE_CHUNK, szbox, cidout) < 0 {
310 sys_munmap(szbox as *u8, 16)
311 return 0 - 1
312 }
313
314 let head: *u8 = sys_mmap(VC_HEAD_CAP)
315 let fd: i64 = sys_openat_rd(tmppath)
316 if fd < 0 {
317 sys_munmap(head, VC_HEAD_CAP)
318 sys_munmap(szbox as *u8, 16)
319 return 0 - 1
320 }
321 var hn: i64 = 0
322 var go: i64 = 1
323 while go == 1 {
324 let dst: *u8 = ((head as i64) + hn) as *u8
325 let r: i64 = sys_read(fd, dst, VC_HEAD_CAP - hn)
326 if r <= 0 { go = 0 } else {
327 hn = hn + r
328 if hn >= VC_HEAD_CAP { go = 0 }
329 }
330 }
331 sys_close(fd)
332
333 let vtype: i64 = mv_classify_plan(head, hn, class_code, MV_CTX_NONE, ref_root,
334 source, ext, refout, cidout)
335 sys_munmap(head, VC_HEAD_CAP)
336
337 let gp: *i64 = sys_mmap(16) as *i64
338 let gl: *i64 = sys_mmap(16) as *i64
339 let found: i64 = mv_store_get(store, cidout, gp, gl)
340 sys_munmap(gp as *u8, 16)
341 sys_munmap(gl as *u8, 16)
342 if found == 1 {
343 sys_unlinkat(tmppath)
344 sys_munmap(szbox as *u8, 16)
345 return 0
346 }
347
348 let phys: *u8 = sys_mmap(VC_PATH_CAP)
349 vc_phys_of(refout, ref_root, phys_root, phys, VC_PATH_CAP)
350 vc_mkdirp(phys)
351 if sys_renameat(tmppath, phys) < 0 {
352 sys_munmap(phys, VC_PATH_CAP)
353 sys_munmap(szbox as *u8, 16)
354 return 0 - 1
355 }
356 sys_munmap(phys, VC_PATH_CAP)
357
358 let rec: *u8 = sys_mmap(VC_REC_CAP)
359 let rl: i64 = mv_record_build(rec, class_code, vtype, MV_LVL_TS, source, refout)
360 let rc: i64 = mv_store_put(store, cidout, rec, rl)
361 sys_munmap(rec, VC_REC_CAP)
362 sys_munmap(szbox as *u8, 16)
363 if rc < 0 { return 0 - 1 }
364 return 1
365}
366
367// R0 (/compare/mediaingest, 2026-08-30): THE pacing hook. Every fetch this organ makes goes through it, before
368// (phase 0: the polite wait for the url's host, served by sleeping) and after (phase 1: the REAL status fed back so
369// a 429 or 503 becomes a longer next interval, persisted per host). It DELEGATES to nx_paced_fetch, which delegates
370// to nx_crawl_pace -- one ruler, no second copy. MEASURED before this hook: this organ imported no pacing at all,
371// so a site capture hit its host as fast as the asset loop ran. retry_after_s is 0 here on purpose: the fetch lib
372// hands back bytes and a status, not the headers, and a guessed Retry-After would be a fabricated measurement.
373func vc_pace_hook(url: *u8, phase: i64, status: i64) -> i64 {
374 if phase == 0 { return pf_before(url) }
375 return pf_after(url, status, 0)
376}
377
378func vc_fetch_tmp(store_i: i64, phys_root: *u8, url: *u8, seqn: i64,
379 out: *u8, stbox: *i64) -> i64 {
380 var o: i64 = vc_cat(out, 0, phys_root)
381 o = vc_cat(out, o, "/_incoming/cap" as *u8)
382 o = mv_u_dec(out, o, seqn)
383 out[o] = 0 as u8
384 vc_mkdirp(out)
385 let fd: i64 = sys_openat_wr(out, 420)
386 if fd < 0 { return 0 - 1 }
387 stbox[0] = 0
388 vc_pace_hook(url, 0, 0)
389 let n: i64 = hf_fetch_to_file(store_i, url, 0, 0, fd, 0, stbox)
390 vc_pace_hook(url, 1, stbox[0])
391 sys_close(fd)
392 if n <= 0 { sys_unlinkat(out); return 0 - 1 }
393 if stbox[0] != 200 { sys_unlinkat(out); return 0 - 2 }
394 return n
395}
396
397func vc_capture_media(store_i: i64, store: *u8, ref_root: *u8, phys_root: *u8,
398 url: *u8, source: *u8, class_code: i64, seqn: i64,
399 cidout: *u8) -> i64 {
400 let tmp: *u8 = sys_mmap(VC_PATH_CAP)
401 let stbox: *i64 = sys_mmap(16) as *i64
402 let n: i64 = vc_fetch_tmp(store_i, phys_root, url, seqn, tmp, stbox)
403 sys_munmap(stbox as *u8, 16)
404 if n < 0 { sys_munmap(tmp, VC_PATH_CAP); return n }
405
406 let ext: *u8 = sys_mmap(VC_EXT_CAP)
407 vc_ext_of(url, "bin" as *u8, ext, VC_EXT_CAP)
408 let ref: *u8 = sys_mmap(VC_PATH_CAP)
409 let r: i64 = vc_ingest_file(store, ref_root, phys_root, tmp, source, ext,
410 class_code, cidout, ref)
411 sys_munmap(ext, VC_EXT_CAP)
412 sys_munmap(ref, VC_PATH_CAP)
413 sys_munmap(tmp, VC_PATH_CAP)
414 return r
415}
416
417func vc_items_add(items: *u8, io: i64, cid: *u8) -> i64 {
418 let cl: i64 = vc_strlen(cid)
419 if io + cl + 2 >= VC_ITEMS_CAP { return 0 - 1 }
420 var y: i64 = 0
421 while y < cl { items[io + y] = cid[y]; y = y + 1 }
422 items[io + cl] = 10 as u8
423 return io + cl + 1
424}
425
426func vc_seen(set: *u8, n: i64, hay: *u8) -> i64 {
427 let hl: i64 = vc_strlen(hay)
428 var ls: i64 = 0
429 var i: i64 = 0
430 while i <= n {
431 var eol: i64 = 0
432 if i == n { eol = 1 } else { if set[i] == (10 as u8) { eol = 1 } }
433 if eol == 1 {
434 if i - ls == hl {
435 var same: i64 = 1
436 var k: i64 = 0
437 while k < hl { if set[ls + k] != hay[k] { same = 0 } k = k + 1 }
438 if same == 1 { return 1 }
439 }
440 ls = i + 1
441 }
442 i = i + 1
443 }
444 return 0
445}
446
447func vc_page_core(store_i: i64, store: *u8, cpfx: *u8, ref_root: *u8, phys_root: *u8,
448 url: *u8, source: *u8, body: *u8, blen: i64, max_assets: i64,
449 site_col: *u8, page_col: *u8, stats: *i64,
450 links_out: *u8, links_cap: i64, links_len: *i64) -> i64 {
451 let items: *u8 = sys_mmap(VC_ITEMS_CAP)
452 var io: i64 = 0
453
454 let ptmp: *u8 = sys_mmap(VC_PATH_CAP)
455 var po: i64 = vc_cat(ptmp, 0, phys_root)
456 po = vc_cat(ptmp, po, "/_incoming/page" as *u8)
457 po = mv_u_dec(ptmp, po, stats[VC_ST_PAGES])
458 ptmp[po] = 0 as u8
459 vc_mkdirp(ptmp)
460 let pcid: *u8 = sys_mmap(VC_CID_CAP)
461 let pfd: i64 = sys_openat_wr(ptmp, 420)
462 if pfd >= 0 {
463 sys_write(pfd, body, blen)
464 sys_close(pfd)
465 let pref: *u8 = sys_mmap(VC_PATH_CAP)
466 let pr: i64 = vc_ingest_file(store, ref_root, phys_root, ptmp, source,
467 "html" as *u8, MV_CLASS_REAL, pcid, pref)
468 sys_munmap(pref, VC_PATH_CAP)
469 if pr >= 0 {
470 stats[VC_ST_PAGE] = stats[VC_ST_PAGE] + 1
471 let r2: i64 = vc_items_add(items, io, pcid)
472 if r2 >= 0 { io = r2 }
473 } else { stats[VC_ST_FAIL] = stats[VC_ST_FAIL] + 1 }
474 } else { stats[VC_ST_FAIL] = stats[VC_ST_FAIL] + 1 }
475 sys_munmap(ptmp, VC_PATH_CAP)
476
477 let imgs: *u8 = sys_mmap(VC_LINKS_CAP)
478 let inn: i64 = mvf_extract(body, blen, "img" as *u8, "src" as *u8,
479 "" as *u8, imgs, VC_LINKS_CAP)
480 let one: *u8 = sys_mmap(VC_URL_CAP)
481 let href: *u8 = sys_mmap(VC_URL_CAP)
482 let acid: *u8 = sys_mmap(VC_CID_CAP)
483 var got: i64 = 0
484
485 if inn > 0 {
486 var ls: i64 = 0
487 var i: i64 = 0
488 while i <= inn {
489 var eol: i64 = 0
490 if i == inn { eol = 1 } else { if imgs[i] == (10 as u8) { eol = 1 } }
491 if eol == 1 {
492 let hl: i64 = i - ls
493 if hl > 0 {
494 if hl < VC_URL_CAP - 1 {
495 if got < max_assets {
496 stats[VC_ST_FOUND] = stats[VC_ST_FOUND] + 1
497 var q: i64 = 0
498 while q < hl { href[q] = imgs[ls + q]; q = q + 1 }
499 href[hl] = 0 as u8
500 if mvf_resolve(url, href, one, VC_URL_CAP) > 0 {
501 let seqn: i64 = stats[VC_ST_PAGES] * VC_MAGIC_4096 + got + 1
502 let r: i64 = vc_capture_media(store_i, store, ref_root, phys_root,
503 one, source, MV_CLASS_REAL, seqn, acid)
504 if r == 1 {
505 stats[VC_ST_NEW] = stats[VC_ST_NEW] + 1
506 let ra: i64 = vc_items_add(items, io, acid)
507 if ra >= 0 { io = ra }
508 got = got + 1
509 }
510 if r == 0 {
511 stats[VC_ST_DUP] = stats[VC_ST_DUP] + 1
512 let rd: i64 = vc_items_add(items, io, acid)
513 if rd >= 0 { io = rd }
514 got = got + 1
515 }
516 if r < 0 { stats[VC_ST_FAIL] = stats[VC_ST_FAIL] + 1 }
517 } else { stats[VC_ST_FAIL] = stats[VC_ST_FAIL] + 1 }
518 }
519 }
520 }
521 ls = i + 1
522 }
523 i = i + 1
524 }
525 }
526 sys_munmap(imgs, VC_LINKS_CAP)
527
528 if io > 0 {
529 mvc_batch_add_pfx(cpfx, page_col, items, io)
530 mvc_batch_add_pfx(cpfx, site_col, items, io)
531 }
532 sys_munmap(items, VC_ITEMS_CAP)
533
534 if links_cap > 0 {
535 links_len[0] = 0
536 let ln: i64 = mvf_extract(body, blen, "a" as *u8, "href" as *u8,
537 "" as *u8, links_out, links_cap)
538 if ln > 0 { if ln < links_cap { links_len[0] = ln } }
539 }
540
541 sys_munmap(one, VC_URL_CAP)
542 sys_munmap(href, VC_URL_CAP)
543 sys_munmap(acid, VC_CID_CAP)
544 sys_munmap(pcid, VC_CID_CAP)
545 return got
546}
547
548func vc_declare_page_cols(cpfx: *u8, url: *u8, host: *u8,
549 site_col: *u8, page_col: *u8) -> i64 {
550 mvc_declare_pfx(cpfx, "site" as *u8, host, host, host, url, "-" as *u8, site_col)
551 let pkey: *u8 = sys_mmap(VC_URL_CAP)
552 var i: i64 = 0
553 while url[i] != (0 as u8) {
554 if i < VC_URL_CAP - 1 { pkey[i] = url[i] }
555 i = i + 1
556 }
557 if i > VC_URL_CAP - 1 { i = VC_URL_CAP - 1 }
558 pkey[i] = 0 as u8
559 mvc_declare_pfx(cpfx, "page" as *u8, host, pkey, pkey, url, site_col, page_col)
560 sys_munmap(pkey, VC_URL_CAP)
561 return 1
562}
563
564func vc_report_stats(report: *u8, off: i64, stats: *i64) -> i64 {
565 var o: i64 = off
566 o = vc_cat(report, o, ",\"page_items\":" as *u8)
567 o = mv_u_dec(report, o, stats[VC_ST_PAGE])
568 o = vc_cat(report, o, ",\"assets_found\":" as *u8)
569 o = mv_u_dec(report, o, stats[VC_ST_FOUND])
570 o = vc_cat(report, o, ",\"stored\":" as *u8)
571 o = mv_u_dec(report, o, stats[VC_ST_NEW])
572 o = vc_cat(report, o, ",\"dup\":" as *u8)
573 o = mv_u_dec(report, o, stats[VC_ST_DUP])
574 o = vc_cat(report, o, ",\"failed\":" as *u8)
575 o = mv_u_dec(report, o, stats[VC_ST_FAIL])
576 return o
577}
578
579func vc_capture_page(store_i: i64, store: *u8, cpfx: *u8, ref_root: *u8, phys_root: *u8,
580 url: *u8, source: *u8, max_assets: i64, report: *u8, rcap: i64) -> i64 {
581 let host: *u8 = sys_mmap(VC_HOST_CAP)
582 mvf_host_of(url, host)
583
584 let site_col: *u8 = sys_mmap(VC_CID_CAP)
585 let page_col: *u8 = sys_mmap(VC_CID_CAP)
586 vc_declare_page_cols(cpfx, url, host, site_col, page_col)
587
588 let page: *u8 = sys_mmap(VC_PAGE_CAP)
589 vc_pace_hook(url, 0, 0)
590 let pn: i64 = hf_fetch(store_i, url, 0, 0, page, VC_PAGE_CAP)
591 var pst: i64 = 0
592 if pn > 0 { pst = hf_status(page, pn) }
593 vc_pace_hook(url, 1, pst)
594 if pn <= 0 { sys_munmap(page, VC_PAGE_CAP); return 0 - 2 }
595 var boff: i64 = hf_body_off(page, pn)
596 if boff < 0 { boff = 0 }
597 let body: *u8 = ((page as i64) + boff) as *u8
598
599 let stats: *i64 = sys_mmap(8 * VC_ST_N) as *i64
600 var z: i64 = 0
601 while z < VC_ST_N { stats[z] = 0; z = z + 1 }
602 stats[VC_ST_PAGES] = 1
603
604 let lbox: *i64 = sys_mmap(16) as *i64
605 let got: i64 = vc_page_core(store_i, store, cpfx, ref_root, phys_root, url, source,
606 body, pn - boff, max_assets, site_col, page_col, stats,
607 "" as *u8, 0, lbox)
608 sys_munmap(page, VC_PAGE_CAP)
609 sys_munmap(lbox as *u8, 16)
610
611 var o: i64 = vc_cat(report, 0, "{\"scope\":\"page\",\"url\":\"" as *u8)
612 o = vc_cat(report, o, url)
613 o = vc_cat(report, o, "\",\"site_col\":\"" as *u8)
614 o = vc_cat(report, o, site_col)
615 o = vc_cat(report, o, "\",\"page_col\":\"" as *u8)
616 o = vc_cat(report, o, page_col)
617 o = vc_cat(report, o, "\"" as *u8)
618 o = vc_report_stats(report, o, stats)
619 o = vc_cat(report, o, "}" as *u8)
620 report[o] = 10 as u8
621 report[o + 1] = 0 as u8
622
623 sys_munmap(stats as *u8, 8 * VC_ST_N)
624 sys_munmap(host, VC_HOST_CAP)
625 sys_munmap(site_col, VC_CID_CAP)
626 sys_munmap(page_col, VC_CID_CAP)
627 return got
628}
629
630func vc_capture_site(store_i: i64, store: *u8, cpfx: *u8, ref_root: *u8, phys_root: *u8,
631 url: *u8, source: *u8, max_pages: i64, max_assets: i64,
632 report: *u8, rcap: i64) -> i64 {
633 let host: *u8 = sys_mmap(VC_HOST_CAP)
634 mvf_host_of(url, host)
635
636 let site_col: *u8 = sys_mmap(VC_CID_CAP)
637 mvc_declare_pfx(cpfx, "site" as *u8, host, host, host, url, "-" as *u8, site_col)
638
639 let frontier: *u8 = sys_mmap(VC_VISIT_CAP)
640 let visited: *u8 = sys_mmap(VC_VISIT_CAP)
641 var fo: i64 = vc_cat(frontier, 0, url)
642 frontier[fo] = 10 as u8
643 fo = fo + 1
644 var vo: i64 = 0
645
646 let stats: *i64 = sys_mmap(8 * VC_ST_N) as *i64
647 var z: i64 = 0
648 while z < VC_ST_N { stats[z] = 0; z = z + 1 }
649
650 let cur: *u8 = sys_mmap(VC_URL_CAP)
651 let one: *u8 = sys_mmap(VC_URL_CAP)
652 let href: *u8 = sys_mmap(VC_URL_CAP)
653 let lhost: *u8 = sys_mmap(VC_HOST_CAP)
654 let page_col: *u8 = sys_mmap(VC_CID_CAP)
655 let page: *u8 = sys_mmap(VC_PAGE_CAP)
656 let links: *u8 = sys_mmap(VC_LINKS_CAP)
657 let lbox: *i64 = sys_mmap(16) as *i64
658
659 var fp: i64 = 0
660 var pages: i64 = 0
661 var refused: i64 = 0
662
663 while fp < fo {
664 if pages >= max_pages { fp = fo } else {
665 var stop: i64 = 0 - 1
666 var q: i64 = fp
667 while q < fo {
668 if stop < 0 { if frontier[q] == (10 as u8) { stop = q } }
669 q = q + 1
670 }
671 if stop < 0 { stop = fo }
672
673 let cl: i64 = stop - fp
674 var ok: i64 = 1
675 if cl <= 0 { ok = 0 }
676 if cl >= VC_URL_CAP - 1 { ok = 0 }
677
678 if ok == 1 {
679 var k: i64 = 0
680 while k < cl { cur[k] = frontier[fp + k]; k = k + 1 }
681 cur[cl] = 0 as u8
682 if vc_seen(visited, vo, cur) == 1 { ok = 0 }
683 }
684 if ok == 1 {
685 if vo + cl + 2 < VC_VISIT_CAP {
686 var m: i64 = 0
687 while m < cl { visited[vo + m] = cur[m]; m = m + 1 }
688 visited[vo + cl] = 10 as u8
689 vo = vo + cl + 1
690 } else { ok = 0; refused = refused + 1 }
691 }
692
693 if ok == 1 {
694 vc_pace_hook(cur, 0, 0)
695 let pn: i64 = hf_fetch(store_i, cur, 0, 0, page, VC_PAGE_CAP)
696 var cst: i64 = 0
697 if pn > 0 { cst = hf_status(page, pn) }
698 vc_pace_hook(cur, 1, cst)
699 if pn <= 0 { stats[VC_ST_FAIL] = stats[VC_ST_FAIL] + 1 } else {
700 var boff: i64 = hf_body_off(page, pn)
701 if boff < 0 { boff = 0 }
702 let body: *u8 = ((page as i64) + boff) as *u8
703
704 pages = pages + 1
705 stats[VC_ST_PAGES] = pages
706 vc_declare_page_cols(cpfx, cur, host, site_col, page_col)
707 lbox[0] = 0
708 vc_page_core(store_i, store, cpfx, ref_root, phys_root, cur, source,
709 body, pn - boff, max_assets, site_col, page_col, stats,
710 links, VC_LINKS_CAP, lbox)
711
712 let ln: i64 = lbox[0]
713 var ls: i64 = 0
714 var i: i64 = 0
715 while i <= ln {
716 var eol: i64 = 0
717 if i == ln { eol = 1 } else { if links[i] == (10 as u8) { eol = 1 } }
718 if eol == 1 {
719 let hl: i64 = i - ls
720 if hl > 0 {
721 if hl < VC_URL_CAP - 1 {
722 var y: i64 = 0
723 while y < hl { href[y] = links[ls + y]; y = y + 1 }
724 href[hl] = 0 as u8
725 if mvf_resolve(cur, href, one, VC_URL_CAP) > 0 {
726 mvf_host_of(one, lhost)
727 if vc_eq(lhost, host) == 1 {
728 if vc_seen(visited, vo, one) == 0 {
729 if vc_seen(frontier, fo, one) == 0 {
730 let ul: i64 = vc_strlen(one)
731 if fo + ul + 2 < VC_VISIT_CAP {
732 var w: i64 = 0
733 while w < ul { frontier[fo + w] = one[w]; w = w + 1 }
734 frontier[fo + ul] = 10 as u8
735 fo = fo + ul + 1
736 } else { refused = refused + 1 }
737 }
738 }
739 }
740 }
741 }
742 }
743 ls = i + 1
744 }
745 i = i + 1
746 }
747 }
748 }
749 fp = stop + 1
750 }
751 }
752
753 var o: i64 = vc_cat(report, 0, "{\"scope\":\"site\",\"host\":\"" as *u8)
754 o = vc_cat(report, o, host)
755 o = vc_cat(report, o, "\",\"site_col\":\"" as *u8)
756 o = vc_cat(report, o, site_col)
757 o = vc_cat(report, o, "\",\"pages\":" as *u8)
758 o = mv_u_dec(report, o, pages)
759 o = vc_report_stats(report, o, stats)
760 o = vc_cat(report, o, ",\"budget_refused\":" as *u8)
761 o = mv_u_dec(report, o, refused)
762 o = vc_cat(report, o, "}" as *u8)
763 report[o] = 10 as u8
764 report[o + 1] = 0 as u8
765
766 sys_munmap(page, VC_PAGE_CAP)
767 sys_munmap(links, VC_LINKS_CAP)
768 sys_munmap(frontier, VC_VISIT_CAP)
769 sys_munmap(visited, VC_VISIT_CAP)
770 sys_munmap(stats as *u8, 8 * VC_ST_N)
771 sys_munmap(lbox as *u8, 16)
772 return pages
773}
774
775func vc_write_file(path: *u8, content: *u8, n: i64) -> i64 {
776 vc_mkdirp(path)
777 let fd: i64 = sys_openat_wr(path, 420)
778 if fd < 0 { return 0 - 1 }
779 sys_write(fd, content, n)
780 sys_close(fd)
781 return n
782}
783
784func do_selftest() -> i64 {
785 var fails: i64 = 0
786 let store: *u8 = "knowledge/store/vctest-" as *u8
787 let cpfx: *u8 = "knowledge/store/vctestc-" as *u8
788 let refroot: *u8 = "/volume1/vault" as *u8
789 let physroot: *u8 = "/tmp/vcphys" as *u8
790
791 let e: *u8 = sys_mmap(VC_EXT_CAP)
792 vc_ext_of("https://h/a/b/photo.JPG?x=1" as *u8, "bin" as *u8, e, VC_EXT_CAP)
793 if vc_eq(e, "jpg" as *u8) != 1 { fails = fails + 1 }
794 vc_w("T1 ext of photo.JPG?x=1 -> " as *u8); vc_w(e); vc_w("\n" as *u8)
795
796 vc_ext_of("https://cdn/stream/9f8a2b" as *u8, "bin" as *u8, e, VC_EXT_CAP)
797 if vc_eq(e, "bin" as *u8) != 1 { fails = fails + 1 }
798 vc_w("T2 extensionless CDN url -> " as *u8); vc_w(e); vc_w("\n" as *u8)
799
800 let tmp: *u8 = "/tmp/vcphys/_incoming/t3" as *u8
801 let png: *u8 = sys_mmap(64)
802 png[0] = 137 as u8; png[1] = 80 as u8; png[2] = 78 as u8; png[3] = 71 as u8
803 png[4] = 13 as u8; png[5] = 10 as u8; png[6] = 26 as u8; png[7] = 10 as u8
804 // ★ seed with the current epoch so each gate RUN produces a FRESH CID.
805 // A selftest that writes a fixed fixture into a persistent store is not
806 // repeatable: the SECOND run sees the FIRST run's record and every
807 // new-ingest assertion flips to dup. Uniqueness per run restores isolation.
808 let tnonce: i64 = sys_now_realtime_sec()
809 var f: i64 = 8
810 while f < 16 { png[f] = ((tnonce >> ((f - 8) * 8)) & 255) as u8; f = f + 1 }
811 while f < 64 { png[f] = f as u8; f = f + 1 }
812 vc_write_file(tmp, png, 64)
813 let cid: *u8 = sys_mmap(VC_CID_CAP)
814 let ref: *u8 = sys_mmap(VC_PATH_CAP)
815 let r1: i64 = vc_ingest_file(store, refroot, physroot, tmp, "test" as *u8,
816 "png" as *u8, MV_CLASS_REAL, cid, ref)
817 if r1 != 1 { fails = fails + 1 }
818 vc_w("T3 first ingest (want 1=new) -> " as *u8); vc_n(r1)
819 vc_w(" ref " as *u8); vc_w(ref); vc_w("\n" as *u8)
820
821 if vc_has_pfx(ref, refroot) != 1 { fails = fails + 1 }
822 if vc_has_pfx(ref, physroot) == 1 { fails = fails + 1 }
823 vc_w("T13 record ref is LOGICAL (/volume1/vault), not phys -> ok\n" as *u8)
824
825 let phys: *u8 = sys_mmap(VC_PATH_CAP)
826 vc_phys_of(ref, refroot, physroot, phys, VC_PATH_CAP)
827 let pchk: i64 = sys_openat_rd(phys)
828 if pchk < 0 { fails = fails + 1 } else { sys_close(pchk) }
829 let lchk: i64 = sys_openat_rd(ref)
830 if lchk >= 0 { fails = fails + 1; sys_close(lchk) }
831 vc_w("T14 blob at PHYSICAL " as *u8); vc_w(phys)
832 vc_w(" (fd " as *u8); vc_n(pchk); vc_w("), logical absent (fd " as *u8); vc_n(lchk); vc_w(")\n" as *u8)
833
834 if vc_has_pfx(phys, physroot) != 1 { fails = fails + 1 }
835 vc_w("T15 vc_phys_of round-trips ref->phys under phys_root -> ok\n" as *u8)
836
837 let passt: *u8 = sys_mmap(VC_PATH_CAP)
838 vc_phys_of("/some/other/path.png" as *u8, refroot, physroot, passt, VC_PATH_CAP)
839 if vc_eq(passt, "/some/other/path.png" as *u8) != 1 { fails = fails + 1 }
840 vc_w("T16 non-ref-root path passes through unchanged -> " as *u8); vc_w(passt); vc_w("\n" as *u8)
841
842 vc_write_file(tmp, png, 64)
843 let cid2: *u8 = sys_mmap(VC_CID_CAP)
844 let ref2: *u8 = sys_mmap(VC_PATH_CAP)
845 let r2: i64 = vc_ingest_file(store, refroot, physroot, tmp, "test" as *u8,
846 "png" as *u8, MV_CLASS_REAL, cid2, ref2)
847 if r2 != 0 { fails = fails + 1 }
848 if vc_eq(cid, cid2) != 1 { fails = fails + 1 }
849 vc_w("T5 re-ingest identical bytes (want 0=dup, same cid) -> " as *u8); vc_n(r2); vc_w("\n" as *u8)
850
851 let scol: *u8 = sys_mmap(VC_CID_CAP)
852 mvc_declare_pfx(cpfx, "site" as *u8, "t.example" as *u8, "t.example" as *u8,
853 "t.example" as *u8, "https://t.example" as *u8, "-" as *u8, scol)
854 let ib: *u8 = sys_mmap(VC_MAGIC_1024)
855 let io: i64 = vc_items_add(ib, 0, cid)
856 mvc_batch_add_pfx(cpfx, scol, ib, io)
857
858 let mem: *u8 = sys_mmap(VC_MAGIC_65536)
859 let mn: i64 = mvc_items_pfx(cpfx, scol, mem, VC_MAGIC_65536)
860 var members: i64 = 0
861 var resolved: i64 = 0
862 let gp: *i64 = sys_mmap(16) as *i64
863 let gl: *i64 = sys_mmap(16) as *i64
864 let mcid: *u8 = sys_mmap(VC_CID_CAP)
865 var ls: i64 = 0
866 var i: i64 = 0
867 while i <= mn {
868 var eol: i64 = 0
869 if i == mn { eol = 1 } else { if mem[i] == (10 as u8) { eol = 1 } }
870 if eol == 1 {
871 let hl: i64 = i - ls
872 if hl > 0 {
873 if hl < VC_CID_CAP - 1 {
874 members = members + 1
875 var k: i64 = 0
876 while k < hl { mcid[k] = mem[ls + k]; k = k + 1 }
877 mcid[hl] = 0 as u8
878 if mv_store_get(store, mcid, gp, gl) == 1 { resolved = resolved + 1 }
879 }
880 }
881 ls = i + 1
882 }
883 i = i + 1
884 }
885 if members == 0 { fails = fails + 1 }
886 if resolved != members { fails = fails + 1 }
887 vc_w("T6 INVARIANT every collection member has a record -> " as *u8)
888 vc_n(resolved); vc_w("/" as *u8); vc_n(members); vc_w("\n" as *u8)
889
890 let ghost: *u8 = "nxc1-0000000000000000000000000000000000000000000000000000000000000000" as *u8
891 if mv_store_get(store, ghost, gp, gl) == 1 { fails = fails + 1 }
892 vc_w("T7 NEG never-ingested cid does NOT resolve\n" as *u8)
893
894 let html: *u8 = "<html><body><img src=\"/a/one.png\"><a href=\"/next\">n</a><img src=\"https://cdn.x/two.jpg\"></body></html>" as *u8
895 let outb: *u8 = sys_mmap(VC_MAGIC_8192)
896 let an: i64 = mvf_extract(html, vc_strlen(html), "img" as *u8, "src" as *u8,
897 "" as *u8, outb, VC_MAGIC_8192)
898 let acount: i64 = mvc_count_lines(outb, an)
899 if acount != 2 { fails = fails + 1 }
900 vc_w("T8 img srcs extracted (want 2) -> " as *u8); vc_n(acount); vc_w("\n" as *u8)
901
902 let res: *u8 = sys_mmap(VC_URL_CAP)
903 mvf_resolve("https://t.example/dir/page.html" as *u8, "/a/one.png" as *u8, res, VC_URL_CAP)
904 if vc_eq(res, "https://t.example/a/one.png" as *u8) != 1 { fails = fails + 1 }
905 vc_w("T9 relative -> absolute: " as *u8); vc_w(res); vc_w("\n" as *u8)
906
907 // T17 -- protocol-relative //host/path inherits the base scheme (the fix for
908 // the 8/14 Pima_cotton asset failures; the dominant CDN url shape)
909 let pr: *u8 = sys_mmap(VC_URL_CAP)
910 mvf_resolve("https://en.wikipedia.org/wiki/X" as *u8, "//upload.wikimedia.org/a/b.jpg" as *u8, pr, VC_URL_CAP)
911 if vc_eq(pr, "https://upload.wikimedia.org/a/b.jpg" as *u8) != 1 { fails = fails + 1 }
912 vc_w("T17 protocol-relative -> " as *u8); vc_w(pr); vc_w("\n" as *u8)
913
914 let h1: *u8 = sys_mmap(VC_HOST_CAP)
915 let h2: *u8 = sys_mmap(VC_HOST_CAP)
916 mvf_host_of("https://t.example/a" as *u8, h1)
917 mvf_host_of("https://evil.other/a" as *u8, h2)
918 if vc_eq(h1, h2) == 1 { fails = fails + 1 }
919 if vc_eq(h1, "t.example" as *u8) != 1 { fails = fails + 1 }
920 vc_w("T10 same-host filter separates " as *u8); vc_w(h1)
921 vc_w(" from " as *u8); vc_w(h2); vc_w("\n" as *u8)
922
923 let small: *u8 = sys_mmap(64)
924 let over: i64 = vc_items_add(small, VC_ITEMS_CAP - 4, cid)
925 if over >= 0 { fails = fails + 1 }
926 vc_w("T11 over-envelope membership add REFUSES -> " as *u8); vc_n(over); vc_w("\n" as *u8)
927
928 let set: *u8 = "https://t.example/abc\n" as *u8
929 if vc_seen(set, 21, "https://t.example/ab" as *u8) == 1 { fails = fails + 1 }
930 if vc_seen(set, 21, "https://t.example/abc" as *u8) != 1 { fails = fails + 1 }
931 vc_w("T12 visited-set matches whole lines only\n" as *u8)
932
933 vc_w("verdict=" as *u8)
934 if fails == 0 { vc_w("GREEN 17/17\n" as *u8) } else {
935 vc_w("RED fails=" as *u8); vc_n(fails); vc_w("\n" as *u8)
936 }
937 if fails == 0 { return 0 }
938 return 1
939}
940
941func vc_usage() -> i64 {
942 vc_werr("usage:\n nx_vault_capture media <url> [source]\n nx_vault_capture edge <url> [source] (media, with the SSRF url guard in front -- for urls chosen by an untrusted page)\n nx_vault_capture page <url> [source] [max-assets]\n nx_vault_capture site <url> [source] [max-pages] [max-assets]\n nx_vault_capture selftest\n" as *u8)
943 return 2
944}
945
946func argp(argv: *i64, i: i64) -> *u8 { return (argv[i]) as *u8 }
947
948func vc_atoi(s: *u8) -> i64 {
949 var v: i64 = 0
950 var i: i64 = 0
951 while s[i] != (0 as u8) {
952 let c: i64 = s[i] as i64
953 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } }
954 i = i + 1
955 }
956 return v
957}
958
959func main(argc: i64, argv: *i64) -> i64 {
960 if argc < 2 { return vc_usage() }
961 let verb: *u8 = argp(argv, 1)
962
963 if vc_eq(verb, "selftest" as *u8) == 1 { return do_selftest() }
964
965 if argc < 3 { return vc_usage() }
966 let url: *u8 = argp(argv, 2)
967
968 let store: *u8 = "knowledge/store/mvault-" as *u8
969 let cpfx: *u8 = "knowledge/store/mvcoll-" as *u8
970
971 let ref_root: *u8 = sys_mmap(VC_ROOT_CAP)
972 let phys_root: *u8 = sys_mmap(VC_ROOT_CAP)
973 vc_load_roots(ref_root, phys_root)
974
975 var source: *u8 = "web" as *u8
976 if argc > 3 { source = argp(argv, 3) }
977
978 let store_i: i64 = hf_store_load()
979 if store_i <= 0 {
980 vc_werr("ERR: trust store load failed (data/mozilla_certdata.txt on CWD?)\n" as *u8)
981 return 3
982 }
983
984 let report: *u8 = sys_mmap(VC_REPORT_CAP)
985
986 // ---- THE UNTRUSTED DOOR (/compare/mediaingest R11 ge_edge_push) -------------------------------
987 // `media` is the TRUSTED verb: an operator or an in-estate organ hands it a url it already vouches
988 // for, and its behaviour here is byte-unchanged. `edge` is the SAME capture with an SSRF admission
989 // check in front, and it exists because the browser-extension push hands this organ a url chosen by
990 // whatever page the user happened to be looking at. Fetching that unguarded is a server-side request
991 // forgery primitive pointed at our own LAN: this estate has already measured the shape once, when an
992 // unauthenticated SSRF-capable fetcher bound to INADDR_ANY answered from another host.
993 // The rule set is NOT re-implemented here -- it is nx_url_safety_lib, the one ruler shared with
994 // nx_clean_serve_daemon and nx_cleanview, so a host one surface learns to refuse is refused here too.
995 // The refusal NAMES WHICH RULE FIRED rather than saying "unsafe", because a guard whose refusals are
996 // indistinguishable cannot be told apart from a guard that refuses everything.
997 var do_media: i64 = 0
998 if vc_eq(verb, "media" as *u8) == 1 { do_media = 1 }
999 if vc_eq(verb, "edge" as *u8) == 1 {
1000 let ur: i64 = us_reason(url, vc_strlen(url))
1001 if ur != US_OK {
1002 vc_werr("EDGE REFUSED url-unsafe rule=" as *u8)
1003 vc_werr(us_reason_name(ur))
1004 vc_werr(" url=" as *u8)
1005 vc_werr(url)
1006 vc_werr(" -- refused BEFORE any fetch, so nothing on the private side was ever contacted\n" as *u8)
1007 return VC_EXIT_UNSAFE_URL
1008 }
1009 do_media = 1
1010 }
1011
1012 if do_media == 1 {
1013 let cid: *u8 = sys_mmap(VC_CID_CAP)
1014 let host: *u8 = sys_mmap(VC_HOST_CAP)
1015 mvf_host_of(url, host)
1016 let scol: *u8 = sys_mmap(VC_CID_CAP)
1017 mvc_declare_pfx(cpfx, "site" as *u8, host, host, host, url, "-" as *u8, scol)
1018
1019 let r: i64 = vc_capture_media(store_i, store, ref_root, phys_root, url,
1020 source, MV_CLASS_REAL, 1, cid)
1021 if r < 0 {
1022 vc_werr("ERR: capture failed (fetch or non-200)\n" as *u8)
1023 return 6
1024 }
1025 let ib: *u8 = sys_mmap(VC_MAGIC_1024)
1026 let io: i64 = vc_items_add(ib, 0, cid)
1027 if io > 0 { mvc_batch_add_pfx(cpfx, scol, ib, io) }
1028
1029 var o: i64 = vc_cat(report, 0, "{\"scope\":\"media\",\"cid\":\"" as *u8)
1030 o = vc_cat(report, o, cid)
1031 o = vc_cat(report, o, "\",\"site_col\":\"" as *u8)
1032 o = vc_cat(report, o, scol)
1033 o = vc_cat(report, o, "\",\"stored\":" as *u8)
1034 o = mv_u_dec(report, o, r)
1035 o = vc_cat(report, o, ",\"dup\":" as *u8)
1036 if r == 0 { o = mv_u_dec(report, o, 1) } else { o = mv_u_dec(report, o, 0) }
1037 o = vc_cat(report, o, "}" as *u8)
1038 report[o] = 10 as u8
1039 sys_write(1, report, o + 1)
1040 return 0
1041 }
1042
1043 if vc_eq(verb, "page" as *u8) == 1 {
1044 var ma: i64 = VC_DEF_ASSETS
1045 if argc > 4 { ma = vc_atoi(argp(argv, 4)) }
1046 if ma <= 0 { ma = VC_DEF_ASSETS }
1047 if ma > VC_MAX_ASSETS { ma = VC_MAX_ASSETS }
1048 let got: i64 = vc_capture_page(store_i, store, cpfx, ref_root, phys_root, url,
1049 source, ma, report, VC_REPORT_CAP)
1050 if got == (0 - 2) {
1051 vc_werr("ERR: page fetch failed\n" as *u8)
1052 return 6
1053 }
1054 sys_write(1, report, vc_strlen(report))
1055 return 0
1056 }
1057
1058 if vc_eq(verb, "site" as *u8) == 1 {
1059 var mp: i64 = VC_DEF_PAGES
1060 if argc > 4 { mp = vc_atoi(argp(argv, 4)) }
1061 if mp <= 0 { mp = VC_DEF_PAGES }
1062 if mp > VC_MAX_PAGES { mp = VC_MAX_PAGES }
1063 var ma: i64 = VC_DEF_ASSETS
1064 if argc > 5 { ma = vc_atoi(argp(argv, 5)) }
1065 if ma <= 0 { ma = VC_DEF_ASSETS }
1066 if ma > VC_MAX_ASSETS { ma = VC_MAX_ASSETS }
1067 vc_capture_site(store_i, store, cpfx, ref_root, phys_root, url, source,
1068 mp, ma, report, VC_REPORT_CAP)
1069 sys_write(1, report, vc_strlen(report))
1070 return 0
1071 }
1072
1073 return vc_usage()
1074}