refactor uri scheme handling
This commit is contained in:
@@ -33,6 +33,7 @@ import 'package:socks5_proxy/socks_client.dart';
|
|||||||
import 'package:weblibre/core/http_error_handler.dart';
|
import 'package:weblibre/core/http_error_handler.dart';
|
||||||
import 'package:weblibre/data/models/web_page_info.dart';
|
import 'package:weblibre/data/models/web_page_info.dart';
|
||||||
import 'package:weblibre/extensions/http_encoding.dart';
|
import 'package:weblibre/extensions/http_encoding.dart';
|
||||||
|
import 'package:weblibre/extensions/uri.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/entities/browser_icon.dart';
|
import 'package:weblibre/features/geckoview/domain/entities/browser_icon.dart';
|
||||||
import 'package:weblibre/features/user/domain/repositories/cache.dart';
|
import 'package:weblibre/features/user/domain/repositories/cache.dart';
|
||||||
import 'package:weblibre/features/web_feed/utils/feed_finder.dart';
|
import 'package:weblibre/features/web_feed/utils/feed_finder.dart';
|
||||||
@@ -129,9 +130,7 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
|||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _isHttpUrl(Uri url) {
|
static bool _isHttpUrl(Uri url) => url.isHttpOrHttps;
|
||||||
return url.scheme.startsWith('https') || url.scheme.startsWith('http');
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<Resource> _extractIcons(Uri baseUrl, Document document) {
|
static List<Resource> _extractIcons(Uri baseUrl, Document document) {
|
||||||
final List<Resource> icons = [];
|
final List<Resource> icons = [];
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ final class GenericWebsiteServiceProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$genericWebsiteServiceHash() =>
|
String _$genericWebsiteServiceHash() =>
|
||||||
r'b320635f651574a500ee6764688ef26b6d39ef8f';
|
r'a2bd892f0ca07467eaa906648e57f232a7e50155';
|
||||||
|
|
||||||
abstract class _$GenericWebsiteService extends $Notifier<void> {
|
abstract class _$GenericWebsiteService extends $Notifier<void> {
|
||||||
void build();
|
void build();
|
||||||
|
|||||||
@@ -24,4 +24,8 @@ extension UriX on Uri {
|
|||||||
Uri get base => Uri.parse('$scheme://$authority');
|
Uri get base => Uri.parse('$scheme://$authority');
|
||||||
|
|
||||||
bool get hasSupportedScheme => _supportedSchemes.contains(scheme);
|
bool get hasSupportedScheme => _supportedSchemes.contains(scheme);
|
||||||
|
|
||||||
|
bool get isHttp => isScheme('http');
|
||||||
|
bool get isHttps => isScheme('https');
|
||||||
|
bool get isHttpOrHttps => isHttp || isHttps;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-4
@@ -19,14 +19,13 @@
|
|||||||
*/
|
*/
|
||||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:weblibre/extensions/uri.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/domain/repositories/site_permissions.dart';
|
import 'package:weblibre/features/geckoview/features/browser/domain/repositories/site_permissions.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/sheets/tracking_protection_provider.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/sheets/tracking_protection_provider.dart';
|
||||||
|
|
||||||
part 'site_settings_badge_provider.g.dart';
|
part 'site_settings_badge_provider.g.dart';
|
||||||
|
|
||||||
const _supportedSchemes = {'http', 'https'};
|
|
||||||
|
|
||||||
/// Provider that determines whether to show the site settings badge on the tab icon.
|
/// Provider that determines whether to show the site settings badge on the tab icon.
|
||||||
/// Returns true if any site-specific setting has been altered from defaults.
|
/// Returns true if any site-specific setting has been altered from defaults.
|
||||||
@Riverpod()
|
@Riverpod()
|
||||||
@@ -41,8 +40,7 @@ Future<bool> showSiteSettingsBadge(Ref ref) async {
|
|||||||
hasTrackingProtectionExceptionProvider(tabState.id).future,
|
hasTrackingProtectionExceptionProvider(tabState.id).future,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!tabState.url.hasScheme ||
|
if (!tabState.url.hasScheme || !tabState.url.isHttpOrHttps) {
|
||||||
!_supportedSchemes.contains(tabState.url.scheme)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -48,4 +48,4 @@ final class ShowSiteSettingsBadgeProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$showSiteSettingsBadgeHash() =>
|
String _$showSiteSettingsBadgeHash() =>
|
||||||
r'e326617d2d794844c6bbe6d909042b91d95c2996';
|
r'd1499809901363705d23b17aab375fd414080544';
|
||||||
|
|||||||
+2
-1
@@ -25,6 +25,7 @@ import 'package:skeletonizer/skeletonizer.dart';
|
|||||||
import 'package:text_scroll/text_scroll.dart';
|
import 'package:text_scroll/text_scroll.dart';
|
||||||
import 'package:weblibre/core/design/app_colors.dart';
|
import 'package:weblibre/core/design/app_colors.dart';
|
||||||
import 'package:weblibre/core/routing/routes.dart';
|
import 'package:weblibre/core/routing/routes.dart';
|
||||||
|
import 'package:weblibre/extensions/uri.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
|
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/domain/entities/sheet.dart';
|
import 'package:weblibre/features/geckoview/features/browser/domain/entities/sheet.dart';
|
||||||
@@ -52,7 +53,7 @@ class AppBarTitle extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final icon = useMemoized(() {
|
final icon = useMemoized(() {
|
||||||
if (tabState.url.isScheme('http')) {
|
if (tabState.url.isHttp) {
|
||||||
return Icon(
|
return Icon(
|
||||||
MdiIcons.lockOff,
|
MdiIcons.lockOff,
|
||||||
color: Theme.of(context).colorScheme.error,
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
|||||||
+2
-1
@@ -22,6 +22,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
|
import 'package:weblibre/extensions/uri.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
|
|
||||||
class CertificateTile extends HookConsumerWidget {
|
class CertificateTile extends HookConsumerWidget {
|
||||||
@@ -36,7 +37,7 @@ class CertificateTile extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final icon = useMemoized(() {
|
final icon = useMemoized(() {
|
||||||
if (tabState.url.isScheme('http')) {
|
if (tabState.url.isHttp) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
MdiIcons.lockOff,
|
MdiIcons.lockOff,
|
||||||
|
|||||||
+2
-2
@@ -17,6 +17,7 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
import 'package:weblibre/extensions/uri.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/open_link_tools/domain/entities/url_cleaner_result.dart';
|
import 'package:weblibre/features/geckoview/features/open_link_tools/domain/entities/url_cleaner_result.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/open_link_tools/domain/services/url_cleaner_rule.dart';
|
import 'package:weblibre/features/geckoview/features/open_link_tools/domain/services/url_cleaner_rule.dart';
|
||||||
|
|
||||||
@@ -243,8 +244,7 @@ List<String> _findQueryParamMatchesWithRegex(String url, RegExp paramRegex) {
|
|||||||
bool _hasSafeScheme(String url) {
|
bool _hasSafeScheme(String url) {
|
||||||
final parsed = Uri.tryParse(url);
|
final parsed = Uri.tryParse(url);
|
||||||
if (parsed == null || !parsed.hasScheme) return true; // schemeless is ok
|
if (parsed == null || !parsed.hasScheme) return true; // schemeless is ok
|
||||||
final scheme = parsed.scheme.toLowerCase();
|
return parsed.isHttpOrHttps;
|
||||||
return scheme == 'http' || scheme == 'https';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-compiled regexes for URL normalization — avoids recompiling on every call.
|
// Pre-compiled regexes for URL normalization — avoids recompiling on every call.
|
||||||
|
|||||||
+6
-8
@@ -22,6 +22,7 @@ import 'dart:convert';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:weblibre/extensions/uri.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/open_link_tools/data/models/unshorten_response_data.dart';
|
import 'package:weblibre/features/geckoview/features/open_link_tools/data/models/unshorten_response_data.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/open_link_tools/domain/entities/unshorten_result.dart';
|
import 'package:weblibre/features/geckoview/features/open_link_tools/domain/entities/unshorten_result.dart';
|
||||||
|
|
||||||
@@ -136,14 +137,11 @@ class UrlUnshortenerService extends _$UrlUnshortenerService {
|
|||||||
// Reject non-HTTP(S) URLs from the API to prevent scheme-based attacks.
|
// Reject non-HTTP(S) URLs from the API to prevent scheme-based attacks.
|
||||||
if (result.success && result.finalUrl != null) {
|
if (result.success && result.finalUrl != null) {
|
||||||
final parsed = Uri.tryParse(result.finalUrl!);
|
final parsed = Uri.tryParse(result.finalUrl!);
|
||||||
if (parsed != null && parsed.hasScheme) {
|
if (parsed != null && parsed.hasScheme && !parsed.isHttpOrHttps) {
|
||||||
final scheme = parsed.scheme.toLowerCase();
|
return UnshortenResult(
|
||||||
if (scheme != 'http' && scheme != 'https') {
|
success: false,
|
||||||
return UnshortenResult(
|
error: 'Unsupported URL scheme: ${parsed.scheme}',
|
||||||
success: false,
|
);
|
||||||
error: 'Unsupported URL scheme: $scheme',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ final class UrlUnshortenerServiceProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$urlUnshortenerServiceHash() =>
|
String _$urlUnshortenerServiceHash() =>
|
||||||
r'627fd3cc6f6c2251f8f582fcff27cb5a7e2c7813';
|
r'19dae758ed0c48cb183e3da814d01039548fae40';
|
||||||
|
|
||||||
abstract class _$UrlUnshortenerService extends $AsyncNotifier<Set<String>> {
|
abstract class _$UrlUnshortenerService extends $AsyncNotifier<Set<String>> {
|
||||||
FutureOr<Set<String>> build();
|
FutureOr<Set<String>> build();
|
||||||
|
|||||||
+4
-7
@@ -68,13 +68,10 @@ class OpenSharedContent extends HookConsumerWidget {
|
|||||||
// Debounce the URL to avoid running expensive operations on every keystroke.
|
// Debounce the URL to avoid running expensive operations on every keystroke.
|
||||||
final debouncedUrl = useState(currentUrl);
|
final debouncedUrl = useState(currentUrl);
|
||||||
final debouncer = useDebouncer(const Duration(milliseconds: 300));
|
final debouncer = useDebouncer(const Duration(milliseconds: 300));
|
||||||
useEffect(
|
useEffect(() {
|
||||||
() {
|
debouncer.eventOccured(() => debouncedUrl.value = currentUrl);
|
||||||
debouncer.eventOccured(() => debouncedUrl.value = currentUrl);
|
return null;
|
||||||
return null;
|
}, [currentUrl]);
|
||||||
},
|
|
||||||
[currentUrl],
|
|
||||||
);
|
|
||||||
|
|
||||||
final parsedDebouncedUrl = Uri.tryParse(debouncedUrl.value);
|
final parsedDebouncedUrl = Uri.tryParse(debouncedUrl.value);
|
||||||
final hasExternalApp = useCachedFuture(
|
final hasExternalApp = useCachedFuture(
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|||||||
import 'package:weblibre/data/models/web_page_info.dart';
|
import 'package:weblibre/data/models/web_page_info.dart';
|
||||||
import 'package:weblibre/domain/services/generic_website.dart';
|
import 'package:weblibre/domain/services/generic_website.dart';
|
||||||
import 'package:weblibre/extensions/ref_cache.dart';
|
import 'package:weblibre/extensions/ref_cache.dart';
|
||||||
|
import 'package:weblibre/extensions/uri.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
|
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||||
@@ -32,16 +33,13 @@ import 'package:weblibre/features/user/domain/repositories/tor_settings.dart';
|
|||||||
|
|
||||||
part 'website_title.g.dart';
|
part 'website_title.g.dart';
|
||||||
|
|
||||||
const _supportedFetchSchemes = {'http', 'https'};
|
|
||||||
|
|
||||||
@Riverpod()
|
@Riverpod()
|
||||||
class CompletePageInfo extends _$CompletePageInfo {
|
class CompletePageInfo extends _$CompletePageInfo {
|
||||||
@override
|
@override
|
||||||
AsyncValue<WebPageInfo> build(TabState cached) {
|
AsyncValue<WebPageInfo> build(TabState cached) {
|
||||||
ref.cacheFor(const Duration(minutes: 2));
|
ref.cacheFor(const Duration(minutes: 2));
|
||||||
|
|
||||||
if (cached.isPageInfoComplete ||
|
if (cached.isPageInfoComplete || !cached.url.isHttpOrHttps) {
|
||||||
!_supportedFetchSchemes.contains(cached.url.scheme)) {
|
|
||||||
return AsyncData(cached);
|
return AsyncData(cached);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ final class CompletePageInfoProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$completePageInfoHash() => r'edf5538b913d1ebb285c15ec83644818fe90707d';
|
String _$completePageInfoHash() => r'4af0e7c06f07e95aa7e0037782e1a7e661ee61d1';
|
||||||
|
|
||||||
final class CompletePageInfoFamily extends $Family
|
final class CompletePageInfoFamily extends $Family
|
||||||
with
|
with
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:nullability/nullability.dart';
|
import 'package:nullability/nullability.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:weblibre/extensions/uri.dart';
|
||||||
import 'package:weblibre/utils/uri_parser.dart' as uri_parser;
|
import 'package:weblibre/utils/uri_parser.dart' as uri_parser;
|
||||||
|
|
||||||
String? validateUrl(
|
String? validateUrl(
|
||||||
@@ -41,8 +42,7 @@ String? validateUrl(
|
|||||||
if (uri_parser.tryParseUrl(value, eagerParsing: eagerParsing)
|
if (uri_parser.tryParseUrl(value, eagerParsing: eagerParsing)
|
||||||
case final Uri url) {
|
case final Uri url) {
|
||||||
if (!requireAuthority || url.authority.isNotEmpty) {
|
if (!requireAuthority || url.authority.isNotEmpty) {
|
||||||
if (!onlyHttpProtocol ||
|
if (!onlyHttpProtocol || url.isHttpOrHttps) {
|
||||||
(url.isScheme('https') || url.isScheme('http'))) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user