LibWeb: Return OptionalNone from DOMURL::parse on failure

This ports one more function away from needing to use the awkward
valid state of the URL class.
This commit is contained in:
Shannon Booth 2025-01-22 17:35:52 +13:00 committed by Sam Atkins
parent b81d6945dc
commit fd27eef0d1
Notes: github-actions[bot] 2025-01-22 12:34:57 +00:00
18 changed files with 63 additions and 65 deletions

View file

@ -1128,7 +1128,7 @@ URL::URL Document::parse_url(StringView url) const
auto base_url = this->base_url();
// 2. Return the result of applying the URL parser to url, with baseURL.
return DOMURL::parse(url, base_url);
return DOMURL::parse(url, base_url).value_or(URL::URL {});
}
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#encoding-parsing-a-url
@ -1145,7 +1145,7 @@ URL::URL Document::encoding_parse_url(StringView url) const
auto base_url = this->base_url();
// 5. Return the result of applying the URL parser to url, with baseURL and encoding.
return DOMURL::parse(url, base_url, encoding);
return DOMURL::parse(url, base_url, encoding).value_or(URL::URL {});
}
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#encoding-parsing-and-serializing-a-url

View file

@ -133,15 +133,15 @@ void DOMURL::revoke_object_url(JS::VM&, StringView url)
auto url_record = parse(url);
// Spec Bug: https://github.com/w3c/FileAPI/issues/207, missing check for URL failure parsing.
if (!url_record.is_valid())
if (!url_record.has_value())
return;
// 2. If url records scheme is not "blob", return.
if (url_record.scheme() != "blob"sv)
if (url_record->scheme() != "blob"sv)
return;
// 3. Let entry be urlRecords blob URL entry.
auto& entry = url_record.blob_url_entry();
auto const& entry = url_record->blob_url_entry();
// 4. If entry is null, return.
if (!entry.has_value())
@ -156,7 +156,7 @@ void DOMURL::revoke_object_url(JS::VM&, StringView url)
// 7. Remove an entry from the Blob URL Store for url.
// FIXME: Spec bug: https://github.com/w3c/FileAPI/issues/207, urlRecord should instead be passed through.
FileAPI::remove_entry_from_blob_url_store(url_record);
FileAPI::remove_entry_from_blob_url_store(*url_record);
}
// https://url.spec.whatwg.org/#dom-url-canparse
@ -485,7 +485,7 @@ void strip_trailing_spaces_from_an_opaque_path(DOMURL& url)
}
// https://url.spec.whatwg.org/#concept-url-parser
URL::URL parse(StringView input, Optional<URL::URL const&> base_url, Optional<StringView> encoding)
Optional<URL::URL> parse(StringView input, Optional<URL::URL const&> base_url, Optional<StringView> encoding)
{
// FIXME: We should probably have an extended version of URL::URL for LibWeb instead of standalone functions like this.
@ -494,7 +494,7 @@ URL::URL parse(StringView input, Optional<URL::URL const&> base_url, Optional<St
// 2. If url is failure, return failure.
if (!url.has_value())
return {}; // FIXME: Migrate this API to return an OptionalNone on failure.
return {};
// 3. If urls scheme is not "blob", return url.
if (url->scheme() != "blob")

View file

@ -96,6 +96,6 @@ private:
void strip_trailing_spaces_from_an_opaque_path(DOMURL& url);
// https://url.spec.whatwg.org/#concept-url-parser
URL::URL parse(StringView input, Optional<URL::URL const&> base_url = {}, Optional<StringView> encoding = {});
Optional<URL::URL> parse(StringView input, Optional<URL::URL const&> base_url = {}, Optional<StringView> encoding = {});
}

View file

@ -135,12 +135,12 @@ ErrorOr<Optional<URL::URL>> Response::location_url(Optional<String> const& reque
// 3. If location is a header value, then set location to the result of parsing location with responses URL.
auto location = DOMURL::parse(location_values.first(), url());
if (!location.is_valid())
if (!location.has_value())
return Error::from_string_literal("Invalid 'Location' header URL");
// 4. If location is a URL whose fragment is null, then set locations fragment to requestFragment.
if (!location.fragment().has_value())
location.set_fragment(request_fragment);
if (!location->fragment().has_value())
location->set_fragment(request_fragment);
// 5. Return location.
return location;

View file

@ -124,16 +124,16 @@ WebIDL::ExceptionOr<GC::Ref<Request>> Request::construct_impl(JS::Realm& realm,
auto parsed_url = DOMURL::parse(input.get<String>(), base_url);
// 2. If parsedURL is failure, then throw a TypeError.
if (!parsed_url.is_valid())
if (!parsed_url.has_value())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Input URL is not valid"sv };
// 3. If parsedURL includes credentials, then throw a TypeError.
if (parsed_url.includes_credentials())
if (parsed_url->includes_credentials())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Input URL must not include credentials"sv };
// 4. Set request to a new request whose URL is parsedURL.
input_request = Infrastructure::Request::create(vm);
input_request->set_url(move(parsed_url));
input_request->set_url(parsed_url.release_value());
// 5. Set fallbackMode to "cors".
fallback_mode = Infrastructure::Request::Mode::CORS;
@ -302,21 +302,21 @@ WebIDL::ExceptionOr<GC::Ref<Request>> Request::construct_impl(JS::Realm& realm,
auto parsed_referrer = DOMURL::parse(referrer, base_url);
// 2. If parsedReferrer is failure, then throw a TypeError.
if (!parsed_referrer.is_valid())
if (!parsed_referrer.has_value())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Referrer must be a valid URL"sv };
// 3. If one of the following is true
// - parsedReferrers scheme is "about" and path is the string "client"
// - parsedReferrers origin is not same origin with origin
// then set requests referrer to "client".
auto parsed_referrer_origin = parsed_referrer.origin();
if ((parsed_referrer.scheme() == "about"sv && parsed_referrer.paths().size() == 1 && parsed_referrer.paths()[0] == "client"sv)
auto parsed_referrer_origin = parsed_referrer->origin();
if ((parsed_referrer->scheme() == "about"sv && parsed_referrer->paths().size() == 1 && parsed_referrer->paths()[0] == "client"sv)
|| !parsed_referrer_origin.is_same_origin(origin)) {
request->set_referrer(Infrastructure::Request::Referrer::Client);
}
// 4. Otherwise, set requests referrer to parsedReferrer.
else {
request->set_referrer(move(parsed_referrer));
request->set_referrer(parsed_referrer.release_value());
}
}
}

View file

@ -186,7 +186,7 @@ WebIDL::ExceptionOr<GC::Ref<Response>> Response::redirect(JS::VM& vm, String con
auto parsed_url = DOMURL::parse(url, api_base_url);
// 2. If parsedURL is failure, then throw a TypeError.
if (!parsed_url.is_valid())
if (!parsed_url.has_value())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Redirect URL is not valid"sv };
// 3. If status is not a redirect status, then throw a RangeError.
@ -201,7 +201,7 @@ WebIDL::ExceptionOr<GC::Ref<Response>> Response::redirect(JS::VM& vm, String con
response_object->response()->set_status(status);
// 6. Let value be parsedURL, serialized and isomorphic encoded.
auto value = parsed_url.serialize();
auto value = parsed_url->serialize();
// 7. Append (`Location`, value) to responseObjects responses header list.
auto header = Infrastructure::Header::from_string_pair("Location"sv, value);

View file

@ -268,11 +268,11 @@ GC::Ptr<Fetch::Infrastructure::Request> HTMLLinkElement::create_link_request(HTM
auto url = DOMURL::parse(options.href, options.base_url);
// 4. If url is failure, then return null.
if (!url.is_valid())
if (!url.has_value())
return nullptr;
// 5. Let request be the result of creating a potential-CORS request given url, options's destination, and options's crossorigin.
auto request = create_potential_CORS_request(vm(), url, options.destination, options.crossorigin);
auto request = create_potential_CORS_request(vm(), *url, options.destination, options.crossorigin);
// 6. Set request's policy container to options's policy container.
request->set_policy_container(options.policy_container);

View file

@ -205,7 +205,7 @@ URL::URL EnvironmentSettingsObject::parse_url(StringView url)
auto base_url = api_base_url();
// 2. Return the result of applying the URL parser to url, with baseURL.
return DOMURL::parse(url, base_url);
return DOMURL::parse(url, base_url).value_or(URL::URL {});
}
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#encoding-parsing-a-url
@ -225,7 +225,7 @@ URL::URL EnvironmentSettingsObject::encoding_parse_url(StringView url)
auto base_url = api_base_url();
// 5. Return the result of applying the URL parser to url, with baseURL and encoding.
return DOMURL::parse(url, base_url, encoding);
return DOMURL::parse(url, base_url, encoding).value_or(URL::URL {});
}
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#encoding-parsing-and-serializing-a-url

View file

@ -221,15 +221,15 @@ WebIDL::ExceptionOr<Optional<URL::URL>> resolve_imports_match(ByteString const&
// 6. If url is failure, then throw a TypeError indicating that resolution of normalizedSpecifier was blocked since the afterPrefix portion
// could not be URL-parsed relative to the resolutionResult mapped to by the specifierKey prefix.
if (!url.is_valid())
if (!url.has_value())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("Could not resolve '{}' as the after prefix portion could not be URL-parsed.", normalized_specifier).release_value_but_fixme_should_propagate_errors() };
// 7. Assert: url is a URL.
VERIFY(url.is_valid());
VERIFY(url.has_value());
// 8. If the serialization of resolutionResult is not a code unit prefix of the serialization of url, then throw a TypeError indicating
// that the resolution of normalizedSpecifier was blocked due to it backtracking above its prefix specifierKey.
if (!Infra::is_code_unit_prefix(resolution_result->serialize(), url.serialize()))
if (!Infra::is_code_unit_prefix(resolution_result->serialize(), url->serialize()))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("Could not resolve '{}' as it backtracks above its prefix specifierKey.", normalized_specifier).release_value_but_fixme_should_propagate_errors() };
// 9. Return url.
@ -250,7 +250,7 @@ Optional<URL::URL> resolve_url_like_module_specifier(ByteString const& specifier
auto url = DOMURL::parse(specifier, base_url);
// 2. If url is failure, then return null.
if (!url.is_valid())
if (!url.has_value())
return {};
// 3. Return url.
@ -261,7 +261,7 @@ Optional<URL::URL> resolve_url_like_module_specifier(ByteString const& specifier
auto url = DOMURL::parse(specifier);
// 3. If url is failure, then return null.
if (!url.is_valid())
if (!url.has_value())
return {};
// 4. Return url.

View file

@ -202,7 +202,7 @@ WebIDL::ExceptionOr<HashMap<URL::URL, ModuleSpecifierMap>> sort_and_normalise_sc
auto scope_prefix_url = DOMURL::parse(scope_prefix.as_string(), base_url);
// 3. If scopePrefixURL is failure, then:
if (!scope_prefix_url.is_valid()) {
if (!scope_prefix_url.has_value()) {
// 1. The user agent may report a warning to the console that the scope prefix URL was not parseable.
auto& console = realm.intrinsics().console_object()->console();
console.output_debug_message(JS::Console::LogLevel::Warn,
@ -213,7 +213,7 @@ WebIDL::ExceptionOr<HashMap<URL::URL, ModuleSpecifierMap>> sort_and_normalise_sc
}
// 4. Let normalizedScopePrefix be the serialization of scopePrefixURL.
auto normalised_scope_prefix = scope_prefix_url.serialize();
auto normalised_scope_prefix = scope_prefix_url->serialize();
// 5. Set normalized[normalizedScopePrefix] to the result of sorting and normalizing a module specifier map given potentialSpecifierMap and baseURL.
normalised.set(normalised_scope_prefix, TRY(sort_and_normalise_module_specifier_map(realm, potential_specifier_map.as_object(), base_url)));

View file

@ -1105,11 +1105,11 @@ WebIDL::ExceptionOr<void> Window::window_post_message_steps(JS::Value message, W
auto parsed_url = DOMURL::parse(options.target_origin);
// 2. If parsedURL is failure, then throw a "SyntaxError" DOMException.
if (!parsed_url.is_valid())
if (!parsed_url.has_value())
return WebIDL::SyntaxError::create(target_realm, MUST(String::formatted("Invalid URL for targetOrigin: '{}'", options.target_origin)));
// 3. Set targetOrigin to parsedURL's origin.
target_origin = parsed_url.origin();
target_origin = parsed_url->origin();
}
// 6. Let transfer be options["transfer"].

View file

@ -167,14 +167,14 @@ void Internals::spoof_current_url(String const& url_string)
{
auto url = DOMURL::parse(url_string);
VERIFY(url.is_valid());
VERIFY(url.has_value());
auto origin = url.origin();
auto origin = url->origin();
auto& window = internals_window();
window.associated_document().set_url(url);
window.associated_document().set_url(url.value());
window.associated_document().set_origin(origin);
HTML::relevant_settings_object(window.associated_document()).creation_url = url;
HTML::relevant_settings_object(window.associated_document()).creation_url = url.release_value();
}
GC::Ref<InternalAnimationTimeline> Internals::create_internal_animation_timeline()

View file

@ -302,7 +302,7 @@ static void update(JS::VM& vm, GC::Ref<Job> job)
auto resolved_scope = DOMURL::parse("./"sv, job->script_url);
// 2. Set maxScopeString to "/", followed by the strings in resolvedScopes path (including empty strings), separated from each other by "/".
max_scope_string = join_paths_with_slash(resolved_scope);
max_scope_string = join_paths_with_slash(*resolved_scope);
}
// 14. Else:
else {
@ -310,9 +310,9 @@ static void update(JS::VM& vm, GC::Ref<Job> job)
auto max_scope = DOMURL::parse(service_worker_allowed.get<Vector<ByteBuffer>>()[0], job->script_url);
// 2. If maxScopes origin is jobs script url's origin, then:
if (max_scope.origin().is_same_origin(job->script_url.origin())) {
if (max_scope->origin().is_same_origin(job->script_url.origin())) {
// 1. Set maxScopeString to "/", followed by the strings in maxScopes path (including empty strings), separated from each other by "/".
max_scope_string = join_paths_with_slash(max_scope);
max_scope_string = join_paths_with_slash(*max_scope);
}
}

View file

@ -81,13 +81,13 @@ GC::Ref<WebIDL::Promise> ServiceWorkerContainer::register_(String script_url, Re
}
// https://w3c.github.io/ServiceWorker/#start-register-algorithm
void ServiceWorkerContainer::start_register(Optional<URL::URL> scope_url, URL::URL script_url, GC::Ref<WebIDL::Promise> promise, HTML::EnvironmentSettingsObject& client, URL::URL referrer, Bindings::WorkerType worker_type, Bindings::ServiceWorkerUpdateViaCache update_via_cache)
void ServiceWorkerContainer::start_register(Optional<URL::URL> scope_url, Optional<URL::URL> script_url, GC::Ref<WebIDL::Promise> promise, HTML::EnvironmentSettingsObject& client, URL::URL referrer, Bindings::WorkerType worker_type, Bindings::ServiceWorkerUpdateViaCache update_via_cache)
{
auto& realm = this->realm();
auto& vm = realm.vm();
// 1. If scriptURL is failure, reject promise with a TypeError and abort these steps.
if (!script_url.is_valid()) {
if (!script_url.has_value()) {
WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scriptURL is not a valid URL"sv));
return;
}
@ -95,17 +95,17 @@ void ServiceWorkerContainer::start_register(Optional<URL::URL> scope_url, URL::U
// 2. Set scriptURLs fragment to null.
// Note: The user agent does not store the fragment of the scripts url.
// This means that the fragment does not have an effect on identifying service workers.
script_url.set_fragment({});
script_url->set_fragment({});
// 3. If scriptURLs scheme is not one of "http" and "https", reject promise with a TypeError and abort these steps.
if (!script_url.scheme().is_one_of("http"sv, "https"sv)) {
if (!script_url->scheme().is_one_of("http"sv, "https"sv)) {
WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scriptURL must have a scheme of 'http' or 'https'"sv));
return;
}
// 4. If any of the strings in scriptURLs path contains either ASCII case-insensitive "%2f" or ASCII case-insensitive "%5c",
// reject promise with a TypeError and abort these steps.
auto invalid_path = script_url.paths().first_matching([&](auto& path) {
auto invalid_path = script_url->paths().first_matching([&](auto& path) {
return path.contains("%2f"sv, CaseSensitivity::CaseInsensitive) || path.contains("%5c"sv, CaseSensitivity::CaseInsensitive);
});
if (invalid_path.has_value()) {
@ -156,7 +156,7 @@ void ServiceWorkerContainer::start_register(Optional<URL::URL> scope_url, URL::U
}
// 11. Let job be the result of running Create Job with register, storage key, scopeURL, scriptURL, promise, and client.
auto job = Job::create(vm, Job::Type::Register, storage_key.value(), scope_url.value(), script_url, promise, client);
auto job = Job::create(vm, Job::Type::Register, storage_key.value(), scope_url.value(), script_url.release_value(), promise, client);
// 12. Set jobs worker type to workerType.
job->worker_type = worker_type;

View file

@ -49,7 +49,7 @@ private:
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
void start_register(Optional<URL::URL> scope_url, URL::URL script_url, GC::Ref<WebIDL::Promise>, HTML::EnvironmentSettingsObject&, URL::URL referrer, Bindings::WorkerType, Bindings::ServiceWorkerUpdateViaCache);
void start_register(Optional<URL::URL> scope_url, Optional<URL::URL> script_url, GC::Ref<WebIDL::Promise>, HTML::EnvironmentSettingsObject&, URL::URL referrer, Bindings::WorkerType, Bindings::ServiceWorkerUpdateViaCache);
GC::Ref<HTML::EnvironmentSettingsObject> m_service_worker_client;
};

View file

@ -49,22 +49,22 @@ WebIDL::ExceptionOr<GC::Ref<WebSocket>> WebSocket::construct_impl(JS::Realm& rea
auto url_record = DOMURL::parse(url, base_url);
// 3. If urlRecord is failure, then throw a "SyntaxError" DOMException.
if (!url_record.is_valid())
if (!url_record.has_value())
return WebIDL::SyntaxError::create(realm, "Invalid URL"_string);
// 4. If urlRecords scheme is "http", then set urlRecords scheme to "ws".
if (url_record.scheme() == "http"sv)
url_record.set_scheme("ws"_string);
if (url_record->scheme() == "http"sv)
url_record->set_scheme("ws"_string);
// 5. Otherwise, if urlRecords scheme is "https", set urlRecords scheme to "wss".
else if (url_record.scheme() == "https"sv)
url_record.set_scheme("wss"_string);
else if (url_record->scheme() == "https"sv)
url_record->set_scheme("wss"_string);
// 6. If urlRecords scheme is not "ws" or "wss", then throw a "SyntaxError" DOMException.
if (!url_record.scheme().is_one_of("ws"sv, "wss"sv))
if (!url_record->scheme().is_one_of("ws"sv, "wss"sv))
return WebIDL::SyntaxError::create(realm, "Invalid protocol"_string);
// 7. If urlRecords fragment is non-null, then throw a "SyntaxError" DOMException.
if (url_record.fragment().has_value())
if (url_record->fragment().has_value())
return WebIDL::SyntaxError::create(realm, "Presence of URL fragment is invalid"_string);
Vector<String> protocols_sequence;
@ -94,14 +94,14 @@ WebIDL::ExceptionOr<GC::Ref<WebSocket>> WebSocket::construct_impl(JS::Realm& rea
}
// 10. Set this's url to urlRecord.
web_socket->set_url(url_record);
web_socket->set_url(*url_record);
// 11. Let client be thiss relevant settings object.
auto& client = relevant_settings_object;
// FIXME: 12. Run this step in parallel:
// 1. Establish a WebSocket connection given urlRecord, protocols, and client. [FETCH]
TRY_OR_THROW_OOM(vm, web_socket->establish_web_socket_connection(url_record, protocols_sequence, client));
TRY_OR_THROW_OOM(vm, web_socket->establish_web_socket_connection(*url_record, protocols_sequence, client));
return web_socket;
}

View file

@ -487,20 +487,20 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::open(String const& method_string, Stri
auto parsed_url = DOMURL::parse(url, api_base_url, api_url_character_encoding);
// 6. If parsedURL is failure, then throw a "SyntaxError" DOMException.
if (!parsed_url.is_valid())
if (!parsed_url.has_value())
return WebIDL::SyntaxError::create(realm(), "Invalid URL"_string);
// 7. If the async argument is omitted, set async to true, and set username and password to null.
// NOTE: This is handled in the overload lacking the async argument.
// 8. If parsedURLs host is non-null, then:
if (parsed_url.host().has_value()) {
if (parsed_url->host().has_value()) {
// 1. If the username argument is not null, set the username given parsedURL and username.
if (username.has_value())
parsed_url.set_username(username.value());
parsed_url->set_username(username.value());
// 2. If the password argument is not null, set the password given parsedURL and password.
if (password.has_value())
parsed_url.set_password(password.value());
parsed_url->set_password(password.value());
}
// 9. If async is false, the current global object is a Window object, and either thiss timeout is
@ -523,7 +523,7 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::open(String const& method_string, Stri
// Set thiss request method to method.
m_request_method = normalized_method.span();
// Set thiss request URL to parsedURL.
m_request_url = parsed_url;
m_request_url = parsed_url.release_value();
// Set thiss synchronous flag if async is false; otherwise unset thiss synchronous flag.
m_synchronous = !async;
// Empty thiss author request headers.

View file

@ -286,9 +286,7 @@ String SourceHighlighterClient::to_html_string(URL::URL const& url, URL::URL con
auto attribute_url = MUST(String::formatted("{}", attribute_value));
auto attribute_url_without_quotes = attribute_url.bytes_as_string_view().trim("\""sv);
if (auto resolved = Web::DOMURL::parse(attribute_url_without_quotes, base_url); resolved.is_valid())
return resolved;
return {};
return Web::DOMURL::parse(attribute_url_without_quotes, base_url);
};
size_t span_index = 0;