fix: use Zig's numeric parsing in protocol and escape-sequence parsers
Rust's str::parse accepts and rejects different inputs than Zig's std.fmt.parseInt/parseFloat (underscores, base prefixes, signs). The config layer already used the Zig ports; now the protocol parsers do too (call site -> Zig reference -> status in final-Z12.md): - zig_fmt moves into the leaf crate ghostty-zigfmt (ghostty-vt cannot depend on ghostty-os); ghostty-os re-exports it unchanged. - ghostty-vt: kitty graphics keys, kitty text sizing, OSC 4/5/10-19 color indices, hex color components and intensities, OSC 9 sleep and progress, OSC 133 exit codes, OSC 3008 fields, kitty color palette. - ghostty-terminal: tmux layout, format variables and pane tabs. - ghostty-os: URI ports and %XX decoding; ghostty-bench CLI arguments. Expected values were cross-checked against the real Zig toolchain. Test Plan: - cargo test --workspace --no-fail-fast - just check-macos (both), just check-windows, just check-wasm
This commit is contained in:
1 parent
ecd67c8f3e
commit
16eb1fba61
22 files changed
+290
-186
No files matched your search
@@ -89,9 +89,10 @@ fn percent_decode(s: &str) -> Cow<'_, [u8]> {
|
||||
while i < b.len() {
|
||||
if b[i] == b'%'
|
||||
&& i + 2 < b.len()
|
||||
&& let (Some(h), Some(l)) = (hexval(b[i + 1]), hexval(b[i + 2]))
|
||||
// `std.fmt.parseInt(u8, "XX", 16)`: accepts a sign and `_`.
|
||||
&& let Ok(v) = crate::zig_fmt::parse_int::<u8>(&b[i + 1..i + 3], 16)
|
||||
{
|
||||
out.push((h << 4) | l);
|
||||
out.push(v);
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
@@ -101,15 +102,6 @@ fn percent_decode(s: &str) -> Cow<'_, [u8]> {
|
||||
Cow::Owned(out)
|
||||
}
|
||||
|
||||
fn hexval(c: u8) -> Option<u8> {
|
||||
match c {
|
||||
b'0'..=b'9' => Some(c - b'0'),
|
||||
b'a'..=b'f' => Some(c - b'a' + 10),
|
||||
b'A'..=b'F' => Some(c - b'A' + 10),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// `std.Uri`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Uri<'a> {
|
||||
@@ -217,8 +209,7 @@ fn parse_authority<'a>(authority: &'a str, uri: &mut Uri<'a>) -> Result<(), Pars
|
||||
// if not part of the V6 address field
|
||||
end_of_host = end_of_host.min(index);
|
||||
uri.port = Some(
|
||||
authority[index + 1..]
|
||||
.parse::<u16>()
|
||||
crate::zig_fmt::parse_int::<u16>(&authority.as_bytes()[index + 1..], 10)
|
||||
.map_err(|_| ParseError::InvalidPort)?,
|
||||
);
|
||||
}
|
||||
@@ -228,8 +219,7 @@ fn parse_authority<'a>(authority: &'a str, uri: &mut Uri<'a>) -> Result<(), Pars
|
||||
// if not part of the userinfo field
|
||||
end_of_host = end_of_host.min(index);
|
||||
uri.port = Some(
|
||||
authority[index + 1..]
|
||||
.parse::<u16>()
|
||||
crate::zig_fmt::parse_int::<u16>(&authority.as_bytes()[index + 1..], 10)
|
||||
.map_err(|_| ParseError::InvalidPort)?,
|
||||
);
|
||||
}
|
||||
@@ -411,6 +401,24 @@ fn is_valid_mac_address(s: &str) -> bool {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn zig_parse_int_port_and_percent() {
|
||||
let u = parse("http://host:8_0/p", ParseOptions::default()).unwrap();
|
||||
assert_eq!(u.port, Some(80));
|
||||
let u = parse("http://host:+80/p", ParseOptions::default()).unwrap();
|
||||
assert_eq!(u.port, Some(80));
|
||||
assert!(parse("http://host:0x50/p", ParseOptions::default()).is_err());
|
||||
assert!(parse("http://host:65536/p", ParseOptions::default()).is_err());
|
||||
assert!(parse("http://host:-1/p", ParseOptions::default()).is_err());
|
||||
// `%XX` is decoded with `parseInt(u8, XX, 16)`: sign and nothing else.
|
||||
let u = parse(
|
||||
"file://localhost/a%+Ab%-0c%_1d%1_e%41",
|
||||
ParseOptions::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(&*u.path.to_raw_bytes(), b"/a\x0ab\x00c%_1d%1_eA");
|
||||
}
|
||||
|
||||
const MAC: ParseOptions = ParseOptions {
|
||||
mac_address: true,
|
||||
raw_path: false,
|
||||
|
||||
Reference in new issue
Block a user