diff --git a/Libraries/LibURL/Parser.cpp b/Libraries/LibURL/Parser.cpp index 0d32c124fa..a8df8a3981 100644 --- a/Libraries/LibURL/Parser.cpp +++ b/Libraries/LibURL/Parser.cpp @@ -534,6 +534,9 @@ static ErrorOr domain_to_ascii(StringView domain, bool be_strict) // https://url.spec.whatwg.org/#concept-host-parser Optional Parser::parse_host(StringView input, bool is_opaque) { +#ifdef ENABLE_RUST + return RustIntegration::parse_host(input, is_opaque); +#endif // 1. If input starts with U+005B ([), then: if (input.starts_with('[')) { // 1. If input does not end with U+005D (]), IPv6-unclosed validation error, return failure. diff --git a/Libraries/LibURL/Rust/src/ffi/url.rs b/Libraries/LibURL/Rust/src/ffi/url.rs index ea78a8665d..8d613b07f0 100644 --- a/Libraries/LibURL/Rust/src/ffi/url.rs +++ b/Libraries/LibURL/Rust/src/ffi/url.rs @@ -17,6 +17,7 @@ use crate::url::Url; use crate::url::basic_parse; use crate::url::basic_parse_into; use crate::url::is_special_scheme; +use crate::url::parse_host; #[repr(C)] #[derive(Clone, Copy)] @@ -91,6 +92,7 @@ pub struct RustBasicParseOptions { } pub type FfiUrlResultFn = unsafe extern "C" fn(*mut c_void, *const RustFfiUrl); +pub type FfiHostResultFn = unsafe extern "C" fn(*mut c_void, *const FfiUrlHost); fn abort_on_panic R, R>(f: F) -> R { match catch_unwind(AssertUnwindSafe(f)) { @@ -287,3 +289,33 @@ pub unsafe extern "C" fn rust_url_basic_parse( did_succeed }) } + +/// # Safety +/// `input` must be valid for `input_length` bytes. +/// `on_complete` is called exactly once with either a host result or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_url_parse_host( + input: *const u8, + input_length: usize, + is_opaque: bool, + ctx: *mut c_void, + on_complete: FfiHostResultFn, +) -> bool { + abort_on_panic(|| { + // SAFETY: caller guarantees input is valid. + let input_bytes = unsafe { std::slice::from_raw_parts(input, input_length) }; + let input_str = String::from_utf8_lossy(input_bytes); + + let Some(host) = parse_host(&input_str, is_opaque) else { + // SAFETY: on_complete is a valid function pointer; ctx is caller-provided. + unsafe { on_complete(ctx, std::ptr::null()) }; + return false; + }; + + let ffi_result = host_to_ffi(Some(&host)); + + // SAFETY: ffi_result borrows from host, which lives until the callback returns. + unsafe { on_complete(ctx, &raw const ffi_result) }; + true + }) +} diff --git a/Libraries/LibURL/Rust/src/url/host.rs b/Libraries/LibURL/Rust/src/url/host.rs index 54113d66ee..3f4e1b88be 100644 --- a/Libraries/LibURL/Rust/src/url/host.rs +++ b/Libraries/LibURL/Rust/src/url/host.rs @@ -521,7 +521,7 @@ fn ends_in_a_number_checker(input: &str) -> bool { } // https://url.spec.whatwg.org/#concept-host-parser -pub(super) fn parse_host(input: &str, is_opaque: bool) -> Option { +pub(crate) fn parse_host(input: &str, is_opaque: bool) -> Option { // 1. If input starts with U+005B ([), then: if input.starts_with('[') { // 1. If input does not end with U+005D (]), IPv6-unclosed validation error, return failure. diff --git a/Libraries/LibURL/Rust/src/url/mod.rs b/Libraries/LibURL/Rust/src/url/mod.rs index 888dfdc082..7d2ec05296 100644 --- a/Libraries/LibURL/Rust/src/url/mod.rs +++ b/Libraries/LibURL/Rust/src/url/mod.rs @@ -12,6 +12,7 @@ mod scheme; mod serialize; mod types; +pub(crate) use self::host::parse_host; pub(crate) use self::scheme::default_port_for_scheme; pub(crate) use self::scheme::is_special_scheme; pub(crate) use self::scheme::special_schemes; diff --git a/Libraries/LibURL/RustIntegration.cpp b/Libraries/LibURL/RustIntegration.cpp index 64fd02be3a..39717e8cf7 100644 --- a/Libraries/LibURL/RustIntegration.cpp +++ b/Libraries/LibURL/RustIntegration.cpp @@ -174,6 +174,32 @@ static FFI::FfiUrlHost host_to_ffi(Optional const& host) return result; } +static Optional host_from_ffi(FFI::FfiUrlHost const& ffi) +{ + if (!ffi.has_host) + return {}; + + switch (ffi.kind) { + case FFI::RustUrlHostKind::String: + return Host(string_from_ffi({ ffi.string_data, ffi.string_length })); + case FFI::RustUrlHostKind::Ipv4: { + u32 const n = (static_cast(ffi.ipv4[0]) << 24) + | (static_cast(ffi.ipv4[1]) << 16) + | (static_cast(ffi.ipv4[2]) << 8) + | static_cast(ffi.ipv4[3]); + return Host(IPv4Address(NetworkOrdered(n))); + } + case FFI::RustUrlHostKind::Ipv6: { + Array pieces; + for (size_t i = 0; i < 8; i++) + pieces[i] = (static_cast(ffi.ipv6[i * 2]) << 8) | ffi.ipv6[(i * 2) + 1]; + return Host(IPv6Address(pieces)); + } + } + + VERIFY_NOT_REACHED(); +} + static Optional url_from_ffi(FFI::RustFfiUrl const& ffi) { URL url; @@ -181,28 +207,8 @@ static Optional url_from_ffi(FFI::RustFfiUrl const& ffi) url.set_username(string_from_ffi(ffi.username)); url.set_password(string_from_ffi(ffi.password)); - if (ffi.host.has_host) { - switch (ffi.host.kind) { - case FFI::RustUrlHostKind::String: - url.set_host(Host(string_from_ffi({ ffi.host.string_data, ffi.host.string_length }))); - break; - case FFI::RustUrlHostKind::Ipv4: { - u32 const n = (static_cast(ffi.host.ipv4[0]) << 24) - | (static_cast(ffi.host.ipv4[1]) << 16) - | (static_cast(ffi.host.ipv4[2]) << 8) - | static_cast(ffi.host.ipv4[3]); - url.set_host(Host(IPv4Address(NetworkOrdered(n)))); - break; - } - case FFI::RustUrlHostKind::Ipv6: { - Array pieces; - for (size_t i = 0; i < 8; i++) - pieces[i] = (static_cast(ffi.host.ipv6[i * 2]) << 8) | ffi.host.ipv6[(i * 2) + 1]; - url.set_host(Host(IPv6Address(pieces))); - break; - } - } - } + if (auto host = host_from_ffi(ffi.host); host.has_value()) + url.set_host(host.release_value()); if (ffi.has_port) url.set_port(ffi.port); @@ -388,6 +394,10 @@ struct ParseCallbackCtx { URL* url_inout; }; +struct HostParseCallbackCtx { + Optional* result; +}; + static void on_basic_parse_complete(void* ctx_ptr, FFI::RustFfiUrl const* ffi_result) { auto* ctx = static_cast(ctx_ptr); @@ -398,6 +408,29 @@ static void on_basic_parse_complete(void* ctx_ptr, FFI::RustFfiUrl const* ffi_re *ctx->url_inout = **ctx->result; } +static void on_parse_host_complete(void* ctx_ptr, FFI::FfiUrlHost const* ffi_result) +{ + auto* ctx = static_cast(ctx_ptr); + if (!ffi_result) + return; + *ctx->result = host_from_ffi(*ffi_result); +} + +Optional parse_host(StringView input, bool is_opaque) +{ + Optional result; + HostParseCallbackCtx ctx { .result = &result }; + bool const did_succeed = FFI::rust_url_parse_host( + reinterpret_cast(input.characters_without_null_termination()), + input.length(), + is_opaque, + &ctx, + on_parse_host_complete); + if (!did_succeed) + return {}; + return result; +} + Optional parse_basic_url(StringView input, Optional base_url, URL* url, Optional state_override, Optional encoding) { auto const state_override_from_cpp = [](Parser::State state) { diff --git a/Libraries/LibURL/RustIntegration.h b/Libraries/LibURL/RustIntegration.h index b8d9752b87..2c21b06dcd 100644 --- a/Libraries/LibURL/RustIntegration.h +++ b/Libraries/LibURL/RustIntegration.h @@ -19,6 +19,7 @@ namespace URL::RustIntegration { Optional parse_basic_url(StringView input, Optional base_url = {}, URL* url = nullptr, Optional state_override = {}, Optional encoding = {}); +Optional parse_host(StringView input, bool is_opaque = false); class URLPattern { public: