diff --git a/src/html5ever/Cargo.lock b/src/html5ever/Cargo.lock index d1646acb1..72445a7c7 100644 --- a/src/html5ever/Cargo.lock +++ b/src/html5ever/Cargo.lock @@ -62,6 +62,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "html5ever" version = "0.39.0" @@ -192,6 +201,7 @@ dependencies = [ "tikv-jemalloc-ctl", "tikv-jemallocator", "typed-arena", + "url", "xml5ever", ] @@ -263,6 +273,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "phf" version = "0.13.1" @@ -504,6 +520,18 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + [[package]] name = "utf-8" version = "0.7.6" diff --git a/src/html5ever/Cargo.toml b/src/html5ever/Cargo.toml index 2cc913cf1..216f21098 100644 --- a/src/html5ever/Cargo.toml +++ b/src/html5ever/Cargo.toml @@ -17,6 +17,7 @@ tikv-jemalloc-ctl = {version = "0.6.1", features = ["stats"]} xml5ever = "0.39.0" encoding_rs = "0.8" idna = "1.1.0" +url = "2.5.8" [profile.release] lto = true diff --git a/src/html5ever/url.rs b/src/html5ever/url.rs index 98a83b606..6c2699e83 100644 --- a/src/html5ever/url.rs +++ b/src/html5ever/url.rs @@ -18,6 +18,7 @@ // IDNA-2008 behavior diverged from the spec. Value-in / value-out: a UTF-8 // host string becomes its punycode form, or an error. +use ::url::Url; use std::os::raw::c_uchar; use std::slice; @@ -81,3 +82,357 @@ pub extern "C" fn lpurl_free(ptr: *mut c_uchar, len: usize) { drop(Box::from_raw(slice)); } } + +#[no_mangle] +pub unsafe extern "C" fn url_parse(ptr: *const c_uchar, len: usize, err: *mut i32) -> *mut Url { + let slice = match str_from(ptr, len) { + Some(s) => s, + None => { + *err = url::ParseError::EmptyHost as i32; + return std::ptr::null_mut(); + } + }; + + match Url::parse(slice) { + Ok(url) => Box::into_raw(Box::new(url)), + Err(e) => { + *err = e as i32; + std::ptr::null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn url_join( + base: *const Url, + ptr: *const c_uchar, + len: usize, + err: *mut i32, +) -> *mut Url { + let base = unsafe { &*base }; + let slice = match str_from(ptr, len) { + Some(s) => s, + None => { + *err = url::ParseError::EmptyHost as i32; + return std::ptr::null_mut(); + } + }; + + match base.join(slice) { + Ok(url) => Box::into_raw(Box::new(url)), + Err(e) => { + *err = e as i32; + std::ptr::null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn url_can_parse(ptr: *const c_uchar, len: usize) -> bool { + let slice = match str_from(ptr, len) { + Some(s) => s, + None => return false, + }; + + Url::parse(slice).is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn url_can_parse_with_base( + base_ptr: *const c_uchar, + base_len: usize, + ptr: *const c_uchar, + len: usize, +) -> bool { + let base_slice = match str_from(base_ptr, base_len) { + Some(s) => s, + None => return false, + }; + let slice = match str_from(ptr, len) { + Some(s) => s, + None => return false, + }; + + match Url::parse(base_slice) { + Ok(url) => url.join(slice).is_ok(), + Err(_) => false, + } +} + +#[no_mangle] +pub unsafe extern "C" fn url_free(url: *mut Url) { + if url.is_null() { + return; + } + drop(Box::from_raw(url)); +} + +#[no_mangle] +pub unsafe extern "C" fn url_to_string( + url: *const Url, + out_ptr: *mut *const c_uchar, + out_len: *mut usize, +) { + let url = unsafe { &*url }; + let s = url.as_str(); + *out_ptr = s.as_ptr(); + *out_len = s.len(); +} + +#[no_mangle] +pub unsafe extern "C" fn url_set_username(url: *mut Url, ptr: *const c_uchar, len: usize) -> i32 { + let url = unsafe { &mut *url }; + let slice = match str_from(ptr, len) { + Some(s) => s, + None => return -1, + }; + + match url.set_username(slice) { + Ok(()) => 0, + Err(()) => -1, + } +} + +#[no_mangle] +pub unsafe extern "C" fn url_get_username( + url: *const Url, + out_ptr: *mut *const c_uchar, + out_len: *mut usize, +) { + let url = unsafe { &*url }; + let username = url.username(); + *out_ptr = username.as_ptr(); + *out_len = username.len(); +} + +#[no_mangle] +pub unsafe extern "C" fn url_set_password(url: *mut Url, ptr: *const c_uchar, len: usize) -> i32 { + let url = unsafe { &mut *url }; + let slice = match str_from(ptr, len) { + Some(s) => s, + None => return -1, + }; + + match url.set_password(Some(slice)) { + Ok(()) => 0, + Err(()) => -1, + } +} + +#[no_mangle] +pub unsafe extern "C" fn url_get_password( + url: *const Url, + out_ptr: *mut *const c_uchar, + out_len: *mut usize, +) -> i32 { + let url = unsafe { &*url }; + match url.password() { + Some(password) => { + *out_ptr = password.as_ptr(); + *out_len = password.len(); + 0 + } + None => -1, + } +} + +#[repr(C)] +pub struct OwnedString { + pub ptr: *mut c_uchar, + pub len: usize, +} + +#[no_mangle] +pub unsafe extern "C" fn free_owned_string(owned: OwnedString) { + if owned.ptr.is_null() || owned.len == 0 { + return; + } + + unsafe { + let slice = std::ptr::slice_from_raw_parts_mut(owned.ptr, owned.len); + drop(Box::from_raw(slice)); + } +} + +#[no_mangle] +pub unsafe extern "C" fn url_get_origin(url: *const Url) -> OwnedString { + let url = unsafe { &*url }; + let origin = url.origin().ascii_serialization(); + let len = origin.len(); + let ptr = Box::into_raw(origin.into_bytes().into_boxed_slice()) as *mut c_uchar; + OwnedString { ptr, len } +} + +#[no_mangle] +pub unsafe extern "C" fn url_set_port(url: *mut Url, port: u16) -> i32 { + let url = unsafe { &mut *url }; + match url.set_port(Some(port)) { + Ok(()) => 0, + Err(()) => -1, + } +} + +#[no_mangle] +pub unsafe extern "C" fn url_get_port(url: *const Url) -> i32 { + let url = unsafe { &*url }; + match url.port() { + Some(port) => port as i32, + None => -1, + } +} + +#[no_mangle] +pub unsafe extern "C" fn url_set_host(url: *mut Url, ptr: *const c_uchar, len: usize) -> i32 { + let url = unsafe { &mut *url }; + + let slice = match str_from(ptr, len) { + Some(s) => s, + None => return -1, + }; + + match url.set_host(Some(slice)) { + Ok(()) => 0, + Err(_) => -1, + } +} + +#[no_mangle] +pub unsafe extern "C" fn url_get_host( + url: *const Url, + out_ptr: *mut *const c_uchar, + out_len: *mut usize, +) -> i32 { + let url = unsafe { &*url }; + + match url.host_str() { + Some(h) => unsafe { + *out_ptr = h.as_ptr(); + *out_len = h.len(); + 0 + }, + None => unsafe { + *out_len = 0; + -1 + }, + } +} + +#[no_mangle] +pub unsafe extern "C" fn url_set_scheme(url: *mut Url, ptr: *const c_uchar, len: usize) -> i32 { + let url = unsafe { &mut *url }; + let slice = match str_from(ptr, len) { + Some(s) => s, + None => return -1, + }; + + match url.set_scheme(slice) { + Ok(()) => 0, + Err(()) => -1, + } +} + +#[no_mangle] +pub unsafe extern "C" fn url_get_scheme( + url: *const Url, + out_ptr: *mut *const c_uchar, + out_len: *mut usize, +) { + let url = unsafe { &*url }; + let scheme = url.scheme(); + *out_ptr = scheme.as_ptr(); + *out_len = scheme.len(); +} + +#[no_mangle] +pub unsafe extern "C" fn url_set_path(url: *mut Url, ptr: *const c_uchar, len: usize) -> i32 { + let url = unsafe { &mut *url }; + let slice = match str_from(ptr, len) { + Some(s) => s, + None => return -1, + }; + + url.set_path(slice); + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn url_get_path( + url: *const Url, + out_ptr: *mut *const c_uchar, + out_len: *mut usize, +) { + let url = unsafe { &*url }; + let path = url.path(); + *out_ptr = path.as_ptr(); + *out_len = path.len(); +} + +#[no_mangle] +pub unsafe extern "C" fn url_set_query(url: *mut Url, ptr: *const c_uchar, len: usize) -> i32 { + let url = unsafe { &mut *url }; + let slice = match str_from(ptr, len) { + Some(s) => s, + None => return -1, + }; + + url.set_query(Some(slice)); + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn url_get_query( + url: *const Url, + out_ptr: *mut *const c_uchar, + out_len: *mut usize, +) -> i32 { + let url = unsafe { &*url }; + match url.query() { + Some(query) => { + *out_ptr = query.as_ptr(); + *out_len = query.len(); + 0 + } + None => -1, + } +} + +#[no_mangle] +pub unsafe extern "C" fn url_set_fragment(url: *mut Url, ptr: *const c_uchar, len: usize) -> i32 { + let url = unsafe { &mut *url }; + let slice = match str_from(ptr, len) { + Some(s) => s, + None => return -1, + }; + + url.set_fragment(Some(slice)); + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn url_get_fragment( + url: *const Url, + out_ptr: *mut *const c_uchar, + out_len: *mut usize, +) -> i32 { + let url = unsafe { &*url }; + match url.fragment() { + Some(fragment) => { + *out_ptr = fragment.as_ptr(); + *out_len = fragment.len(); + 0 + } + None => -1, + } +} + +#[no_mangle] +pub unsafe extern "C" fn url_get_href( + url: *const Url, + out_ptr: *mut *const c_uchar, + out_len: *mut usize, +) { + let url = unsafe { &*url }; + let href = url.as_str(); + *out_ptr = href.as_ptr(); + *out_len = href.len(); +} diff --git a/src/sys/url.zig b/src/sys/url.zig new file mode 100644 index 000000000..e74a511d5 --- /dev/null +++ b/src/sys/url.zig @@ -0,0 +1,71 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! Bindings for Servo's rust-url (https://github.com/servo/rust-url). +//! Check @src/html5ever/url.rs for Rust-side of the bindings. + +const std = @import("std"); +const mem = std.mem; + +pub const Url = anyopaque; + +pub const OwnedString = extern struct { + ptr: [*]const u8, + len: usize, + + pub inline fn deinit(self: OwnedString) void { + return free_owned_string(self); + } + + pub inline fn slice(self: OwnedString) []const u8 { + return self.ptr[0..self.len]; + } +}; + +/// https://docs.rs/url/latest/url/enum.ParseError.html +pub const ParseError = enum(i32) { + EmptyHost, + IdnaError, + InvalidPort, + InvalidIpv4Address, + InvalidIpv6Address, + InvalidDomainCharacter, + RelativeUrlWithoutBase, + RelativeUrlWithCannotBeABaseBase, + SetHostOnCannotBeABaseUrl, + Overflow, +}; + +/// If return value is null, `err` indicates `ParseError`. +pub extern "c" fn url_parse(ptr: [*]const u8, len: usize, err: *i32) ?*Url; +/// If return value is null, `err` indicates `ParseError`. +pub extern "c" fn url_join(base: *const Url, ptr: [*]const u8, len: usize, err: *i32) ?*Url; +pub extern "c" fn url_free(url: *Url) void; +pub extern "c" fn url_can_parse(ptr: [*]const u8, len: usize) bool; +pub extern "c" fn url_can_parse_with_base(base_ptr: [*]const u8, base_len: usize, ptr: [*]const u8, len: usize) bool; +pub extern "c" fn url_to_string(url: *const Url, out_ptr: *[*]const u8, out_len: *usize) void; +pub extern "c" fn url_set_host(url: *Url, ptr: [*]const u8, len: usize) i32; +pub extern "c" fn url_get_host(url: *const Url, out_ptr: *[*]const u8, out_len: *usize) void; +/// This function allocates on Rust-side. +pub extern "c" fn url_get_origin(url: *const Url) OwnedString; +pub extern "c" fn free_owned_string(owned: OwnedString) void; +pub extern "c" fn url_set_port(url: *Url, port: u16) i32; + +extern "c" fn url_get_port(url: *const Url) i32; +pub inline fn urlGetPort(url: *const Url) ?u16 { + const result = url_get_port(url); + if (result < 0) return null; + return @intCast(result); +}