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"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cbindgen",
|
"cbindgen",
|
||||||
|
"encoding_rs",
|
||||||
"libregex_rust",
|
"libregex_rust",
|
||||||
"libunicode_rust",
|
"libunicode_rust",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,5 @@ ladybird_lib(LibURL url)
|
||||||
target_link_libraries(LibURL PRIVATE liburl_rust LibUnicode LibTextCodec LibRegex PkgConfig::LIBPSL)
|
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)
|
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)
|
target_compile_definitions(LibURL PRIVATE ENABLE_RUST)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ edition = "2024"
|
||||||
crate-type = ["staticlib"]
|
crate-type = ["staticlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
encoding_rs = "0.8.35"
|
||||||
libregex_rust = { path = "../../LibRegex/Rust" }
|
libregex_rust = { path = "../../LibRegex/Rust" }
|
||||||
libunicode_rust = { path = "../../LibUnicode/Rust" }
|
libunicode_rust = { path = "../../LibUnicode/Rust" }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* 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)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub(crate) enum EncodeItem {
|
pub(crate) enum EncodeItem {
|
||||||
|
|
@ -12,37 +13,6 @@ pub(crate) enum EncodeItem {
|
||||||
Error(u32),
|
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
|
// https://encoding.spec.whatwg.org/#get-an-output-encoding
|
||||||
pub(crate) fn get_output_encoding(encoding: &str) -> &str {
|
pub(crate) fn get_output_encoding(encoding: &str) -> &str {
|
||||||
// 1. If encoding is replacement or UTF-16BE/LE, then return UTF-8.
|
// 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 {
|
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.
|
let mut encoder = encoding.new_encoder();
|
||||||
unsafe {
|
let mut total_read = 0usize;
|
||||||
textcodec_rust_encode(
|
let Some(output_capacity) = encoder.max_buffer_length_from_utf8_without_replacement(input.len()) else {
|
||||||
encoding.as_ptr(),
|
return false;
|
||||||
encoding.len(),
|
};
|
||||||
input.as_ptr(),
|
let mut output = Vec::with_capacity(output_capacity);
|
||||||
input.len(),
|
|
||||||
std::ptr::addr_of_mut!(callbacks) as *mut c_void,
|
loop {
|
||||||
on_encode_byte_with_callbacks,
|
let (result, read) =
|
||||||
on_encode_error_with_callbacks,
|
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");
|
result.push_str("%3B");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
assert!(
|
assert!(did_succeed, "encoding_rs should encode any valid output encoding");
|
||||||
did_succeed,
|
|
||||||
"TextCodec::encode should succeed for a valid output encoding"
|
|
||||||
);
|
|
||||||
|
|
||||||
// 6. Return output.
|
// 6. Return output.
|
||||||
result
|
result
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
#include <AK/StringUtils.h>
|
#include <AK/StringUtils.h>
|
||||||
#include <AK/Utf8View.h>
|
#include <AK/Utf8View.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
#include <LibTextCodec/Encoder.h>
|
|
||||||
#include <LibURL/Parser.h>
|
#include <LibURL/Parser.h>
|
||||||
#include <LibURL/RustFFI.h>
|
#include <LibURL/RustFFI.h>
|
||||||
#include <LibURL/RustIntegration.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
|
Found 12 tests
|
||||||
|
|
||||||
8 Pass
|
12 Pass
|
||||||
4 Fail
|
|
||||||
Pass iso-2022-jp encoder: very basic
|
Pass iso-2022-jp encoder: very basic
|
||||||
Pass iso-2022-jp encoder: basics
|
Pass iso-2022-jp encoder: basics
|
||||||
Pass iso-2022-jp encoder: Katakana
|
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: Roman SO/SI ESC
|
||||||
Pass iso-2022-jp encoder: Katakana SO/SI ESC
|
Pass iso-2022-jp encoder: Katakana SO/SI ESC
|
||||||
Pass iso-2022-jp encoder: jis0208 SO/SI ESC
|
Pass iso-2022-jp encoder: jis0208 SO/SI ESC
|
||||||
Fail iso-2022-jp encoder: U+FFFD
|
Pass iso-2022-jp encoder: U+FFFD
|
||||||
Fail iso-2022-jp encoder: Roman U+FFFD
|
Pass iso-2022-jp encoder: Roman U+FFFD
|
||||||
Fail iso-2022-jp encoder: Katakana U+FFFD
|
Pass iso-2022-jp encoder: Katakana U+FFFD
|
||||||
Fail iso-2022-jp encoder: jis0208 U+FFFD
|
Pass iso-2022-jp encoder: jis0208 U+FFFD
|
||||||
Loading…
Reference in a new issue