mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 19:46:16 -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>
* fix(pwa): sanitize portal URL before appending stalker cmd
A registered portal URL carrying a fragment would swallow the appended
cmd (everything after # is never transmitted), and a trailing bare '?'
produced '??cmd='. Drop the hash and pick the separator from the
sanitized href before appending. Flagged by Greptile/Codex on #1334.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(pwa): make stalker cmd append visibly query-only for CodeQL
Rebuild the /stalker request URL through the URL object and concatenate
the encoded cmd strictly behind a literal '?', so static analysis can
see the tainted value never reaches host or path (js/request-forgery
alert on the previous separator ternary). Behavior unchanged; the
fragment/bare-'?' regression tests still pin the wire format.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
56 lines
2.3 KiB
TypeScript
56 lines
2.3 KiB
TypeScript
/**
|
|
* Wire encoding for the Stalker `cmd` query parameter.
|
|
*
|
|
* A real MAG/STB sends `cmd` unencoded: the portal's own client JS
|
|
* concatenates raw `key=value` pairs, WebKit's URL layer escapes only the
|
|
* characters a URL cannot carry (space, quotes, non-ASCII), and PHP's `$_GET`
|
|
* then applies exactly one form-urldecode. The portal therefore sees the
|
|
* stored cmd decoded **once** — pre-encoded sequences such as `%3A` arrive as
|
|
* `:`, and a literal `+` arrives as a space.
|
|
*
|
|
* `encodeURIComponent` (the previous behavior) broke that contract for any
|
|
* cmd already containing `%`: `%3A` went out as `%253A` and reached the
|
|
* portal still encoded, so strict panels and the stock
|
|
* `preg_match`-based `create_link` handlers saw a different string than a
|
|
* real STB would send.
|
|
*
|
|
* This encoder reproduces the reference wire bytes instead:
|
|
*
|
|
* - `%` passes through untouched (never double-encoded);
|
|
* - characters the WHATWG URL serializer keeps raw in a query stay raw
|
|
* (`/ : ? = + , @ $ [ ] ! * ( ) ~ - _ .`), so the bytes we emit survive
|
|
* `new URL(...)` unchanged;
|
|
* - everything else is percent-encoded. That covers the characters that
|
|
* would restructure our request (`&`, `#`, and `;` for PHP setups with a
|
|
* `;` argument separator) — a malicious portal cannot smuggle extra query
|
|
* parameters through cmd — plus characters a URL cannot carry raw (space,
|
|
* quotes, control characters, non-ASCII). All of them decode back to the
|
|
* original byte on the server, so the portal-visible value is unaffected.
|
|
*/
|
|
|
|
const SAFE_CMD_CHAR = /^[A-Za-z0-9\-_.~!*()/:?@$,+=[\]%]$/;
|
|
|
|
const utf8Encoder = new TextEncoder();
|
|
|
|
/**
|
|
* Percent-encode every UTF-8 byte of a character. Deliberately not
|
|
* `encodeURIComponent`, whose unreserved set (e.g. `'`) overlaps characters
|
|
* this encoder must escape because `new URL(...)` would re-encode them and
|
|
* change the wire bytes behind our back.
|
|
*/
|
|
function percentEncodeChar(char: string): string {
|
|
let encoded = '';
|
|
for (const byte of utf8Encoder.encode(char)) {
|
|
encoded += `%${byte.toString(16).toUpperCase().padStart(2, '0')}`;
|
|
}
|
|
return encoded;
|
|
}
|
|
|
|
export function encodeStalkerCmdValue(value: string): string {
|
|
let encoded = '';
|
|
for (const char of value) {
|
|
encoded += SAFE_CMD_CHAR.test(char) ? char : percentEncodeChar(char);
|
|
}
|
|
return encoded;
|
|
}
|