mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-23 06:58:05 +02:00
Journaux : masquer les identifiants Xtream des URL et exceptions
Le repli XMLTV journalisait l'URL `xmltv.php?username=…&password=…` du panneau en clair, et le texte des `ClientException` / `ProcessException` recopiait aussi l'URL : les identifiants de l'abonné finissaient dans `docker logs`. Passage par `LogRedactor.redactUrl` dans XmltvEpgService, EpgApi, le relais live, FFmpegSessionManager et RecordingScheduler. Test : capture des `print` via une Zone pour vérifier le masquage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
da67c479b3
commit
327873d254
6 files changed
+115
-8
No files matched your search
@@ -257,7 +257,12 @@ class EpgApi {
|
|||||||
.map((p) => p.toJson(channelId))
|
.map((p) => p.toJson(channelId))
|
||||||
.toList();
|
.toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('[EpgApi] source XMLTV indisponible pour $channelId : $e');
|
// L'exception peut recopier l'URL `player_api`/`xmltv.php`, identifiants
|
||||||
|
// compris.
|
||||||
|
print(
|
||||||
|
'[EpgApi] source XMLTV indisponible pour $channelId : '
|
||||||
|
'${LogRedactor.redactUrl('$e')}',
|
||||||
|
);
|
||||||
return const [];
|
return const [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -307,7 +307,11 @@ Stream<List<int>> _resilientLiveBody(
|
|||||||
yield chunk;
|
yield chunk;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('[Live Proxy] $streamId : coupure amont ($e)');
|
// Un `ClientException` recopie l'URL amont, identifiants compris.
|
||||||
|
print(
|
||||||
|
'[Live Proxy] $streamId : coupure amont '
|
||||||
|
'(${LogRedactor.redactUrl('$e')})',
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
upstream.client.close();
|
upstream.client.close();
|
||||||
}
|
}
|
||||||
@@ -494,7 +498,9 @@ Handler createLiveStreamHandler(
|
|||||||
} on ProcessException catch (e) {
|
} on ProcessException catch (e) {
|
||||||
// Serveur saturé (plus de processus ou de mémoire) : un 503 que le
|
// Serveur saturé (plus de processus ou de mémoire) : un 503 que le
|
||||||
// lecteur sait traiter, plutôt qu'une exception qui remonte en 500.
|
// lecteur sait traiter, plutôt qu'une exception qui remonte en 500.
|
||||||
print('[Live Turbo] $streamId : FFmpeg n\'a pas démarré ($e)');
|
// `ProcessException` liste les arguments, donc l'URL `-i` du panneau.
|
||||||
|
print('[Live Turbo] $streamId : FFmpeg n\'a pas démarré '
|
||||||
|
'(${LogRedactor.redactUrl('$e')})');
|
||||||
return Response(503, body: 'FFmpeg start failed');
|
return Response(503, body: 'FFmpeg start failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import 'dart:async';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import '../utils/log_redactor.dart';
|
||||||
|
|
||||||
/// One running FFmpeg transcoding session (live, VOD or recording playback).
|
/// One running FFmpeg transcoding session (live, VOD or recording playback).
|
||||||
class FfmpegSession {
|
class FfmpegSession {
|
||||||
final String id;
|
final String id;
|
||||||
@@ -121,7 +123,9 @@ class FfmpegSessionManager {
|
|||||||
process.stderr.transform(utf8.decoder).listen((data) {
|
process.stderr.transform(utf8.decoder).listen((data) {
|
||||||
session.recentStderr.add(data);
|
session.recentStderr.add(data);
|
||||||
if (session.recentStderr.length > 20) session.recentStderr.removeAt(0);
|
if (session.recentStderr.length > 20) session.recentStderr.removeAt(0);
|
||||||
print('[FFmpeg $id] $data');
|
// FFmpeg rappelle l'URL d'entrée (« Input #0 … from 'http://…' ») :
|
||||||
|
// masquer les identifiants Xtream avant de journaliser.
|
||||||
|
print('[FFmpeg $id] ${LogRedactor.redactUrl(data)}');
|
||||||
});
|
});
|
||||||
|
|
||||||
process.exitCode.then((code) {
|
process.exitCode.then((code) {
|
||||||
|
|||||||
@@ -533,7 +533,12 @@ class RecordingScheduler {
|
|||||||
await _launchFfmpeg(active);
|
await _launchFfmpeg(active);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
// Attraper TOUTES les exceptions pour éviter de crasher le serveur
|
// Attraper TOUTES les exceptions pour éviter de crasher le serveur
|
||||||
print('[RecordingScheduler] ERREUR dans _startRecording: $e\n$st');
|
// `ProcessException` liste les arguments FFmpeg, URL de la source
|
||||||
|
// comprise : masquer les identifiants avant de journaliser.
|
||||||
|
print(
|
||||||
|
'[RecordingScheduler] ERREUR dans _startRecording: '
|
||||||
|
'${LogRedactor.redactUrl('$e')}\n$st',
|
||||||
|
);
|
||||||
_db.updateRecordingStatus(
|
_db.updateRecordingStatus(
|
||||||
recording.id,
|
recording.id,
|
||||||
'failed',
|
'failed',
|
||||||
@@ -662,7 +667,10 @@ class RecordingScheduler {
|
|||||||
try {
|
try {
|
||||||
await _launchFfmpeg(active);
|
await _launchFfmpeg(active);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('[RecordingScheduler] Relance impossible: $e');
|
print(
|
||||||
|
'[RecordingScheduler] Relance impossible: '
|
||||||
|
'${LogRedactor.redactUrl('$e')}',
|
||||||
|
);
|
||||||
_active.remove(recording.id);
|
_active.remove(recording.id);
|
||||||
await _finalize(
|
await _finalize(
|
||||||
active,
|
active,
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import 'dart:typed_data';
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:xml/xml_events.dart';
|
import 'package:xml/xml_events.dart';
|
||||||
|
|
||||||
|
import '../utils/log_redactor.dart';
|
||||||
|
|
||||||
/// Guide TV construit à partir de dumps XMLTV publics.
|
/// Guide TV construit à partir de dumps XMLTV publics.
|
||||||
///
|
///
|
||||||
/// Beaucoup de panneaux Xtream servent un EPG figé depuis plusieurs jours, ou
|
/// Beaucoup de panneaux Xtream servent un EPG figé depuis plusieurs jours, ou
|
||||||
@@ -116,6 +118,11 @@ class XmltvEpgService {
|
|||||||
var ok = 0;
|
var ok = 0;
|
||||||
|
|
||||||
for (final url in sourceUrls) {
|
for (final url in sourceUrls) {
|
||||||
|
// La source peut être le `xmltv.php` du panneau, dont l'URL porte les
|
||||||
|
// identifiants de l'abonné en clair : ne jamais la journaliser telle
|
||||||
|
// quelle (elle finirait dans `docker logs`). Le texte de l'exception
|
||||||
|
// est masqué aussi, un `ClientException` recopiant l'URL demandée.
|
||||||
|
final safeUrl = LogRedactor.redactUrl(url);
|
||||||
try {
|
try {
|
||||||
final parsed = await _downloadAndParse(url);
|
final parsed = await _downloadAndParse(url);
|
||||||
// Première source servie gagne : les suivantes ne comblent que les
|
// Première source servie gagne : les suivantes ne comblent que les
|
||||||
@@ -125,10 +132,12 @@ class XmltvEpgService {
|
|||||||
}
|
}
|
||||||
ok++;
|
ok++;
|
||||||
print(
|
print(
|
||||||
'[XmltvEpg] $url : ${parsed.length} chaînes indexées',
|
'[XmltvEpg] $safeUrl : ${parsed.length} chaînes indexées',
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('[XmltvEpg] $url : échec ($e)');
|
print(
|
||||||
|
'[XmltvEpg] $safeUrl : échec (${LogRedactor.redactUrl('$e')})',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:http/testing.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import '../services/xmltv_epg_service.dart';
|
||||||
|
|
||||||
|
/// URL du dump `xmltv.php` d'un panneau Xtream : les identifiants de
|
||||||
|
/// l'abonné y figurent en clair, comme en production.
|
||||||
|
const _panelUrl =
|
||||||
|
'http://panel.example:8080/xmltv.php?username=john&password=hunter2';
|
||||||
|
|
||||||
|
/// Exécute [body] en capturant tout ce qui passe par `print`.
|
||||||
|
Future<List<String>> _capturePrints(Future<void> Function() body) async {
|
||||||
|
final lines = <String>[];
|
||||||
|
await runZoned(
|
||||||
|
body,
|
||||||
|
zoneSpecification: ZoneSpecification(
|
||||||
|
print: (self, parent, zone, line) => lines.add(line),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('XmltvEpgService — journalisation', () {
|
||||||
|
test('masque les identifiants de l\'URL quand la source répond', () async {
|
||||||
|
final service = XmltvEpgService(
|
||||||
|
sourceUrls: const [_panelUrl],
|
||||||
|
client: MockClient(
|
||||||
|
(_) async => http.Response('<?xml version="1.0"?><tv></tv>', 200),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final lines = await _capturePrints(service.ensureFresh);
|
||||||
|
final xmltvLines = lines.where((l) => l.startsWith('[XmltvEpg]'));
|
||||||
|
|
||||||
|
expect(xmltvLines, isNotEmpty);
|
||||||
|
for (final line in xmltvLines) {
|
||||||
|
expect(line, isNot(contains('john')));
|
||||||
|
expect(line, isNot(contains('hunter2')));
|
||||||
|
}
|
||||||
|
expect(
|
||||||
|
lines,
|
||||||
|
contains(contains('username=***&password=***')),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('masque les identifiants de l\'URL et de l\'exception en cas d\'échec',
|
||||||
|
() async {
|
||||||
|
// `ClientException` recopie l'URL demandée dans son message : c'est
|
||||||
|
// par là que les identifiants fuyaient aussi.
|
||||||
|
final service = XmltvEpgService(
|
||||||
|
sourceUrls: const [_panelUrl],
|
||||||
|
client: MockClient(
|
||||||
|
(request) async => throw http.ClientException(
|
||||||
|
'Connection refused',
|
||||||
|
request.url,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final lines = await _capturePrints(service.ensureFresh);
|
||||||
|
final failure = lines.firstWhere(
|
||||||
|
(l) => l.contains('échec'),
|
||||||
|
orElse: () => fail('aucune ligne d\'échec journalisée : $lines'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(failure, isNot(contains('john')));
|
||||||
|
expect(failure, isNot(contains('hunter2')));
|
||||||
|
expect(failure, contains('username=***&password=***'));
|
||||||
|
expect(failure, contains('Connection refused'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in new issue
Block a user