/* * Copyright (c) 2026, the Ladybird developers. * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include namespace URL::RustIntegration { static String string_from_ffi(FFI::RustUrlByteSlice slice) { return String::from_ascii_without_validation({ reinterpret_cast(slice.data), slice.length }); } static FFI::FfiUrlHost host_to_ffi(Optional const& host) { FFI::FfiUrlHost result {}; if (!host.has_value()) { result.has_host = false; return result; } result.has_host = true; host->value().visit( [&](IPv4Address const& addr) { result.kind = FFI::RustUrlHostKind::Ipv4; u32 const n = addr.to_u32(); result.ipv4[0] = static_cast(n >> 24); result.ipv4[1] = static_cast(n >> 16); result.ipv4[2] = static_cast(n >> 8); result.ipv4[3] = static_cast(n); }, [&](IPv6Address const& addr) { result.kind = FFI::RustUrlHostKind::Ipv6; for (size_t i = 0; i < 8; i++) { u16 piece = addr[i]; result.ipv6[i * 2] = static_cast(piece >> 8); result.ipv6[(i * 2) + 1] = static_cast(piece & 0xff); } }, [&](String const& str) { result.kind = FFI::RustUrlHostKind::String; result.string_data = reinterpret_cast(str.bytes().data()); result.string_length = str.bytes().size(); }); return result; } static Optional url_from_ffi(FFI::RustFfiUrl const& ffi) { URL url; url.set_scheme(string_from_ffi(ffi.scheme)); 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 (ffi.has_port) url.set_port(ffi.port); else url.set_port({}); url.set_has_an_opaque_path(ffi.has_opaque_path); Vector paths; if (ffi.path_segments) { paths.ensure_capacity(ffi.path_segment_count); for (size_t i = 0; i < ffi.path_segment_count; i++) paths.unchecked_append(string_from_ffi(ffi.path_segments[i])); } url.set_raw_paths(move(paths)); if (ffi.has_query) url.set_query(string_from_ffi(ffi.query)); else url.set_query({}); if (ffi.has_fragment) url.set_fragment(string_from_ffi(ffi.fragment)); else url.set_fragment({}); return url; } struct UrlFfiStorage { Vector path_segments; FFI::RustFfiUrl ffi_url {}; }; static UrlFfiStorage url_to_ffi(URL const& url) { UrlFfiStorage storage; storage.ffi_url.scheme = { reinterpret_cast(url.scheme().bytes().data()), url.scheme().bytes().size() }; storage.ffi_url.username = { reinterpret_cast(url.username().bytes().data()), url.username().bytes().size() }; storage.ffi_url.password = { reinterpret_cast(url.password().bytes().data()), url.password().bytes().size() }; storage.ffi_url.host = host_to_ffi(url.host()); storage.ffi_url.has_port = url.port().has_value(); storage.ffi_url.port = url.port().value_or(0); storage.path_segments.ensure_capacity(url.paths().size()); for (auto const& segment : url.paths()) { storage.path_segments.unchecked_append({ reinterpret_cast(segment.bytes().data()), segment.bytes().size(), }); } storage.ffi_url.path_segments = storage.path_segments.data(); storage.ffi_url.path_segment_count = storage.path_segments.size(); storage.ffi_url.has_opaque_path = url.has_an_opaque_path(); storage.ffi_url.has_query = url.query().has_value(); if (url.query().has_value()) storage.ffi_url.query = { reinterpret_cast(url.query()->bytes().data()), url.query()->bytes().size() }; storage.ffi_url.has_fragment = url.fragment().has_value(); if (url.fragment().has_value()) storage.ffi_url.fragment = { reinterpret_cast(url.fragment()->bytes().data()), url.fragment()->bytes().size() }; return storage; } struct ParseCallbackCtx { Optional* result; URL* url_inout; }; static void on_basic_parse_complete(void* ctx_ptr, FFI::RustFfiUrl const* ffi_result) { auto* ctx = static_cast(ctx_ptr); if (!ffi_result) return; *ctx->result = url_from_ffi(*ffi_result); if (ctx->url_inout && ctx->result->has_value()) *ctx->url_inout = **ctx->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) { return static_cast(to_underlying(state)); }; Optional base_storage; if (base_url.has_value()) base_storage = url_to_ffi(*base_url); Optional url_storage; if (url) url_storage = url_to_ffi(*url); FFI::RustBasicParseOptions options { .has_base_url = base_url.has_value(), .has_url = url != nullptr, .base_url = base_storage.has_value() ? base_storage->ffi_url : FFI::RustFfiUrl {}, .url = url_storage.has_value() ? url_storage->ffi_url : FFI::RustFfiUrl {}, .has_state_override = state_override.has_value(), .state_override = state_override.map(state_override_from_cpp).value_or(FFI::State::SchemeStart), .encoding = { reinterpret_cast(encoding.has_value() ? encoding->characters_without_null_termination() : nullptr), encoding.value_or(""sv).length(), }, }; Optional result; ParseCallbackCtx ctx { .result = &result, .url_inout = url }; bool const did_succeed = rust_url_basic_parse( reinterpret_cast(input.characters_without_null_termination()), input.length(), &options, &ctx, on_basic_parse_complete); if (!did_succeed) return {}; return result; } } namespace URL::FFI { extern "C" bool textcodec_rust_encode(uint8_t const* encoding, size_t encoding_length, uint8_t const* input, size_t input_length, void* ctx, FfiByteFn on_byte, FfiCodePointFn on_error) { auto encoder = TextCodec::encoder_for(StringView { encoding, encoding_length }); if (!encoder.has_value()) return false; auto result = encoder->process( Utf8View { StringView { input, input_length } }, [&](u8 byte) -> ErrorOr { on_byte(ctx, byte); return {}; }, [&](u32 error) -> ErrorOr { on_error(ctx, error); return {}; }); return !result.is_error(); } }