Straw audit 2026-07-04. Three availability defects in the extraction core:
- runtime.rs: cap the embedded QuickJS runtime (64 MiB mem / 2 MiB stack / 5s
wall-clock interrupt) so a hostile or looping player.js can't hang the
extraction thread or OOM the process. Previously Runtime::new() set no limits.
- player_manager.rs: replace the never-cleared sticky-error flags with
invalidate-on-failure. Any deobf/extract error now drops the cached player.js
so the next call re-fetches fresh and retries, instead of one routine player.js
rotation permanently wedging ALL extraction until app restart (clear_all_caches
existed but was never called).
- stream_extractor.rs: per-format graceful degradation. nsig-deobf failure falls
back to the throttled original URL (YouTube still serves it, rate-limited);
sig-cipher failure skips just that format. One bad format no longer aborts the
whole video, and manifests/captions still populate. NPE parity.
- Cargo.toml: release panic=unwind (inert as a straw dep; kept consistent for
standalone builds/tests).
120 tests pass, clippy clean.
Caught during the cargo-ndk cross-compile — strawcore-core was
emitting its own libstrawcore_core.so (~306 KB per ABI) into Straw's
jniLibs. That .so is never loaded by Android; the wrapper crate's
libstrawcore.so is the only entry point.
rlib only is what consumer crates need.
Straw's wrapper crate already owns the name 'strawcore' (and that name
is baked into the Android .so file + Kotlin's System.loadLibrary call).
Renaming this extractor crate to 'strawcore-core' resolves the cargo
package-name collision so both can live in the same workspace dep tree.
The repository keeps the name strawcore.
Port NewPipeExtractor's JS pipeline: player.js fetch + cache, sig and
nsig function extraction, deobfuscation, sticky-error caching.
src/youtube/js/
* runtime.rs — rquickjs wrapper (mirrors utils/JavaScript.java)
compile_or_throw + run(snippet, name, parameter)
* lexer.rs — match_to_closing_brace via the `ress` JS scanner
(NPE's lexer is derived from the same crate
upstream)
* extractor.rs — iframe_api → embed page fallback for player.js
URL, regex-driven hash extraction, clean-and-fetch
* signature.rs — 6 sig fn name regexes (front-most-recent),
deobf-function-body via lexer w/ regex fallback,
helper-object + global-string-array extraction,
signatureTimestamp, snippet assembler
* nsig.rs — 8 nsig fn name regexes (incl. array-indirection),
body via lexer w/ regex fallback, fixupFunction
early-return strip
* player_manager.rs — orchestrator + sticky-error cache mirroring
YoutubeJavaScriptPlayerManager
PORT DEVIATIONS from NPE (each flagged in code):
* dropped the 6th sig fn name regex (used Java backref \2; Rust's
`regex` crate is backtracking-free, so we substitute a loose form
that NPE itself half-broke per audit Track B §2.1)
* dropped the Java atomic group `(?>...)` from helper-object regex —
Rust's NFA is already linear-time
* nsig fixup substitutes `(?:"undefined"|'undefined')` for the
\1 backref; harmless loosening
* sig and nsig assembled snippets prepend `var` — QuickJS rejects
bare-assignment to undeclared identifiers; NPE relied on Rhino's
non-strict mode
Tests:
* 43 lib unit tests (up from 7 in Phase 1)
* 7 Phase 2 offline integration tests against a hand-crafted
minified synthetic player.js — exercises the full sig pipeline
(build_deobfuscator → runtime::run) and nsig fixup_function
* 7 Phase 1 live smoke tests still green
57/57 total green.