fix(sources): match a MAC from the URL in any letter case

Only the source's own `macAddress` was compared caselessly, so an M3U
URL carrying `?mac=…` (nested ones too) left the MAC visible in a name
written in another case. Any MAC-shaped secret, and every form of the
source's own MAC, is now compared lower-cased; passwords keep their case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-14 03:04:05 +02:00
1 parent c6d7b241d3
commit d24e89a5e1
2 files changed
+27 -11

No files matched your search

@@ -162,6 +162,20 @@ describe('sourceDisplayName', () => {
);
});
it('finds a MAC from the URL whatever its letter case', () => {
const mac = '00:1A:79:00:00:01';
for (const url of [
`https://host.test/list.m3u?mac=${mac}`,
`https://proxy.test/list?url=${encodeURIComponent(
`http://origin.test/c/?mac=${mac}`
)}`,
]) {
expect(
sourceDisplayName({ title: 'Box 00:1a:79:00:00:01', url })
).toBe(new URL(url).host);
}
});
it('keeps a name holding the password word in another case', () => {
expect(
sourceDisplayName({
@@ -33,6 +33,8 @@ const SHOWN_ACCOUNT_KEYS = new Set(['username', 'login', 'user', 'usr']);
*/
const MIN_SECRET_LENGTH = 3;
const ABSOLUTE_URL = /^[a-z][a-z0-9+.-]*:\/\//i;
/** Six hex pairs, as a STB MAC is written, wherever the secret came from. */
const MAC_ADDRESS = /^[0-9a-f]{2}(?:[:-][0-9a-f]{2}){5}$/i;
function parseUrl(value: string | undefined): URL | null {
if (!value) {
@@ -215,24 +217,24 @@ function shows(texts: readonly string[], secret: string): boolean {
* Whether the text shows any of the source's secrets, read as written and
* fully decoded (so `%3a` escapes count as `:`). A password is compared in
* its own case: `Stress` in a name is not the password `stress`. A MAC is
* the same address in either letter case, so it is compared lower-cased.
* the same address in either letter case, so it is compared lower-cased:
* the source's own, and any MAC-shaped secret its URL carries (`?mac=…`,
* nested ones included).
*/
export function containsSourceSecret(
text: string,
playlist: SourceDisplayFields
): boolean {
const texts = [text, decodeFully(text)].map((form) => form.trim());
if (sourceSecrets(playlist).some((secret) => shows(texts, secret))) {
return true;
}
const mac = playlist.macAddress?.toLowerCase();
const lowerTexts = texts.map((form) => form.toLowerCase());
return (
!!mac &&
[mac, encodeURIComponent(mac).toLowerCase()].some((form) =>
shows(lowerTexts, form)
)
);
const ownMac = playlist.macAddress?.toLowerCase();
return sourceSecrets(playlist).some((secret) => {
const plain = decodeFully(secret).toLowerCase();
const isMac = plain === ownMac || MAC_ADDRESS.test(plain);
return isMac
? shows(lowerTexts, secret.toLowerCase())
: shows(texts, secret);
});
}
/**