code wiki / _hdl_build / nx_galx_manga.nx

nx_galx_manga.nx source

↩ module page · 148 lines · 6131 B

1// nx_galx_manga.nx -- MANGA collection + reader lib for the gallery (media-manager -> gallery migration). 2// A manga = an ordered set of page images extracted by nx_cbx_pages into knowledge/staging/media/reader/<slug>/ 3// (cbx.json manifest + page000.<ext> ...). This lib is the SOVEREIGN, pure-IO backend the gallery daemon wires 4// three thin routes onto, so it is gate-testable in isolation (no daemon/socket) -- exactly the nx_mgmt_upload 5// lib+gate split. Single responsibility: list staged manga / return a manga's page manifest / resolve+guard a 6// page-file path (path-traversal fail-closed) / idempotently register a slug in the manga index. NO fork here 7// (the daemon owns extraction). Sovereign: nx_syscalls only. license_tier: ORIGINAL 8import "nx_syscalls.nx" 9 10const MG_INDEX: *u8 = "knowledge/status/galx_manga_index.txt" 11const MG_READER: *u8 = "knowledge/staging/media/reader/" 12 13func mg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 14func mg_cat(dst: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[o + i] = s[i]; i = i + 1 } return o + i } 15func mg_utoa(dst: *u8, o: i64, v: i64) -> i64 { 16 let t: *u8 = sys_mmap(24); var m: i64 = v; var k: i64 = 0 17 if m == 0 { t[0] = 48 as u8; k = 1 } 18 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 19 var w: i64 = o; var i: i64 = 0 20 while i < k { dst[w] = t[k - 1 - i]; w = w + 1; i = i + 1 } 21 return w 22} 23 24// 1 iff s[0..n) is a SAFE single path component: [A-Za-z0-9._-] only, nonempty, <=128, and contains no ".." 25// (fail-closed path-traversal guard -- the security seam for /mangaraw and /api/manga/pages). 26func mg_name_ok(s: *u8, n: i64) -> i64 { 27 if n <= 0 { return 0 } 28 if n > 128 { return 0 } 29 var i: i64 = 0 30 while i < n { 31 let c: i64 = s[i] as i64 32 var ok: i64 = 0 33 if c >= 48 { if c <= 57 { ok = 1 } } 34 if c >= 65 { if c <= 90 { ok = 1 } } 35 if c >= 97 { if c <= 122 { ok = 1 } } 36 if c == 46 { ok = 1 } 37 if c == 95 { ok = 1 } 38 if c == 45 { ok = 1 } 39 if ok == 0 { return 0 } 40 i = i + 1 41 } 42 var j: i64 = 0 43 while j + 1 < n { if s[j] == (46 as u8) { if s[j + 1] == (46 as u8) { return 0 } } j = j + 1 } 44 return 1 45} 46 47func mg_read(path: *u8, buf: *u8, cap: i64) -> i64 { 48 let fd: i64 = sys_openat_rd(path) 49 if fd < 0 { return 0 - 1 } 50 var tot: i64 = 0; var r: i64 = 1 51 while r > 0 { 52 let dst: *u8 = ((buf as i64) + tot) as *u8 53 r = sys_read(fd, dst, cap - tot) 54 if r > 0 { tot = tot + r } 55 } 56 sys_close(fd) 57 return tot 58} 59 60// idempotently append slug + "\n" to the manga index (skips if the exact line already present). Returns 61// 1 = added, 0 = already there, -1 = bad slug / open fail. The daemon calls this after a successful extract. 62func mg_index_add(slug: *u8) -> i64 { 63 let sn: i64 = mg_slen(slug) 64 if mg_name_ok(slug, sn) == 0 { return 0 - 1 } 65 let buf: *u8 = sys_mmap(1048576) 66 let n: i64 = mg_read(MG_INDEX, buf, 1048576) 67 if n > 0 { 68 var i: i64 = 0; var ls: i64 = 0 69 while i <= n { 70 var nl: i64 = 0; if i == n { nl = 1 } else { if buf[i] == (10 as u8) { nl = 1 } } 71 if nl == 1 { 72 if i - ls == sn { 73 var eq: i64 = 1; var k: i64 = 0 74 while k < sn { if buf[ls + k] != slug[k] { eq = 0 } k = k + 1 } 75 if eq == 1 { return 0 } 76 } 77 ls = i + 1 78 } 79 i = i + 1 80 } 81 } 82 let fd: i64 = sys_openat_append(MG_INDEX, 0x1a4) 83 if fd < 0 { return 0 - 1 } 84 sys_write(fd, slug, sn) 85 sys_write(fd, "\n" as *u8, 1) 86 sys_close(fd) 87 return 1 88} 89 90// build {"kind":"manga","items":["slug",...],"total":N} from the index (skips malformed/unsafe slugs). Returns length. 91func mg_list_json(out: *u8, cap: i64) -> i64 { 92 let buf: *u8 = sys_mmap(1048576) 93 let n: i64 = mg_read(MG_INDEX, buf, 1048576) 94 var o: i64 = mg_cat(out, 0, "{\"kind\":\"manga\",\"items\":[" as *u8) 95 var total: i64 = 0 96 if n > 0 { 97 var i: i64 = 0; var ls: i64 = 0; var first: i64 = 1 98 while i <= n { 99 var nl: i64 = 0; if i == n { nl = 1 } else { if buf[i] == (10 as u8) { nl = 1 } } 100 if nl == 1 { 101 let ln: i64 = i - ls 102 if ln > 0 { 103 if mg_name_ok(((buf as i64) + ls) as *u8, ln) == 1 { 104 if first == 0 { out[o] = 44 as u8; o = o + 1 } 105 first = 0 106 out[o] = 34 as u8; o = o + 1 107 var k: i64 = 0; while k < ln { out[o] = buf[ls + k]; o = o + 1; k = k + 1 } 108 out[o] = 34 as u8; o = o + 1 109 total = total + 1 110 } 111 } 112 ls = i + 1 113 } 114 i = i + 1 115 } 116 } 117 o = mg_cat(out, o, "],\"total\":" as *u8) 118 o = mg_utoa(out, o, total) 119 o = mg_cat(out, o, "}" as *u8) 120 return o 121} 122 123// copy manga <slug>'s cbx.json manifest into out. Returns length, or 0 if the slug is unsafe/missing. 124func mg_pages_json(slug: *u8, out: *u8, cap: i64) -> i64 { 125 let sn: i64 = mg_slen(slug) 126 if mg_name_ok(slug, sn) == 0 { return 0 } 127 let path: *u8 = sys_mmap(1024) 128 var po: i64 = mg_cat(path, 0, MG_READER) 129 po = mg_cat(path, po, slug) 130 po = mg_cat(path, po, "/cbx.json" as *u8) 131 path[po] = 0 as u8 132 let n: i64 = mg_read(path, out, cap) 133 if n < 0 { return 0 } 134 return n 135} 136 137// resolve a page file to its on-disk path with fail-closed guards. 1 = out is a safe path under the reader dir, 138// 0 = slug/file unsafe (traversal rejected). Existence is checked by the caller (serve). 139func mg_page_path(slug: *u8, file: *u8, out: *u8) -> i64 { 140 let sn: i64 = mg_slen(slug); if mg_name_ok(slug, sn) == 0 { return 0 } 141 let fnn: i64 = mg_slen(file); if mg_name_ok(file, fnn) == 0 { return 0 } 142 var po: i64 = mg_cat(out, 0, MG_READER) 143 po = mg_cat(out, po, slug) 144 out[po] = 47 as u8; po = po + 1 145 po = mg_cat(out, po, file) 146 out[po] = 0 as u8 147 return 1 148}