diff --git a/Libraries/LibUnicode/IDNA.cpp b/Libraries/LibUnicode/IDNA.cpp index b6859f9de3..a999df9901 100644 --- a/Libraries/LibUnicode/IDNA.cpp +++ b/Libraries/LibUnicode/IDNA.cpp @@ -7,6 +7,7 @@ #include #include +#include #include @@ -63,3 +64,27 @@ ErrorOr to_ascii(Utf8View domain_name, ToAsciiOptions const& options) } } + +namespace Unicode::FFI { + +extern "C" void unicode_rust_idna_to_ascii(uint8_t const* domain, size_t domain_length, ToAsciiOptions const* options, void* ctx, FfiIdnaResultFn on_success) +{ + Unicode::IDNA::ToAsciiOptions const cpp_options { + options->check_hyphens ? Unicode::IDNA::CheckHyphens::Yes : Unicode::IDNA::CheckHyphens::No, + options->check_bidi ? Unicode::IDNA::CheckBidi::Yes : Unicode::IDNA::CheckBidi::No, + options->check_joiners ? Unicode::IDNA::CheckJoiners::Yes : Unicode::IDNA::CheckJoiners::No, + options->use_std3_ascii_rules ? Unicode::IDNA::UseStd3AsciiRules::Yes : Unicode::IDNA::UseStd3AsciiRules::No, + options->transitional_processing ? Unicode::IDNA::TransitionalProcessing::Yes : Unicode::IDNA::TransitionalProcessing::No, + options->verify_dns_length ? Unicode::IDNA::VerifyDnsLength::Yes : Unicode::IDNA::VerifyDnsLength::No, + options->ignore_invalid_punycode ? Unicode::IDNA::IgnoreInvalidPunycode::Yes : Unicode::IDNA::IgnoreInvalidPunycode::No, + }; + + auto result = Unicode::IDNA::to_ascii(Utf8View { StringView { domain, domain_length } }, cpp_options); + if (result.is_error()) + return; + + auto const ascii_bytes = result.value().bytes_as_string_view(); + on_success(ctx, reinterpret_cast(ascii_bytes.characters_without_null_termination()), ascii_bytes.length()); +} + +} diff --git a/Libraries/LibUnicode/Rust/src/idna.rs b/Libraries/LibUnicode/Rust/src/idna.rs new file mode 100644 index 0000000000..2ddcd8e973 --- /dev/null +++ b/Libraries/LibUnicode/Rust/src/idna.rs @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2026, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +//! Rust bindings for the LibUnicode IDNA API. + +use std::ffi::c_void; + +/// Options controlling IDNA `to_ascii` processing (C++ `UnicodeRustToAsciiOptions`). +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ToAsciiOptions { + pub check_hyphens: bool, + pub check_bidi: bool, + pub check_joiners: bool, + pub use_std3_ascii_rules: bool, + pub transitional_processing: bool, + pub verify_dns_length: bool, + pub ignore_invalid_punycode: bool, +} + +type FfiIdnaResultFn = unsafe extern "C" fn(*mut c_void, *const u8, usize); + +unsafe extern "C" { + fn unicode_rust_idna_to_ascii( + domain: *const u8, + domain_length: usize, + options: *const ToAsciiOptions, + ctx: *mut c_void, + on_success: FfiIdnaResultFn, + ); +} + +/// Called by C++ on success with the ASCII result bytes. +/// +/// # Safety +/// `ctx` must be a valid `*mut Option` live for the duration of the +/// enclosing `idna_to_ascii` call. `data` must point to `len` valid ASCII bytes. +unsafe extern "C" fn on_idna_result(ctx: *mut c_void, data: *const u8, len: usize) { + // SAFETY: `ctx` was set to `addr_of_mut!(result)` in `idna_to_ascii`. + let out = unsafe { &mut *(ctx as *mut Option) }; + // SAFETY: `data` is valid for `len` bytes; IDNA to_ascii guarantees ASCII output. + let bytes = unsafe { std::slice::from_raw_parts(data, len) }; + debug_assert!(bytes.is_ascii(), "unicode_rust_idna_to_ascii must return ASCII"); + // SAFETY: ASCII is a strict subset of UTF-8. + *out = Some(unsafe { String::from_utf8_unchecked(bytes.to_vec()) }); +} + +pub fn idna_to_ascii(domain: &str, options: ToAsciiOptions) -> Option { + let mut result: Option = None; + // SAFETY: `domain` is valid for its length; `options` and `result` are valid for the call. + unsafe { + unicode_rust_idna_to_ascii( + domain.as_ptr(), + domain.len(), + &raw const options, + std::ptr::addr_of_mut!(result) as *mut c_void, + on_idna_result, + ); + } + result +} diff --git a/Libraries/LibUnicode/Rust/src/lib.rs b/Libraries/LibUnicode/Rust/src/lib.rs index 9c9ebfe57a..d55494e20a 100644 --- a/Libraries/LibUnicode/Rust/src/lib.rs +++ b/Libraries/LibUnicode/Rust/src/lib.rs @@ -10,3 +10,4 @@ mod rust_allocator; pub mod calendar; pub mod character_types; +pub mod idna;