mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-07 19:21:32 -07:00
fix(anime): harden the extension bridge against untrusted repos and hangs
Addresses CodeRabbit review feedback on the Anime Browser: - reject repository package/apk names that are not plain identifiers, and verify the install target resolves inside the extensions directory - count mpv's %n% option escape in UTF-8 bytes, and escape backslashes in header values so a trailing one cannot eat the list separator - key the bridge extension-id cache by APK content, so an in-place upgrade re-uploads instead of running the previous build - bound every bridge, release-listing, and download request with a timeout - enforce the APK size limit while streaming rather than after buffering - read APK bytes on demand instead of holding a base64 copy per extension for the lifetime of the browser - serialize preference mutations and write the file atomically - handle the sidecar spawn error event, and wait for the child to exit in stop() before returning - report a failed Anime Browser bootstrap instead of showing the starting banner forever - keep the preferences panel's save confirmation and in-flight multi-select edits by re-rendering only on a structural schema change
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import type { spawn as spawnType, ChildProcess } from 'node:child_process';
|
||||
import { allocatePort, startSidecar } from './sidecar-process';
|
||||
import type { BundleBinaries } from './sidecar-bundle';
|
||||
|
||||
const binaries: BundleBinaries = {
|
||||
javaPath: '/nonexistent/java',
|
||||
jarPath: '/tmp/MExtensionServer.jar',
|
||||
};
|
||||
|
||||
/** A ChildProcess stand-in: an EventEmitter with the bits startSidecar touches. */
|
||||
function fakeChild(): ChildProcess {
|
||||
const child = new EventEmitter();
|
||||
Object.assign(child, { stdout: null, stderr: null, kill: () => true });
|
||||
return child as unknown as ChildProcess;
|
||||
}
|
||||
|
||||
test('a failed spawn rejects instead of throwing an unhandled error event', async () => {
|
||||
const port = await allocatePort();
|
||||
const child = fakeChild();
|
||||
const spawnImpl = (() => {
|
||||
// Node emits `error` asynchronously when the binary cannot be executed.
|
||||
queueMicrotask(() => child.emit('error', new Error('spawn ENOENT')));
|
||||
return child;
|
||||
}) as unknown as typeof spawnType;
|
||||
|
||||
await assert.rejects(
|
||||
() => startSidecar({ binaries, port, readyTimeoutMs: 2000, spawnImpl }),
|
||||
/could not start.*ENOENT/,
|
||||
);
|
||||
});
|
||||
|
||||
test('an early exit is reported with its code rather than waiting out the deadline', async () => {
|
||||
const port = await allocatePort();
|
||||
const child = fakeChild();
|
||||
const spawnImpl = (() => {
|
||||
queueMicrotask(() => child.emit('exit', 1, null));
|
||||
return child;
|
||||
}) as unknown as typeof spawnType;
|
||||
|
||||
await assert.rejects(
|
||||
() => startSidecar({ binaries, port, readyTimeoutMs: 2000, spawnImpl }),
|
||||
/exited before becoming ready \(code 1/,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user