LibURL: Use encoding_rs for form URL encoding
Replace the Rust URL form-encoding callback bridge into C++ TextCodec with a direct encoding_rs encoder. This keeps percent-encode-after-encoding entirely in Rust and removes liburl_rust's dependency on LibTextCodec. It also happens to fix ISO-2022-JP URL encoding of literal U+FFFD. The LibTextCodec reverse lookup treats generated 0xFFFD table holes as real JIS0208 mappings, so literal U+FFFD skipped the encoder-error path. encoding_rs treats U+FFFD as unmappable, so URL encoding emits the required numeric character reference.
This commit is contained in:
parent
3a0f857cc1
commit
250842d36a
7 changed files with 35 additions and 79 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -741,6 +741,7 @@ name = "liburl_rust"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cbindgen",
|
||||
"encoding_rs",
|
||||
"libregex_rust",
|
||||
"libunicode_rust",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -12,5 +12,5 @@ ladybird_lib(LibURL url)
|
|||
target_link_libraries(LibURL PRIVATE liburl_rust LibUnicode LibTextCodec LibRegex PkgConfig::LIBPSL)
|
||||
|
||||
import_rust_crate(MANIFEST_PATH Rust/Cargo.toml CRATE_NAME liburl_rust FEATURES allocator FFI_HEADER RustFFI.h)
|
||||
target_link_libraries(liburl_rust INTERFACE LibUnicode LibTextCodec LibRegex)
|
||||
target_link_libraries(liburl_rust INTERFACE LibUnicode LibRegex)
|
||||
target_compile_definitions(LibURL PRIVATE ENABLE_RUST)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ edition = "2024"
|
|||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
encoding_rs = "0.8.35"
|
||||
libregex_rust = { path = "../../LibRegex/Rust" }
|
||||
libunicode_rust = { path = "../../LibUnicode/Rust" }
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
use std::ffi::c_void;
|
||||
use encoding_rs::EncoderResult;
|
||||
use encoding_rs::Encoding;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum EncodeItem {
|
||||
|
|
@ -12,37 +13,6 @@ pub(crate) enum EncodeItem {
|
|||
Error(u32),
|
||||
}
|
||||
|
||||
type FfiByteFn = unsafe extern "C" fn(*mut c_void, u8);
|
||||
type FfiCodePointFn = unsafe extern "C" fn(*mut c_void, u32);
|
||||
|
||||
unsafe extern "C" {
|
||||
fn textcodec_rust_encode(
|
||||
encoding: *const u8,
|
||||
encoding_length: usize,
|
||||
input: *const u8,
|
||||
input_length: usize,
|
||||
ctx: *mut c_void,
|
||||
on_byte: FfiByteFn,
|
||||
on_error: FfiCodePointFn,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
struct EncodeCallbacks<'a> {
|
||||
on_item: &'a mut dyn FnMut(EncodeItem),
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_encode_byte_with_callbacks(ctx: *mut c_void, byte: u8) {
|
||||
// SAFETY: `ctx` was set to `addr_of_mut!(callbacks)` in `encode_into`.
|
||||
let callbacks = unsafe { &mut *(ctx as *mut EncodeCallbacks<'_>) };
|
||||
(callbacks.on_item)(EncodeItem::Byte(byte));
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_encode_error_with_callbacks(ctx: *mut c_void, error: u32) {
|
||||
// SAFETY: `ctx` was set to `addr_of_mut!(callbacks)` in `encode_into`.
|
||||
let callbacks = unsafe { &mut *(ctx as *mut EncodeCallbacks<'_>) };
|
||||
(callbacks.on_item)(EncodeItem::Error(error));
|
||||
}
|
||||
|
||||
// https://encoding.spec.whatwg.org/#get-an-output-encoding
|
||||
pub(crate) fn get_output_encoding(encoding: &str) -> &str {
|
||||
// 1. If encoding is replacement or UTF-16BE/LE, then return UTF-8.
|
||||
|
|
@ -58,18 +28,30 @@ pub(crate) fn get_output_encoding(encoding: &str) -> &str {
|
|||
}
|
||||
|
||||
pub(crate) fn encode_into(encoding: &str, input: &str, mut on_item: impl FnMut(EncodeItem)) -> bool {
|
||||
let mut callbacks = EncodeCallbacks { on_item: &mut on_item };
|
||||
let Some(encoding) = Encoding::for_label(encoding.as_bytes()) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// SAFETY: `encoding`, `input`, and `callbacks` are valid for the duration of the call.
|
||||
unsafe {
|
||||
textcodec_rust_encode(
|
||||
encoding.as_ptr(),
|
||||
encoding.len(),
|
||||
input.as_ptr(),
|
||||
input.len(),
|
||||
std::ptr::addr_of_mut!(callbacks) as *mut c_void,
|
||||
on_encode_byte_with_callbacks,
|
||||
on_encode_error_with_callbacks,
|
||||
)
|
||||
let mut encoder = encoding.new_encoder();
|
||||
let mut total_read = 0usize;
|
||||
let Some(output_capacity) = encoder.max_buffer_length_from_utf8_without_replacement(input.len()) else {
|
||||
return false;
|
||||
};
|
||||
let mut output = Vec::with_capacity(output_capacity);
|
||||
|
||||
loop {
|
||||
let (result, read) =
|
||||
encoder.encode_from_utf8_to_vec_without_replacement(&input[total_read..], &mut output, true);
|
||||
total_read += read;
|
||||
|
||||
for byte in output.drain(..) {
|
||||
on_item(EncodeItem::Byte(byte));
|
||||
}
|
||||
|
||||
match result {
|
||||
EncoderResult::InputEmpty => return true,
|
||||
EncoderResult::OutputFull => return false,
|
||||
EncoderResult::Unmappable(unmappable) => on_item(EncodeItem::Error(unmappable as u32)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,10 +177,7 @@ pub(super) fn percent_encode_after_encoding(
|
|||
result.push_str("%3B");
|
||||
}
|
||||
});
|
||||
assert!(
|
||||
did_succeed,
|
||||
"TextCodec::encode should succeed for a valid output encoding"
|
||||
);
|
||||
assert!(did_succeed, "encoding_rs should encode any valid output encoding");
|
||||
|
||||
// 6. Return output.
|
||||
result
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
#include <AK/StringUtils.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibTextCodec/Encoder.h>
|
||||
#include <LibURL/Parser.h>
|
||||
#include <LibURL/RustFFI.h>
|
||||
#include <LibURL/RustIntegration.h>
|
||||
|
|
@ -480,26 +479,3 @@ Optional<URL> parse_basic_url(StringView input, Optional<URL const&> base_url, U
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
namespace URL::FFI {
|
||||
|
||||
extern "C" bool textcodec_rust_encode(u8 const* encoding, size_t encoding_length, u8 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<void> {
|
||||
on_byte(ctx, byte);
|
||||
return {};
|
||||
},
|
||||
[&](u32 error) -> ErrorOr<void> {
|
||||
on_error(ctx, error);
|
||||
return {};
|
||||
});
|
||||
return !result.is_error();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ Harness status: OK
|
|||
|
||||
Found 12 tests
|
||||
|
||||
8 Pass
|
||||
4 Fail
|
||||
12 Pass
|
||||
Pass iso-2022-jp encoder: very basic
|
||||
Pass iso-2022-jp encoder: basics
|
||||
Pass iso-2022-jp encoder: Katakana
|
||||
|
|
@ -12,7 +11,7 @@ Pass iso-2022-jp encoder: SO/SI ESC
|
|||
Pass iso-2022-jp encoder: Roman SO/SI ESC
|
||||
Pass iso-2022-jp encoder: Katakana SO/SI ESC
|
||||
Pass iso-2022-jp encoder: jis0208 SO/SI ESC
|
||||
Fail iso-2022-jp encoder: U+FFFD
|
||||
Fail iso-2022-jp encoder: Roman U+FFFD
|
||||
Fail iso-2022-jp encoder: Katakana U+FFFD
|
||||
Fail iso-2022-jp encoder: jis0208 U+FFFD
|
||||
Pass iso-2022-jp encoder: U+FFFD
|
||||
Pass iso-2022-jp encoder: Roman U+FFFD
|
||||
Pass iso-2022-jp encoder: Katakana U+FFFD
|
||||
Pass iso-2022-jp encoder: jis0208 U+FFFD
|
||||
Loading…
Reference in a new issue