LibUnicode/Rust: Expose IDNA to_ascii to Rust

Add a thin FFI wrapper for Unicode::IDNA::to_ascii.
This commit is contained in:
Shannon Booth 2026-04-05 10:18:52 +02:00 committed by Shannon Booth
parent f985ed83d4
commit 8cc458e73a
3 changed files with 90 additions and 0 deletions

View file

@ -7,6 +7,7 @@
#include <LibUnicode/ICU.h>
#include <LibUnicode/IDNA.h>
#include <LibUnicode/RustFFI.h>
#include <unicode/idna.h>
@ -63,3 +64,27 @@ ErrorOr<String> 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<uint8_t const*>(ascii_bytes.characters_without_null_termination()), ascii_bytes.length());
}
}

View file

@ -0,0 +1,64 @@
/*
* Copyright (c) 2026, Shannon Booth <shannon@serenityos.org>
*
* 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<String>` 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<String>) };
// 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<String> {
let mut result: Option<String> = 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
}

View file

@ -10,3 +10,4 @@ mod rust_allocator;
pub mod calendar;
pub mod character_types;
pub mod idna;