fix: systematic byte fidelity for config strings and RV7 gaps
Resolves the 9 gap groups in docs/rewrite/notes/review-rv7-codex.md and
audits every string-typed config field against its consumers
(field -> consumers -> decode status table in final-Z9.md):
- Windows EnvMap keys use a port of Zig's nls.zig uppercase table
(upcaseW); Windows locale init follows Zig's chain (setlocale +
_putenv_s); malformed WTF-8 now fails process launch conversions like
Zig instead of substituting U+FFFD; kitty file paths convert WTF-8.
- bash --rcfile/--init-file, configured TERM and inherited GTK LANG
keep their original bytes.
- C API clipboard completion and ghostty_surface_binding_action take
bytes; complete_clipboard_request is byte-typed through the core.
- text:/csi:/esc: bindings, enquiry-response, input = path:, embedded
initial_input, search needles, titles, link regexes, clipboard
codepoint map, command palette text and GTK font/namespace values
decode the escape encoding before use.
Remaining by design: D-Bus a{ss} values and +new-window arguments are
UTF-8 by D-Bus type. C API key/preedit text follows separately.
Test Plan:
- cargo test --workspace
- just check-macos (both), just check-windows, just check-wasm
This commit is contained in:
1 parent
ffc91c31e4
commit
af094276f8
40 files changed
+986
-167
No files matched your search
@@ -12,20 +12,6 @@ const DELIMITER: char = ';';
|
||||
#[cfg(not(windows))]
|
||||
const DELIMITER: char = ':';
|
||||
|
||||
/// Uppercase one UTF-16 code unit like Windows' `RtlUpcaseUnicodeChar`
|
||||
/// (Unicode simple case mapping, per unit): surrogate halves and characters
|
||||
/// whose uppercase is not a single BMP code unit are unchanged.
|
||||
fn upcase_unit(u: u16) -> u16 {
|
||||
let Some(c) = char::from_u32(u32::from(u)) else {
|
||||
return u; // lone surrogate half
|
||||
};
|
||||
let mut up = c.to_uppercase();
|
||||
match (up.next(), up.next()) {
|
||||
(Some(m), None) => u16::try_from(u32::from(m)).unwrap_or(u),
|
||||
_ => u,
|
||||
}
|
||||
}
|
||||
|
||||
/// The comparison form of an environment key on Windows: its WTF-16 code
|
||||
/// units, uppercased per unit. Never lossy: distinct lone surrogates stay
|
||||
/// distinct.
|
||||
@@ -33,7 +19,7 @@ fn upcase_unit(u: u16) -> u16 {
|
||||
fn windows_key(key: &OsStr) -> Vec<u16> {
|
||||
crate::osbytes::wtf8_to_wide(&crate::osbytes::os_bytes(key))
|
||||
.into_iter()
|
||||
.map(upcase_unit)
|
||||
.map(crate::windows_nls::upcase_w)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -400,20 +386,26 @@ mod tests {
|
||||
|
||||
#[cfg(test)]
|
||||
mod windows_key_tests {
|
||||
#[cfg(unix)]
|
||||
use super::*;
|
||||
|
||||
fn key(units: &[u16]) -> Vec<u16> {
|
||||
units.iter().copied().map(upcase_unit).collect()
|
||||
units
|
||||
.iter()
|
||||
.copied()
|
||||
.map(crate::windows_nls::upcase_w)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upcase_is_unicode_simple_per_unit() {
|
||||
assert_eq!(upcase_unit(u16::from(b'a')), u16::from(b'A'));
|
||||
assert_eq!(upcase_unit(0x00E9), 0x00C9); // e acute
|
||||
assert_eq!(upcase_unit(0x03C9), 0x03A9); // omega
|
||||
assert_eq!(upcase_unit(0x00DF), 0x00DF); // sharp s has no simple upper
|
||||
assert_eq!(upcase_unit(0xD800), 0xD800);
|
||||
assert_eq!(upcase_unit(0xDFFF), 0xDFFF);
|
||||
fn keys_use_the_windows_table_not_unicode_mappings() {
|
||||
// U+1F80 and U+1F88 are the same key under Windows/Zig's table even
|
||||
// though Unicode's uppercase of U+1F80 is a two-unit sequence.
|
||||
assert_eq!(key(&[0x1F80]), key(&[0x1F88]));
|
||||
// The Georgian letters gained uppercase after the table froze.
|
||||
assert_ne!(key(&[0x10D0]), key(&[0x1C90]));
|
||||
// U+00DF stays distinct from "SS" (and from U+1E9E).
|
||||
assert_ne!(key(&[0xDF]), key(&[0x53, 0x53]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -111,6 +111,7 @@ pub mod uri;
|
||||
pub mod wasm;
|
||||
#[cfg(windows)]
|
||||
pub mod windows;
|
||||
pub mod windows_nls;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod unsupported;
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
//! comments): `setlocale(LC_ALL, "")`, then with `LANG` unset, then
|
||||
//! `en_US.UTF-8` (exported as `LANG`). On Darwin (`target_vendor = "apple"`)
|
||||
//! `LANG`/`LANGUAGE` are additionally pre-populated from Cocoa when unset.
|
||||
//! Other targets (Windows, wasm) have no `setlocale` handling.
|
||||
//! Windows runs the same chain against the C runtime: `setlocale` (with
|
||||
//! Zig's literal `LC_ALL = 6`, which the UCRT rejects, so the chain proceeds
|
||||
//! exactly as it does in Zig) and `_putenv_s` for the `LANG` updates.
|
||||
//! wasm has no `setlocale` handling.
|
||||
|
||||
/// The locale Ghostty falls back to (and exports as `LANG`) when the
|
||||
/// environment's locale can't be set.
|
||||
@@ -14,14 +17,14 @@ pub const FALLBACK_LOCALE: &str = "en_US.UTF-8";
|
||||
/// Ensure that the locale is set (`ensureLocale`).
|
||||
/// Returns `Some("en_US.UTF-8")` if we had to fall back to it (and exported
|
||||
/// it as `LANG`), otherwise `None`.
|
||||
#[cfg(unix)]
|
||||
#[cfg(any(unix, windows))]
|
||||
pub fn ensure_locale() -> Option<String> {
|
||||
posix::ensure_locale()
|
||||
}
|
||||
|
||||
/// Ensure that the locale is set: nothing to do on targets without libc
|
||||
/// locales (Windows, wasm).
|
||||
#[cfg(not(unix))]
|
||||
/// Ensure that the locale is set: nothing to do on targets without a C
|
||||
/// runtime locale (wasm).
|
||||
#[cfg(not(any(unix, windows)))]
|
||||
pub fn ensure_locale() -> Option<String> {
|
||||
None
|
||||
}
|
||||
@@ -53,15 +56,52 @@ pub fn preferred_language_value<S: AsRef<str>>(preferred: &[S]) -> Option<String
|
||||
(!out.is_empty()).then_some(out)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(any(unix, windows))]
|
||||
#[allow(unsafe_code)]
|
||||
mod posix {
|
||||
#[cfg(windows)]
|
||||
use std::ffi::c_char;
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
use super::FALLBACK_LOCALE;
|
||||
#[cfg(target_vendor = "apple")]
|
||||
use super::{format_lang, preferred_language_value};
|
||||
|
||||
/// Zig's `LC_ALL` constant (`os/locale.zig`, "from locale.h"). It is
|
||||
/// the value the Zig code passes on every platform, Windows included.
|
||||
#[cfg(windows)]
|
||||
const LC_ALL: i32 = 6;
|
||||
|
||||
#[cfg(windows)]
|
||||
unsafe extern "C" {
|
||||
fn setlocale(category: i32, locale: *const c_char) -> *mut c_char;
|
||||
fn _putenv_s(name: *const c_char, value: *const c_char) -> i32;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn setlocale_all(locale: &str) -> Option<String> {
|
||||
let c = CString::new(locale).ok()?;
|
||||
// SAFETY: valid NUL-terminated string; the C runtime returns a
|
||||
// pointer to a static string (or null) which we copy immediately.
|
||||
unsafe {
|
||||
let r = setlocale(LC_ALL, c.as_ptr());
|
||||
(!r.is_null()).then(|| CStr::from_ptr(r).to_string_lossy().into_owned())
|
||||
}
|
||||
}
|
||||
|
||||
/// Zig's Windows `setenv`/`unsetenv`: `_putenv_s` (an empty value
|
||||
/// removes the variable).
|
||||
#[cfg(windows)]
|
||||
fn putenv_s(key: &str, value: &str) {
|
||||
let (Ok(k), Ok(v)) = (CString::new(key), CString::new(value)) else {
|
||||
return;
|
||||
};
|
||||
// SAFETY: both pointers are valid NUL-terminated strings that the
|
||||
// C runtime copies; called during early startup like the Zig code.
|
||||
unsafe { _putenv_s(k.as_ptr(), v.as_ptr()) };
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn setlocale_all(locale: &str) -> Option<String> {
|
||||
let c = CString::new(locale).ok()?;
|
||||
// SAFETY: valid NUL-terminated string; setlocale returns a pointer to
|
||||
@@ -72,12 +112,24 @@ mod posix {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn set_env(key: &str, value: &str) {
|
||||
putenv_s(key, value);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn unset_env(key: &str) {
|
||||
putenv_s(key, "");
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn set_env(key: &str, value: &str) {
|
||||
// SAFETY: called during early process startup (locale setup) before
|
||||
// other threads read the environment, mirroring the Zig code.
|
||||
unsafe { std::env::set_var(key, value) };
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn unset_env(key: &str) {
|
||||
// SAFETY: see `set_env`.
|
||||
unsafe { std::env::remove_var(key) };
|
||||
|
||||
@@ -174,6 +174,28 @@ pub fn wtf8_to_wide(b: &[u8]) -> Vec<u16> {
|
||||
out
|
||||
}
|
||||
|
||||
/// The bytes are not well-formed WTF-8 (Zig: `error.InvalidWtf8` /
|
||||
/// `error.InvalidUtf8` from `std.unicode.utf8ToUtf16Le*`).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||
#[error("invalid wtf-8")]
|
||||
pub struct InvalidWtf8;
|
||||
|
||||
/// Strict [`wtf8_to_wide`]: malformed input is an error instead of U+FFFD.
|
||||
/// Lone surrogates encoded as 3-byte sequences are accepted (WTF-8). This is
|
||||
/// the conversion used where Zig's is fallible (process launch).
|
||||
pub fn try_wtf8_to_wide(b: &[u8]) -> Result<Vec<u16>, InvalidWtf8> {
|
||||
let wide = wtf8_to_wide(b);
|
||||
// The lossy decoder yields U+FFFD only for malformed bytes; a genuine
|
||||
// U+FFFD in the input is the 3-byte sequence EF BF BD. Re-encoding is
|
||||
// the cheapest exact check: it reproduces the input iff it was
|
||||
// well-formed.
|
||||
if wide_to_wtf8(&wide) == b {
|
||||
Ok(wide)
|
||||
} else {
|
||||
Err(InvalidWtf8)
|
||||
}
|
||||
}
|
||||
|
||||
/// OS string to reversible `String` (see the module docs). Lossless on every
|
||||
/// platform (Windows goes through WTF-8).
|
||||
pub fn os_to_config_string(s: &OsStr) -> String {
|
||||
@@ -187,6 +209,14 @@ pub fn valid_str_to_config_string(s: &str) -> String {
|
||||
bytes_to_config_string(s.as_bytes())
|
||||
}
|
||||
|
||||
/// Decode a config string for a UI-text boundary that requires UTF-8 (window
|
||||
/// titles, CSS font names, toolkit namespaces): the original bytes, with
|
||||
/// anything that is not valid UTF-8 replaced by U+FFFD. A literal `U+FDD0`
|
||||
/// in the text is restored (not shown as the doubled escape).
|
||||
pub fn config_str_to_text(s: &str) -> String {
|
||||
String::from_utf8_lossy(&config_str_to_bytes(s)).into_owned()
|
||||
}
|
||||
|
||||
/// Inverse of [`os_to_config_string`].
|
||||
pub fn config_str_to_os(s: &str) -> OsString {
|
||||
os_from_bytes(config_str_to_bytes(s))
|
||||
@@ -286,6 +316,33 @@ impl EncodedOs {
|
||||
wtf8_to_wide(&self.to_bytes())
|
||||
}
|
||||
|
||||
/// Strict [`EncodedOs::to_wide`] for process launch: malformed WTF-8 is
|
||||
/// an error (Zig's `utf8ToUtf16Le*` fail) instead of becoming U+FFFD.
|
||||
pub fn try_to_wide(&self) -> Result<Vec<u16>, InvalidWtf8> {
|
||||
try_wtf8_to_wide(&self.to_bytes())
|
||||
}
|
||||
|
||||
/// [`EncodedOs::try_to_wide`] plus a trailing NUL.
|
||||
pub fn try_to_wide_nul(&self) -> Result<Vec<u16>, InvalidWtf8> {
|
||||
let mut w = self.try_to_wide()?;
|
||||
w.push(0);
|
||||
Ok(w)
|
||||
}
|
||||
|
||||
/// Strict [`EncodedOs::to_os`]: on Windows malformed WTF-8 is an error;
|
||||
/// elsewhere every byte string is a valid OS string.
|
||||
pub fn try_to_os(&self) -> Result<OsString, InvalidWtf8> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
Ok(OsString::from_wide(&self.try_to_wide()?))
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
Ok(self.to_os())
|
||||
}
|
||||
}
|
||||
|
||||
/// [`EncodedOs::to_wide`] plus a trailing NUL.
|
||||
#[must_use]
|
||||
pub fn to_wide_nul(&self) -> Vec<u16> {
|
||||
@@ -508,6 +565,39 @@ mod tests {
|
||||
assert_eq!(wtf8_to_wide(&config_str_to_bytes(&s)), wide);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_str_to_text_restores_literal_escape_char() {
|
||||
assert_eq!(
|
||||
config_str_to_text(&valid_str_to_config_string("a\u{fdd0}b")),
|
||||
"a\u{fdd0}b"
|
||||
);
|
||||
assert_eq!(
|
||||
config_str_to_text(&bytes_to_config_string(b"a\xffb")),
|
||||
"a\u{fffd}b"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_wtf8_rejects_malformed_input() {
|
||||
assert_eq!(try_wtf8_to_wide(b"a\xffb"), Err(InvalidWtf8));
|
||||
assert_eq!(try_wtf8_to_wide(b"\xe0\x80\x80"), Err(InvalidWtf8));
|
||||
assert_eq!(try_wtf8_to_wide(b"\xc3"), Err(InvalidWtf8));
|
||||
// A real U+FFFD and lone surrogates are well-formed.
|
||||
assert_eq!(try_wtf8_to_wide("\u{FFFD}".as_bytes()), Ok(vec![0xFFFD]));
|
||||
assert_eq!(try_wtf8_to_wide(b"\xed\xa0\x80"), Ok(vec![0xD800]));
|
||||
assert_eq!(
|
||||
try_wtf8_to_wide("\u{e9}\u{1F600}".as_bytes()),
|
||||
Ok(vec![0xE9, 0xD83D, 0xDE00])
|
||||
);
|
||||
let bad = EncodedOs::from_bytes(b"C:\\\xff");
|
||||
assert_eq!(bad.try_to_wide(), Err(InvalidWtf8));
|
||||
assert_eq!(bad.try_to_wide_nul(), Err(InvalidWtf8));
|
||||
// The lossy form still substitutes (non-launch uses).
|
||||
assert_eq!(bad.to_wide().last(), Some(&0xFFFD));
|
||||
let ok = EncodedOs::from_plain_text("C:\\x");
|
||||
assert_eq!(ok.try_to_wide_nul().unwrap().last(), Some(&0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wtf8_invalid_bytes_become_replacement() {
|
||||
assert_eq!(wtf8_to_wide(b"a\xffb"), vec![0x61, 0xFFFD, 0x62]);
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
//! Windows' per-code-unit uppercase mapping (`RtlUpcaseUnicodeChar`), a port
|
||||
//! of `std/os/windows/nls.zig` (`upcaseW` and its `uppercase_table`).
|
||||
//!
|
||||
//! The table is the locale-independent `l_intl.nls` data, unchanged since
|
||||
//! Windows 8.1 and therefore out of sync with the Unicode standard. Zig's
|
||||
//! `EnvMap` hashes and compares Windows environment keys with it, so this is
|
||||
//! what decides whether two keys name the same variable.
|
||||
|
||||
/// This corresponds to the uppercase table within the locale-independent
|
||||
/// l_intl.nls data (found at system32\l_intl.nls).
|
||||
const UPPERCASE_TABLE: [u16; 2544] = [
|
||||
272, 288, 304, 320, 336, 352, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 368, 384, 400, 256, 416, 256, 256, 432, 256,
|
||||
256, 256, 256, 256, 256, 256, 448, 464, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 480, 496, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 512, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528,
|
||||
528, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528, 544, 560, 528, 528, 528, 576, 528,
|
||||
528, 592, 608, 624, 640, 656, 672, 688, 704, 720, 736, 752, 768, 784, 800, 816, 832, 848, 864,
|
||||
880, 896, 912, 928, 944, 960, 976, 992, 1008, 1024, 528, 528, 528, 528, 528, 528, 528, 528,
|
||||
528, 528, 1040, 528, 528, 1056, 528, 528, 1072, 1088, 1104, 1120, 1136, 1152, 528, 528, 528,
|
||||
1168, 1184, 1200, 1216, 1232, 1248, 1264, 1280, 1296, 1312, 1328, 1344, 1360, 1376, 1392, 1408,
|
||||
528, 528, 528, 1424, 1440, 1456, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528,
|
||||
528, 528, 1472, 528, 528, 528, 528, 528, 528, 528, 528, 1488, 1504, 1520, 1536, 1552, 1568,
|
||||
1584, 1600, 1616, 1632, 1648, 1664, 1680, 1696, 1712, 1728, 1744, 1760, 1776, 1792, 1808, 1824,
|
||||
1840, 1856, 1872, 1888, 1904, 1920, 1936, 1952, 1968, 1984, 528, 528, 528, 528, 2000, 528, 528,
|
||||
2016, 2032, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528,
|
||||
528, 528, 528, 528, 2048, 2064, 528, 528, 528, 528, 2080, 2096, 2112, 2128, 2144, 2160, 2176,
|
||||
2192, 2208, 2224, 2240, 2256, 528, 2272, 2288, 2304, 528, 528, 528, 528, 528, 528, 528, 528,
|
||||
528, 528, 528, 528, 528, 528, 528, 528, 528, 2320, 2336, 2352, 528, 2368, 2384, 528, 528, 528,
|
||||
528, 528, 528, 528, 528, 2400, 2416, 2432, 2448, 2464, 2480, 2496, 528, 528, 528, 528, 528,
|
||||
528, 528, 528, 528, 528, 528, 2512, 2528, 528, 528, 528, 528, 528, 528, 528, 528, 528, 528, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65504, 65504, 65504, 65504, 65504, 65504,
|
||||
65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504,
|
||||
65504, 65504, 65504, 65504, 65504, 65504, 65504, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504,
|
||||
65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 0, 65504,
|
||||
65504, 65504, 65504, 65504, 65504, 65504, 121, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 0, 0, 65535, 0, 65535, 0, 65535, 0, 0, 65535, 0, 65535, 0, 65535, 0, 65535,
|
||||
0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 195, 0, 0, 65535, 0, 65535, 0, 0, 65535, 0, 0, 0, 65535, 0, 0, 0,
|
||||
0, 0, 65535, 0, 0, 97, 0, 0, 0, 65535, 163, 0, 0, 0, 130, 0, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
0, 65535, 0, 0, 0, 0, 65535, 0, 0, 65535, 0, 0, 0, 65535, 0, 65535, 0, 0, 65535, 0, 0, 0,
|
||||
65535, 0, 56, 0, 0, 0, 0, 0, 0, 65534, 0, 0, 65534, 0, 0, 65534, 0, 65535, 0, 65535, 0, 65535,
|
||||
0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 65457, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 0, 0, 65534, 0, 65535, 0, 0, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 0, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 0, 0, 0, 0, 0, 0, 0, 65535, 0, 0, 0, 0, 0, 65535, 0, 0, 0, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 10783, 10780, 0, 65326, 65330, 0, 65331, 65331, 0, 65334,
|
||||
0, 65333, 0, 0, 0, 0, 65331, 0, 0, 65329, 0, 0, 0, 0, 65327, 65325, 0, 10743, 0, 0, 0, 65325,
|
||||
0, 10749, 65323, 0, 0, 65322, 0, 0, 0, 0, 0, 0, 0, 10727, 0, 0, 65318, 0, 0, 65318, 0, 0, 0, 0,
|
||||
65318, 65467, 65319, 65319, 65465, 0, 0, 0, 0, 0, 65317, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65535, 0, 65535, 0, 0, 0, 65535, 0, 0, 0,
|
||||
130, 130, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65498, 65499, 65499, 65499, 0, 65504,
|
||||
65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504,
|
||||
65504, 65504, 65504, 0, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65472,
|
||||
65473, 65473, 0, 0, 0, 0, 0, 0, 0, 0, 65528, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535,
|
||||
0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 0, 7, 0, 0, 0, 0, 0,
|
||||
65535, 0, 0, 65535, 0, 0, 0, 0, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504,
|
||||
65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504,
|
||||
65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65456, 65456, 65456,
|
||||
65456, 65456, 65456, 65456, 65456, 65456, 65456, 65456, 65456, 65456, 65456, 65456, 65456, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535,
|
||||
0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 65521, 0, 65535, 0, 65535,
|
||||
0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65488, 65488,
|
||||
65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488,
|
||||
65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488,
|
||||
65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35332, 0, 0, 0, 3814, 0, 0, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 8, 8, 8, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
8, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
8, 8, 8, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 8, 0, 8, 0, 8, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
74, 74, 86, 86, 86, 86, 100, 100, 128, 128, 112, 112, 126, 126, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 0, 0, 0, 7, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 65508, 0, 65520, 65520, 65520, 65520, 65520, 65520, 65520, 65520, 65520,
|
||||
65520, 65520, 65520, 65520, 65520, 65520, 65520, 0, 0, 0, 0, 65535, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 65510, 65510, 65510, 65510, 65510, 65510, 65510, 65510, 65510, 65510, 65510, 65510,
|
||||
65510, 65510, 65510, 65510, 65510, 65510, 65510, 65510, 65510, 65510, 65510, 65510, 65510,
|
||||
65510, 0, 0, 0, 0, 0, 0, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488,
|
||||
65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488,
|
||||
65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488,
|
||||
65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 65488, 0, 0, 65535, 0, 0,
|
||||
0, 54741, 54744, 0, 65535, 0, 65535, 0, 65535, 0, 0, 0, 0, 0, 0, 65535, 0, 0, 65535, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 58272, 58272, 58272, 58272, 58272, 58272, 58272, 58272, 58272, 58272, 58272, 58272,
|
||||
58272, 58272, 58272, 58272, 58272, 58272, 58272, 58272, 58272, 58272, 58272, 58272, 58272,
|
||||
58272, 58272, 58272, 58272, 58272, 58272, 58272, 58272, 58272, 58272, 58272, 58272, 58272, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 0, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 0, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 0, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0,
|
||||
65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 65535, 0, 65535, 0, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 0, 0, 0, 65535, 0,
|
||||
0, 0, 0, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504,
|
||||
65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504, 65504,
|
||||
65504, 0, 0, 0, 0, 0,
|
||||
];
|
||||
|
||||
/// Cross-platform implementation of `ntdll.RtlUpcaseUnicodeChar`.
|
||||
/// Transforms the UTF-16 code unit in `c` to its uppercased version if there
|
||||
/// is one. Otherwise, returns `c` unmodified.
|
||||
#[must_use]
|
||||
pub fn upcase_w(c: u16) -> u16 {
|
||||
if c < u16::from(b'a') {
|
||||
return c;
|
||||
}
|
||||
if c <= u16::from(b'z') {
|
||||
return c - (u16::from(b'a') - u16::from(b'A'));
|
||||
}
|
||||
if c >= 0xC0 {
|
||||
let mut offset: u16 = 0;
|
||||
|
||||
offset = offset.wrapping_add(u16::from((c >> 8) as u8));
|
||||
offset = UPPERCASE_TABLE[usize::from(offset)];
|
||||
offset = offset.wrapping_add((c >> 4) & 0xF);
|
||||
offset = UPPERCASE_TABLE[usize::from(offset)];
|
||||
offset = offset.wrapping_add(c & 0xF);
|
||||
offset = UPPERCASE_TABLE[usize::from(offset)];
|
||||
|
||||
return c.wrapping_add(offset);
|
||||
}
|
||||
c
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::upcase_w;
|
||||
|
||||
#[test]
|
||||
fn ascii_and_latin() {
|
||||
assert_eq!(upcase_w(u16::from(b'a')), u16::from(b'A'));
|
||||
assert_eq!(upcase_w(u16::from(b'Z')), u16::from(b'Z'));
|
||||
assert_eq!(upcase_w(u16::from(b'_')), u16::from(b'_'));
|
||||
assert_eq!(upcase_w(0xE9), 0xC9); // e acute
|
||||
assert_eq!(upcase_w(0xFF), 0x178); // y diaeresis
|
||||
assert_eq!(upcase_w(0xDF), 0xDF); // sharp s has no simple uppercase
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_the_windows_table_not_modern_unicode() {
|
||||
// Windows folds the Greek prosgegrammeni forms U+1F80 -> U+1F88.
|
||||
assert_eq!(upcase_w(0x1F88), 0x1F88);
|
||||
assert_eq!(upcase_w(0x1F80), 0x1F88);
|
||||
// U+1E9E (capital sharp s) and U+A64A-era additions are unchanged.
|
||||
assert_eq!(upcase_w(0x1E9E), 0x1E9E);
|
||||
// Georgian Mtavruli (U+1C90) is newer than the table: its lowercase
|
||||
// U+10D0 maps to nothing under Windows but to U+1C90 in Unicode.
|
||||
assert_eq!(upcase_w(0x10D0), 0x10D0);
|
||||
// Surrogate halves are untouched.
|
||||
assert_eq!(upcase_w(0xD800), 0xD800);
|
||||
assert_eq!(upcase_w(0xDFFF), 0xDFFF);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user