code wiki / (root) / nx_media_jsexec.nx

nx_media_jsexec.nx source

↩ module page · 136 lines · 6879 B

1// nx_media_jsexec.nx -- JS-EXECUTION media harvester. Runs a page's JavaScript on the sovereign engine 2// and collects the media URLs its code CONSTRUCTS AT RUNTIME (via functions, template literals, string 3// concatenation, class methods) and hands to fetch()/XHR. These are exactly the URLs a STATIC byte/urlfold 4// scan MISSES -- e.g. `fetch(cdn(id)+'/master.m3u8')` where the path is computed, not a literal. 5// 6// SAFE BY CONSTRUCTION for "don't get blacklisted / don't hang on a hostile page": 7// * Fetches PEND -- the engine records the URL and returns a pending promise; it makes NO network call. 8// So running the page cannot hit a rate limit, cannot be fingerprinted by request pattern, cannot leak. 9// * We read the pending URLs and STOP -- we do NOT service them (no fetch storm, no recursive crawl here). 10// * ⚠ REMAINING SAFETY (documented, not yet wired): the interpreter has NO instruction/loop budget, so a 11// hostile `while(true){}` in page JS would hang. LIVE-CRAWLER wiring is BLOCKED on a fuel budget in the 12// eval loop (a coordinated shared-core change -- see coordination.tsv; do NOT wire live until it lands). 13// This module + its gate run only WELL-BEHAVED input, so they are safe today. 14import "nx_syscalls.nx" 15import "nx_js_eval.nx" 16const K_MAGIC_2048: i64 = 2048 17const K_MAGIC_4096: i64 = 4096 18 19// substring search: 1 if `needle` (len nl) occurs anywhere in hay[0..hl), else 0. 20func mjx_contains(hay: *u8, hl: i64, needle: *u8, nl: i64) -> i64 { 21 if nl == 0 { return 1 } 22 if nl > hl { return 0 } 23 var i: i64 = 0 24 let last: i64 = hl - nl 25 while i <= last { 26 var j: i64 = 0 27 var m: i64 = 1 28 while j < nl { if (hay[i + j] & 0xff) != (needle[j] & 0xff) { m = 0; j = nl } else { j = j + 1 } } 29 if m == 1 { return 1 } 30 i = i + 1 31 } 32 return 0 33} 34 35// does this URL look like a MEDIA resource? substring match on known extensions/manifest markers so a 36// trailing ?query / #frag doesn't defeat it. Covers HLS/DASH manifests, video/audio segments, images. 37func mjx_is_media_url(url: *u8, ul: i64) -> i64 { 38 if mjx_contains(url, ul, ".m3u8" as *u8, 5) == 1 { return 1 } // HLS manifest 39 if mjx_contains(url, ul, ".mpd" as *u8, 4) == 1 { return 1 } // DASH manifest 40 if mjx_contains(url, ul, ".mp4" as *u8, 4) == 1 { return 1 } 41 if mjx_contains(url, ul, ".m4s" as *u8, 4) == 1 { return 1 } // fMP4 segment 42 if mjx_contains(url, ul, ".ts" as *u8, 3) == 1 { return 1 } // MPEG-TS segment 43 if mjx_contains(url, ul, ".webm" as *u8, 5) == 1 { return 1 } 44 if mjx_contains(url, ul, ".mkv" as *u8, 4) == 1 { return 1 } 45 if mjx_contains(url, ul, ".mov" as *u8, 4) == 1 { return 1 } 46 if mjx_contains(url, ul, ".mp3" as *u8, 4) == 1 { return 1 } 47 if mjx_contains(url, ul, ".m4a" as *u8, 4) == 1 { return 1 } 48 if mjx_contains(url, ul, ".aac" as *u8, 4) == 1 { return 1 } 49 if mjx_contains(url, ul, ".jpg" as *u8, 4) == 1 { return 1 } 50 if mjx_contains(url, ul, ".jpeg" as *u8, 5) == 1 { return 1 } 51 if mjx_contains(url, ul, ".png" as *u8, 4) == 1 { return 1 } 52 if mjx_contains(url, ul, ".gif" as *u8, 4) == 1 { return 1 } 53 if mjx_contains(url, ul, ".webp" as *u8, 5) == 1 { return 1 } 54 if mjx_contains(url, ul, ".avif" as *u8, 5) == 1 { return 1 } 55 return 0 56} 57 58// Run page JS `src` and COLLECT the media URLs its code hands to fetch(). Writes them newline-separated 59// (each NUL-safe, '\n' delimited) into `outbuf` (cap bytes); returns the COUNT of media URLs found. 60// De-dups exact repeats. `outbox` (>=2 i64) receives [ctx, genv] so a caller can inspect further. 61func mjx_harvest_js(src: *u8, srclen: i64, outbuf: *u8, cap: i64, outbox: *i64) -> i64 { 62 outbuf[0] = 0 as u8 63 let rc: i64 = js_run_source_keep(src, srclen, outbox) 64 if rc != 0 { return 0 } // parse error -> genv is null; do NOT deref 65 let genv: *i64 = (outbox[1]) as *i64 66 if (genv as i64) == 0 { return 0 } 67 let ubuf: *u8 = sys_mmap(K_MAGIC_2048) 68 var found: i64 = 0 69 var wp: i64 = 0 70 // Enumerate pending entries by index: js_pending_url returns 0 once `i` is past the end (its own 71 // range check is the terminator). Hard-capped at 4096 so a bad count can never spin -- kept as 72 // defense-in-depth. (js_pending_total's inline-cast miscompile that first bit us here is now FIXED 73 // in nx_js_eval, two-step cast; this terminator loop doesn't depend on it either way.) 74 var i: i64 = 0 75 var go: i64 = 1 76 while go == 1 { 77 if i >= K_MAGIC_4096 { go = 0 } 78 else { 79 let ul: i64 = js_pending_url(genv, i, ubuf, K_MAGIC_2048) 80 if ul == 0 { go = 0 } 81 else { 82 if mjx_is_media_url(ubuf, ul) == 1 { 83 if mjx_contains(outbuf, wp, ubuf, ul) == 0 { // de-dup exact repeats 84 if (wp + ul + 1) < cap { 85 var k: i64 = 0 86 while k < ul { outbuf[wp] = ubuf[k]; wp = wp + 1; k = k + 1 } 87 outbuf[wp] = 10 as u8; wp = wp + 1 // '\n' 88 found = found + 1 89 } 90 } 91 } 92 i = i + 1 93 } 94 } 95 } 96 outbuf[wp] = 0 as u8 97 return found 98} 99 100// mjx_harvest_js + DOM: identical harvest, but seeds the page HTML as the DOM (js_run_source_keep_doc) so page 101// JS that reads document.getElementById/querySelector (e.g. a player plugin reading a data-* stream token off 102// its container element) resolves the real elements FIRST -- then the stream URL it constructs is captured. 103func mjx_harvest_js_dom(src: *u8, srclen: i64, dochtml: *u8, doclen: i64, outbuf: *u8, cap: i64, outbox: *i64) -> i64 { 104 outbuf[0] = 0 as u8 105 let rc: i64 = js_run_source_keep_doc(src, srclen, dochtml, doclen, outbox) 106 if rc != 0 { return 0 } 107 let genv: *i64 = (outbox[1]) as *i64 108 if (genv as i64) == 0 { return 0 } 109 let ubuf: *u8 = sys_mmap(K_MAGIC_2048) 110 var found: i64 = 0 111 var wp: i64 = 0 112 var i: i64 = 0 113 var go: i64 = 1 114 while go == 1 { 115 if i >= K_MAGIC_4096 { go = 0 } 116 else { 117 let ul: i64 = js_pending_url(genv, i, ubuf, K_MAGIC_2048) 118 if ul == 0 { go = 0 } 119 else { 120 if mjx_is_media_url(ubuf, ul) == 1 { 121 if mjx_contains(outbuf, wp, ubuf, ul) == 0 { 122 if (wp + ul + 1) < cap { 123 var k: i64 = 0 124 while k < ul { outbuf[wp] = ubuf[k]; wp = wp + 1; k = k + 1 } 125 outbuf[wp] = 10 as u8; wp = wp + 1 126 found = found + 1 127 } 128 } 129 } 130 i = i + 1 131 } 132 } 133 } 134 outbuf[wp] = 0 as u8 135 return found 136}