mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 03:56:15 -08:00
fix(playlist): mask query passwords on M3U cards, drop stale auto-detect prefills, doc the assembled-host exception
Addresses Copilot's suppressed review comments: - The m3u-url candidate card no longer shows a get.php password in clear — the display masks the query value while the untouched URL still prefills the form. - A picked candidate is dropped if the user switches to another method before its target form mounts, so it cannot lie in wait and prefill a later visit. - The parser contract now names its one assembled-value exception (labeledHostUrl completing a scheme-less host with http:// and a labeled port). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
This commit is contained in:
5 files changed
+57
-2
No files matched your search
+22
@@ -283,6 +283,28 @@ describe('AddPlaylistDialogComponent', () => {
|
||||
expect(patchValue).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('drops the candidate when the user switches to another method first', () => {
|
||||
const patchValue = jest.fn();
|
||||
(component as { xtreamImport: jest.Mock }).xtreamImport = jest.fn(
|
||||
() => ({ form: { patchValue } })
|
||||
);
|
||||
|
||||
component.onCandidateSelected({
|
||||
kind: 'xtream',
|
||||
confidence: 'high',
|
||||
username: 'alice',
|
||||
});
|
||||
// The user clicks another tile before the xtream form mounted.
|
||||
component.method.set('file');
|
||||
applyPrefill();
|
||||
|
||||
// Coming back to the xtream form later must not prefill it.
|
||||
component.method.set('xtream');
|
||||
applyPrefill();
|
||||
|
||||
expect(patchValue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps the candidate pending while the target form does not exist yet', () => {
|
||||
(component as { xtreamImport: jest.Mock }).xtreamImport = jest.fn(
|
||||
() => undefined
|
||||
|
||||
+8
@@ -181,6 +181,14 @@ export class AddPlaylistDialogComponent {
|
||||
if (!candidate) {
|
||||
return;
|
||||
}
|
||||
// The user can click another method tile before the target form
|
||||
// mounts; a candidate must not lie in wait and prefill a later visit
|
||||
// to its form. Only the method the selection itself switched to may
|
||||
// consume it.
|
||||
if (this.method() !== METHOD_BY_CANDIDATE_KIND[candidate.kind]) {
|
||||
this.pendingPrefill.set(null);
|
||||
return;
|
||||
}
|
||||
switch (candidate.kind) {
|
||||
case 'm3u-url': {
|
||||
const child = this.urlUpload();
|
||||
|
||||
@@ -80,6 +80,20 @@ describe('AutoImportComponent', () => {
|
||||
expect(emitted).toEqual([candidate]);
|
||||
});
|
||||
|
||||
it('masks the query password of an M3U link on the card', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const rows = component.summaryRows({
|
||||
kind: 'm3u-url',
|
||||
confidence: 'low',
|
||||
url: 'http://tv.example.com:8080/get.php?username=alice&password=s3cret&type=m3u_plus',
|
||||
});
|
||||
|
||||
expect(rows[0].value).toContain('username=alice');
|
||||
expect(rows[0].value).toContain('password=••••••');
|
||||
expect(JSON.stringify(rows)).not.toContain('s3cret');
|
||||
});
|
||||
|
||||
it('masks the password in the summary rows', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
|
||||
@@ -130,7 +130,16 @@ export class AutoImportComponent implements OnInit {
|
||||
};
|
||||
switch (candidate.kind) {
|
||||
case 'm3u-url':
|
||||
push('HOME.URL_UPLOAD.PLAYLIST_URL', candidate.url);
|
||||
// get.php links carry the password in their query — the card
|
||||
// must not show it while every other password here is masked.
|
||||
// The candidate keeps the untouched URL for prefilling.
|
||||
push(
|
||||
'HOME.URL_UPLOAD.PLAYLIST_URL',
|
||||
candidate.url?.replace(
|
||||
/([?&]password=)[^&#\s]*/i,
|
||||
'$1••••••'
|
||||
)
|
||||
);
|
||||
break;
|
||||
case 'xtream':
|
||||
push(
|
||||
|
||||
@@ -26,7 +26,9 @@ import {
|
||||
* Unicode math alphabets (`𝚄𝚂𝙴𝚁➤`, `𝙿𝙰𝚂𝚂➤`) to slip past chat spam
|
||||
* filters; the folding turns them back into the ASCII they denote, and
|
||||
* real credentials/URLs are ASCII already, so values pass through it
|
||||
* unchanged.
|
||||
* unchanged. The one deliberate exception is `labeledHostUrl`, which
|
||||
* completes a scheme-less labeled host with `http://` and a separately
|
||||
* labeled port — an assembled value, landing in an editable form field.
|
||||
* 2. **Detection proposes, it never decides.** The output prefills the
|
||||
* existing import forms; classification authority stays with the
|
||||
* behavioral probes those forms already run (portal discovery, Xtream
|
||||
|
||||
Reference in new issue
Block a user