mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 13:16:16 -08:00
fix(sources): search URLs nested in path segments for name secrets
A proxy URL can carry a whole source URL, or a token pair, in one path segment. The display stripper already cleans such segments; the name check now collects their secrets too, so a name holding them collapses to the host. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8323314853
commit
ea5bca7e96
2 files changed
+44
-23
No files matched your search
@@ -102,6 +102,22 @@ describe('sourceDisplayName', () => {
|
||||
expect(sourceDisplayName({ title: 'Mine hunter2', url })).toBe(
|
||||
'host.tv'
|
||||
);
|
||||
expect(
|
||||
sourceDisplayName({
|
||||
title: 'My hunter2',
|
||||
url: 'https://host.test/live/joe/hunter2/list.m3u',
|
||||
})
|
||||
).toBe('host.test');
|
||||
});
|
||||
|
||||
it('drops a name holding a secret of a URL nested in a path segment', () => {
|
||||
const url = `https://proxy.test/fetch/${encodeURIComponent(
|
||||
'http://origin.test/live/joe/hunter2/1.ts'
|
||||
)}/token=tk98765`;
|
||||
for (const title of ['Mine hunter2', 'Mine joe', 'Mine tk98765']) {
|
||||
expect(sourceDisplayName({ title, url })).toBe('proxy.test');
|
||||
}
|
||||
expect(sourceDisplayName({ title: 'Mine', url })).toBe('Mine');
|
||||
});
|
||||
|
||||
it('returns null for a local file without a name', () => {
|
||||
|
||||
@@ -76,10 +76,11 @@ export function sourceHost(
|
||||
const MAX_SECRET_NESTING = 4;
|
||||
|
||||
/**
|
||||
* The secrets a URL carries: its userinfo password, every sensitive query or
|
||||
* fragment parameter (the source's own account name excepted), and the same
|
||||
* again in any URL or parameter list nested in another parameter, whose
|
||||
* account names count as secrets too.
|
||||
* The secrets a URL carries: its userinfo password, the password of an
|
||||
* Xtream stream path, every sensitive query or fragment parameter (the
|
||||
* source's own account name excepted), and the same again in any URL or
|
||||
* parameter list nested in a parameter or path segment, whose account names
|
||||
* count as secrets too.
|
||||
*/
|
||||
function collectUrlSecrets(
|
||||
url: URL,
|
||||
@@ -93,18 +94,14 @@ function collectUrlSecrets(
|
||||
if (depth > 0) {
|
||||
secrets.push(safeDecode(url.username));
|
||||
}
|
||||
// An Xtream stream path carries the account: `/live/<user>/<password>/`.
|
||||
const segments = url.pathname.split('/').map(safeDecode);
|
||||
for (let index = 0; index + 2 < segments.length; index += 1) {
|
||||
if (
|
||||
XTREAM_CREDENTIAL_PATH_SEGMENTS.has(segments[index].toLowerCase())
|
||||
) {
|
||||
secrets.push(segments[index + 2]);
|
||||
if (depth > 0) {
|
||||
secrets.push(segments[index + 1]);
|
||||
}
|
||||
const visitNested = (value: string): void => {
|
||||
const nested = ABSOLUTE_URL.test(value.trim()) ? parseUrl(value) : null;
|
||||
if (nested) {
|
||||
collectUrlSecrets(nested, secrets, depth + 1);
|
||||
} else if (value.includes('=')) {
|
||||
visit(new URLSearchParams(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
const visit = (params: URLSearchParams): void =>
|
||||
params.forEach((value, key) => {
|
||||
const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||
@@ -114,15 +111,23 @@ function collectUrlSecrets(
|
||||
}
|
||||
return;
|
||||
}
|
||||
const nested = ABSOLUTE_URL.test(value.trim())
|
||||
? parseUrl(value)
|
||||
: null;
|
||||
if (nested) {
|
||||
collectUrlSecrets(nested, secrets, depth + 1);
|
||||
} else if (value.includes('=')) {
|
||||
visit(new URLSearchParams(value));
|
||||
}
|
||||
visitNested(value);
|
||||
});
|
||||
// An Xtream stream path carries the account: `/live/<user>/<password>/`.
|
||||
// A proxy may carry a whole URL in one segment.
|
||||
const segments = url.pathname.split('/').map(safeDecode);
|
||||
segments.forEach((segment, index) => {
|
||||
if (
|
||||
XTREAM_CREDENTIAL_PATH_SEGMENTS.has(segment.toLowerCase()) &&
|
||||
index + 2 < segments.length
|
||||
) {
|
||||
secrets.push(segments[index + 2]);
|
||||
if (depth > 0) {
|
||||
secrets.push(segments[index + 1]);
|
||||
}
|
||||
}
|
||||
visitNested(segment);
|
||||
});
|
||||
visit(url.searchParams);
|
||||
const fragment = url.hash.slice(1);
|
||||
if (fragment.includes('=')) {
|
||||
|
||||
Reference in new issue
Block a user