mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 12:36:15 -08:00
fix(stalker): send cmd in the reference MAG wire format
A real MAG sends cmd unencoded and the portal decodes its query exactly
once, so a cmd that already contains percent sequences (%3A tokens,
pre-encoded path segments) must pass through untouched. The previous
encodeURIComponent transport (2c032cd3c, 0.22) double-encoded such cmds
(%3A -> %253A): strict portals and reseller panels that compare cmd
literally, and stock create_link handlers matching the decoded value,
saw a different string than a real STB sends.
The new shared encodeStalkerCmdValue() reproduces the reference wire
bytes: % passes through verbatim, characters the WHATWG URL serializer
keeps raw in a query stay raw (so the bytes survive the axios/new URL
transport unchanged), and everything else is percent-encoded. That
preserves the 0.22 injection protection - &, # (and ; for PHP setups
with a ; argument separator) inside cmd cannot append or truncate query
parameters; they decode back to the original byte server-side.
Both transports now share the format: the Electron query builder is
extracted to buildStalkerRequestUrl() and the web-backend /stalker
proxy appends cmd to the portal URL itself instead of letting axios
turn slashes into %2F (the opposite divergence).
Also unifies the two divergent response-side cmd normalizers: the
cross-portal collection resolver now uses the Stalker store's
normalizeStalkerPlaybackCommand/resolveStalkerPlaybackUrl, so playing
from Favorites/global collections resolves relative (/media/...) and
query-only (?token=...) create_link replies against the portal base
instead of handing the player a bare relative path.
The mock portal's create_link response gains mock-only cmd_received/
query_keys_received diagnostics; a new Electron e2e pins the contract
end-to-end (single decode, injection blocked). Unit corpus tests cover
the encoder, the Electron builder, the web-backend proxy, and the
resolver.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
04b2f9b82e
commit
3326f73388
15 files changed
+600
-59
No files matched your search
@@ -4,6 +4,7 @@ import {
|
||||
addStalkerPortal,
|
||||
addXtreamPortal,
|
||||
closeElectronApp,
|
||||
defaultStalkerMacAddress,
|
||||
defaultStalkerPortalName,
|
||||
defaultXtreamPortalName,
|
||||
expect,
|
||||
@@ -67,6 +68,61 @@ test.describe('Electron Provider Smoke Tests', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('@stalker @electron delivers cmd to the portal decoded exactly once with query injection blocked', async ({
|
||||
dataDir,
|
||||
request,
|
||||
}) => {
|
||||
await resetMockServers(request, ['stalker']);
|
||||
|
||||
const app = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
// Stored cmd with a pre-encoded token (%3A), a literal '+', and a
|
||||
// query-injection attempt (&injected=1#frag).
|
||||
const storedCmd =
|
||||
'ffrt3 http://example.com/ch/123?token=a%3Ab+c&injected=1#frag';
|
||||
|
||||
const response = await app.mainWindow.evaluate(
|
||||
async ({ url, macAddress, cmd }) =>
|
||||
window.electron.stalkerRequest({
|
||||
url,
|
||||
macAddress,
|
||||
params: { action: 'create_link', type: 'itv', cmd },
|
||||
}),
|
||||
{
|
||||
url: `${stalkerMockServer}/portal.php`,
|
||||
macAddress: defaultStalkerMacAddress,
|
||||
cmd: storedCmd,
|
||||
}
|
||||
);
|
||||
|
||||
const js = (
|
||||
response as {
|
||||
js: { cmd_received: string; query_keys_received: string[] };
|
||||
}
|
||||
).js;
|
||||
|
||||
// The portal must see the stored cmd decoded exactly once —
|
||||
// %3A → ':', '+' → space — the same view it gets from a real STB.
|
||||
// The old encodeURIComponent transport double-encoded '%' and
|
||||
// delivered the %3A/+ sequences still encoded.
|
||||
expect(js.cmd_received).toBe(
|
||||
'ffrt3 http://example.com/ch/123?token=a:b c&injected=1#frag'
|
||||
);
|
||||
|
||||
// The '&'/'#' inside cmd stayed inside the cmd value instead of
|
||||
// restructuring the portal query.
|
||||
expect(js.query_keys_received).toEqual([
|
||||
'JsHttpRequest',
|
||||
'action',
|
||||
'cmd',
|
||||
'type',
|
||||
]);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
|
||||
test('@xtream @electron shows refresh overlay immediately from the dashboard Xtream source menu', async ({
|
||||
dataDir,
|
||||
request,
|
||||
|
||||
Reference in new issue
Block a user