From 9a8dac8c835bb5d78f06ca91cc5db19513d0e221 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Thu, 30 Apr 2026 13:28:24 +0800 Subject: [PATCH] fix: normalize bridge metadata authorization --- lib/features/settings/settings_page_core.dart | 22 +++++++++++-- .../settings_about_bridge_metadata_test.dart | 33 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/lib/features/settings/settings_page_core.dart b/lib/features/settings/settings_page_core.dart index 57fa0079..347b3cb7 100644 --- a/lib/features/settings/settings_page_core.dart +++ b/lib/features/settings/settings_page_core.dart @@ -27,7 +27,10 @@ Future> loadBridgeMetadataForSettingsAbout({ fragment: null, ); final authorizationHeader = await authorizationResolver(pingEndpoint); - if (authorizationHeader == null || authorizationHeader.trim().isEmpty) { + final normalizedAuthorizationHeader = _normalizeAuthorizationHeader( + authorizationHeader ?? '', + ); + if (normalizedAuthorizationHeader.isEmpty) { return const { 'status': 'unavailable', 'version': '', @@ -45,7 +48,7 @@ Future> loadBridgeMetadataForSettingsAbout({ .timeout(const Duration(seconds: 4)); request.headers.set( HttpHeaders.authorizationHeader, - 'Bearer $authorizationHeader', + normalizedAuthorizationHeader, ); request.headers.set(HttpHeaders.acceptHeader, 'application/json'); final response = await request.close().timeout(const Duration(seconds: 4)); @@ -88,6 +91,21 @@ Future> loadBridgeMetadataForSettingsAbout({ }; } +String _normalizeAuthorizationHeader(String raw) { + final trimmed = raw.trim(); + if (trimmed.isEmpty) { + return ''; + } + final separatorIndex = trimmed.indexOf(RegExp(r'\s')); + if (separatorIndex > 0 && separatorIndex < trimmed.length - 1) { + final scheme = trimmed.substring(0, separatorIndex); + if (RegExp(r"^[A-Za-z][A-Za-z0-9!#$%&'*+.^_`|~-]*$").hasMatch(scheme)) { + return trimmed; + } + } + return 'Bearer $trimmed'; +} + class SettingsPage extends StatefulWidget { const SettingsPage({ super.key, diff --git a/test/features/settings/settings_about_bridge_metadata_test.dart b/test/features/settings/settings_about_bridge_metadata_test.dart index c1a808e8..0e0e1f6e 100644 --- a/test/features/settings/settings_about_bridge_metadata_test.dart +++ b/test/features/settings/settings_about_bridge_metadata_test.dart @@ -50,6 +50,39 @@ void main() { expect(metadata['buildDate'], '2026-04-21'); }); + test('preserves prebuilt bearer authorization', () async { + final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); + addTearDown(() async { + await server.close(force: true); + }); + + var authorizationHeader = ''; + server.listen((request) async { + authorizationHeader = + request.headers.value(HttpHeaders.authorizationHeader) ?? ''; + request.response + ..statusCode = HttpStatus.ok + ..headers.contentType = ContentType.json + ..write( + jsonEncode({ + 'status': 'ok', + 'version': '991ecb0', + }), + ); + await request.response.close(); + }); + + final metadata = await loadBridgeMetadataForSettingsAbout( + bridgeEndpoint: Uri.parse( + 'http://${server.address.address}:${server.port}', + ), + authorizationResolver: (_) async => 'Bearer bridge-token', + ); + + expect(authorizationHeader, 'Bearer bridge-token'); + expect(metadata['status'], 'ok'); + }); + test('returns unavailable when bridge authorization is missing', () async { final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); addTearDown(() async {