improved uri parsing
This commit is contained in:
@@ -88,7 +88,14 @@ class EditBangScreen extends HookConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
final uri = Uri.parse(urlTextController.text);
|
||||
final uri = parseValidatedUrl(
|
||||
urlTextController.text,
|
||||
eagerParsing: false,
|
||||
onlyHttpProtocol: true,
|
||||
);
|
||||
if (uri == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final bang = Bang(
|
||||
group: BangGroup.user,
|
||||
|
||||
+10
-3
@@ -28,7 +28,7 @@ import 'package:weblibre/features/geckoview/features/bookmarks/domain/repositori
|
||||
import 'package:weblibre/features/geckoview/features/bookmarks/presentation/dialogs/delete_bookmark_dialog.dart';
|
||||
import 'package:weblibre/features/geckoview/features/bookmarks/presentation/widgets/folder_tree_picker.dart';
|
||||
import 'package:weblibre/utils/form_validators.dart';
|
||||
import 'package:weblibre/utils/uri_parser.dart' as uri_parser;
|
||||
import 'package:weblibre/utils/uri_input_parser.dart';
|
||||
|
||||
class BookmarkEntryEditScreen extends HookConsumerWidget {
|
||||
final BookmarkInfo? initialInfo;
|
||||
@@ -66,10 +66,17 @@ class BookmarkEntryEditScreen extends HookConsumerWidget {
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
if (formKey.currentState?.validate() ?? false) {
|
||||
final newUrl = uri_parser.tryParseUrl(
|
||||
var newUrl = parseValidatedUrl(
|
||||
urlTextController.text,
|
||||
eagerParsing: true,
|
||||
)!;
|
||||
onlyHttpProtocol: true,
|
||||
);
|
||||
|
||||
if (newUrl == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
newUrl = redactUriCredentials(newUrl);
|
||||
|
||||
if (exisitingEntry != null) {
|
||||
await ref
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:weblibre/core/logger.dart';
|
||||
import 'package:weblibre/utils/uri_input_parser.dart';
|
||||
|
||||
class BookmarkJSONUtils {
|
||||
final GeckoBookmarksService _service;
|
||||
@@ -156,7 +157,11 @@ class BookmarkJSONUtils {
|
||||
await _service.addItem(parentGuid, uri, title, i);
|
||||
count++;
|
||||
} else {
|
||||
logger.w('Skipping invalid URL: $url');
|
||||
final parsed = Uri.tryParse(url);
|
||||
final redacted = parsed != null
|
||||
? redactUriCredentials(parsed)
|
||||
: url;
|
||||
logger.w('Skipping invalid URL: $redacted');
|
||||
}
|
||||
} catch (e) {
|
||||
logger.e('Failed to import bookmark "$title": $e');
|
||||
|
||||
+23
-4
@@ -74,7 +74,10 @@ class OpenSharedContent extends HookConsumerWidget {
|
||||
return null;
|
||||
}, [currentUrl]);
|
||||
|
||||
final parsedDebouncedUrl = Uri.tryParse(debouncedUrl.value);
|
||||
final parsedDebouncedUrl = parseValidatedUrl(
|
||||
debouncedUrl.value,
|
||||
eagerParsing: false,
|
||||
);
|
||||
final hasExternalApp = useCachedFuture(
|
||||
// ignore: discarded_futures useFuture
|
||||
() => parsedDebouncedUrl != null
|
||||
@@ -163,10 +166,18 @@ class OpenSharedContent extends HookConsumerWidget {
|
||||
|
||||
Future<void> openTab(TabMode tabMode) async {
|
||||
if (formKey.currentState?.validate() == true) {
|
||||
final parsedUrl = parseValidatedUrl(
|
||||
textController.text,
|
||||
eagerParsing: false,
|
||||
);
|
||||
if (parsedUrl == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(
|
||||
url: Uri.parse(textController.text),
|
||||
url: parsedUrl,
|
||||
tabMode: tabMode,
|
||||
containerSelection: selectedContainer.value == null
|
||||
? const TabContainerSelection.unassigned()
|
||||
@@ -183,8 +194,16 @@ class OpenSharedContent extends HookConsumerWidget {
|
||||
|
||||
Future<void> openCustomTab(bool isPrivate) async {
|
||||
if (formKey.currentState?.validate() == true) {
|
||||
final parsedUrl = parseValidatedUrl(
|
||||
textController.text,
|
||||
eagerParsing: false,
|
||||
);
|
||||
if (parsedUrl == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
await GeckoBrowserService().openInCustomTab(
|
||||
url: Uri.parse(textController.text),
|
||||
url: parsedUrl,
|
||||
private: isPrivate,
|
||||
contextId: selectedContainer.value?.id,
|
||||
);
|
||||
@@ -197,7 +216,7 @@ class OpenSharedContent extends HookConsumerWidget {
|
||||
|
||||
Future<void> openInApp() async {
|
||||
if (formKey.currentState?.validate() == true) {
|
||||
final uri = Uri.tryParse(textController.text);
|
||||
final uri = parseValidatedUrl(textController.text, eagerParsing: false);
|
||||
if (uri == null) return;
|
||||
|
||||
final success = await _appLinksService.openAppLink(uri);
|
||||
|
||||
+16
-3
@@ -20,14 +20,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'
|
||||
show GeckoBrowserService;
|
||||
|
||||
import 'package:weblibre/utils/ui_helper.dart';
|
||||
import 'package:weblibre/utils/uri_input_parser.dart';
|
||||
import 'package:weblibre/utils/uri_policy.dart';
|
||||
|
||||
Future<void> openInPrivateCustomTab(BuildContext context, String url) async {
|
||||
try {
|
||||
await GeckoBrowserService().openInCustomTab(
|
||||
url: Uri.parse(url),
|
||||
private: true,
|
||||
final parsedUrl = parseUserInputUrl(
|
||||
url,
|
||||
policy: SchemePolicy.internalIntent,
|
||||
allowSchemelessHosts: true,
|
||||
enforceMaxInputLength: true,
|
||||
);
|
||||
if (parsedUrl == null) {
|
||||
if (context.mounted) {
|
||||
showErrorMessage(context, 'Could not open link: $url');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await GeckoBrowserService().openInCustomTab(url: parsedUrl, private: true);
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
showErrorMessage(context, 'Could not open link: $url');
|
||||
|
||||
@@ -47,8 +47,9 @@ import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/c
|
||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||
import 'package:weblibre/presentation/hooks/on_listenable_change_selector.dart';
|
||||
import 'package:weblibre/presentation/hooks/sampled_value_notifier.dart';
|
||||
import 'package:weblibre/utils/input_classification.dart';
|
||||
import 'package:weblibre/utils/text_field_line_count.dart';
|
||||
import 'package:weblibre/utils/uri_parser.dart' as uri_parser;
|
||||
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
||||
|
||||
class SearchScreen extends HookConsumerWidget {
|
||||
final String? initialSearchText;
|
||||
@@ -358,12 +359,26 @@ class SearchScreen extends HookConsumerWidget {
|
||||
unfocusOnTapOutside: false,
|
||||
onSubmitted: (value) async {
|
||||
if (value.isNotEmpty) {
|
||||
var newUrl = uri_parser.tryParseUrl(
|
||||
value,
|
||||
eagerParsing: true,
|
||||
);
|
||||
final classification = classifyAddressBarInput(value);
|
||||
Uri? newUrl;
|
||||
String? searchQuery;
|
||||
|
||||
if (newUrl == null) {
|
||||
switch (classification) {
|
||||
case NavigateInputClassification(:final uri):
|
||||
newUrl = uri;
|
||||
case SearchInputClassification(:final query):
|
||||
searchQuery = query;
|
||||
case InvalidInputClassification():
|
||||
if (context.mounted) {
|
||||
ui_helper.showErrorMessage(
|
||||
context,
|
||||
'Invalid address',
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (newUrl == null && searchQuery != null) {
|
||||
// Read from both providers - use site if set, otherwise global
|
||||
final siteBang = isEditMode
|
||||
? ref.read(
|
||||
@@ -383,12 +398,12 @@ class SearchScreen extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
if (bang != null) {
|
||||
newUrl = bang.getTemplateUrl(value);
|
||||
newUrl = bang.getTemplateUrl(searchQuery);
|
||||
|
||||
if (!privateTabMode) {
|
||||
await ref
|
||||
.read(bangSearchProvider.notifier)
|
||||
.triggerBangSearch(bang, value);
|
||||
.triggerBangSearch(bang, searchQuery);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/co
|
||||
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
||||
import 'package:weblibre/utils/form_validators.dart';
|
||||
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
||||
import 'package:weblibre/utils/uri_parser.dart' as uri_parser;
|
||||
|
||||
class ContainerSitesScreen extends HookConsumerWidget {
|
||||
final Set<Uri> initialSites;
|
||||
@@ -91,11 +90,16 @@ class ContainerSitesScreen extends HookConsumerWidget {
|
||||
return uriValid;
|
||||
}
|
||||
|
||||
final origin = Uri.parse(
|
||||
uri_parser
|
||||
.tryParseUrl(value, eagerParsing: true)!
|
||||
.origin,
|
||||
final parsedUrl = parseValidatedUrl(
|
||||
value,
|
||||
eagerParsing: true,
|
||||
onlyHttpProtocol: true,
|
||||
);
|
||||
if (parsedUrl == null) {
|
||||
return 'Invalid URL';
|
||||
}
|
||||
|
||||
final origin = Uri.parse(parsedUrl.origin);
|
||||
|
||||
if (sites.value.contains(origin)) {
|
||||
return 'This site has been already assigned';
|
||||
@@ -104,11 +108,16 @@ class ContainerSitesScreen extends HookConsumerWidget {
|
||||
return null;
|
||||
},
|
||||
onSaved: (newValue) async {
|
||||
final origin = Uri.parse(
|
||||
uri_parser
|
||||
.tryParseUrl(newValue, eagerParsing: true)!
|
||||
.origin,
|
||||
final parsedUrl = parseValidatedUrl(
|
||||
newValue,
|
||||
eagerParsing: true,
|
||||
onlyHttpProtocol: true,
|
||||
);
|
||||
if (parsedUrl == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final origin = Uri.parse(parsedUrl.origin);
|
||||
|
||||
final isAssigned = await ref
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
|
||||
@@ -18,14 +18,13 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:weblibre/utils/uri_parser.dart' as uri_parser;
|
||||
import 'package:weblibre/utils/input_classification.dart';
|
||||
|
||||
sealed class SharedContent with FastEquatable {
|
||||
SharedContent();
|
||||
|
||||
factory SharedContent.parse(String content) {
|
||||
if (uri_parser.tryParseUrl(content, eagerParsing: true)
|
||||
case final Uri uri) {
|
||||
if (parseSharedIntentUrl(content) case final Uri uri) {
|
||||
return SharedUrl(uri);
|
||||
} else {
|
||||
return SharedText(content);
|
||||
|
||||
@@ -24,7 +24,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:weblibre/core/routing/routes.dart';
|
||||
import 'package:weblibre/features/web_feed/domain/providers/add_dialog_blocking.dart';
|
||||
import 'package:weblibre/utils/form_validators.dart';
|
||||
import 'package:weblibre/utils/uri_parser.dart' as uri_parser;
|
||||
|
||||
class AddFeedDialog extends HookConsumerWidget {
|
||||
final Uri? initialUri;
|
||||
@@ -83,9 +82,16 @@ class AddFeedDialog extends HookConsumerWidget {
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (formKey.currentState?.validate() == true) {
|
||||
FeedCreateRoute(
|
||||
feedId: uri_parser.tryParseUrl(textController.text)!,
|
||||
).pushReplacement(context);
|
||||
final feedId = parseValidatedUrl(
|
||||
textController.text,
|
||||
eagerParsing: false,
|
||||
onlyHttpProtocol: true,
|
||||
);
|
||||
if (feedId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
FeedCreateRoute(feedId: feedId).pushReplacement(context);
|
||||
}
|
||||
},
|
||||
child: const Text('Add'),
|
||||
|
||||
@@ -34,7 +34,6 @@ import 'package:weblibre/features/web_feed/presentation/widgets/tag_field.dart';
|
||||
import 'package:weblibre/presentation/widgets/failure_widget.dart';
|
||||
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
||||
import 'package:weblibre/utils/form_validators.dart';
|
||||
import 'package:weblibre/utils/uri_parser.dart' as uri_parser;
|
||||
|
||||
enum _DialogMode { create, edit }
|
||||
|
||||
@@ -156,19 +155,22 @@ class _FeedEditContent extends HookConsumerWidget {
|
||||
onPressed: () async {
|
||||
if (formKey.currentState?.validate() ?? false) {
|
||||
final feedData = FeedData(
|
||||
url: uri_parser.tryParseUrl(
|
||||
url: parseValidatedUrl(
|
||||
urlTextController.text,
|
||||
eagerParsing: true,
|
||||
eagerParsing: false,
|
||||
onlyHttpProtocol: true,
|
||||
)!,
|
||||
authors: initialFeed.authors,
|
||||
description: descriptionTextController.text.whenNotEmpty,
|
||||
icon: uri_parser.tryParseUrl(
|
||||
icon: parseValidatedUrl(
|
||||
iconUrlTextController.text,
|
||||
eagerParsing: true,
|
||||
eagerParsing: false,
|
||||
onlyHttpProtocol: true,
|
||||
),
|
||||
siteLink: uri_parser.tryParseUrl(
|
||||
siteLink: parseValidatedUrl(
|
||||
siteLinkTextController.text,
|
||||
eagerParsing: true,
|
||||
eagerParsing: false,
|
||||
onlyHttpProtocol: true,
|
||||
),
|
||||
tags: tags.value.map((tag) => FeedCategory(id: tag)).toList(),
|
||||
title: titleTextController.text.whenNotEmpty,
|
||||
|
||||
Reference in New Issue
Block a user