add rust-url dependency and bindings for it

This commit is contained in:
Halil Durak
2026-06-04 20:08:59 +03:00
parent d695ce10c3
commit 3f006bf912
4 changed files with 455 additions and 0 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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();
}

71
src/sys/url.zig Normal file
View File

@@ -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 <https://www.gnu.org/licenses/>.
//! 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);
}