fix(deps): backport secure Wayland XML parser
This commit is contained in:
+186
@@ -0,0 +1,186 @@
|
||||
// `bytes`, `next_chr`, `parse_lit_str`, `parse_lit_str_cooked` and `parse_lit_str_raw` are adapted
|
||||
// from syn:
|
||||
// https://github.com/dtolnay/syn/blob/362ee2d02df3f1b2e74c7b7a4cf2ed3c106404c9/src/lit.rs#L1062-L1167
|
||||
// and
|
||||
// https://github.com/dtolnay/syn/blob/362ee2d02df3f1b2e74c7b7a4cf2ed3c106404c9/src/lit.rs#L1327-L1388
|
||||
|
||||
/// Get the byte at offset idx, or a default of `b'\0'` if we're looking
|
||||
/// past the end of the input buffer.
|
||||
fn byte(s: &str, idx: usize) -> u8 {
|
||||
if idx < s.len() {
|
||||
s.as_bytes()[idx]
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn next_chr(s: &str) -> char {
|
||||
s.chars().next().unwrap_or('\0')
|
||||
}
|
||||
|
||||
// Returns (content, suffix).
|
||||
fn parse_lit_str(s: &str) -> String {
|
||||
match byte(s, 0) {
|
||||
b'"' => parse_lit_str_cooked(s),
|
||||
b'r' => parse_lit_str_raw(s),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// Clippy false positive
|
||||
// https://github.com/rust-lang-nursery/rust-clippy/issues/2329
|
||||
#[allow(clippy::needless_continue)]
|
||||
fn parse_lit_str_cooked(mut s: &str) -> String {
|
||||
assert_eq!(byte(s, 0), b'"');
|
||||
s = &s[1..];
|
||||
|
||||
let mut content = String::new();
|
||||
'outer: loop {
|
||||
let ch = match byte(s, 0) {
|
||||
b'"' => break,
|
||||
b'\\' => {
|
||||
let b = byte(s, 1);
|
||||
s = &s[2..];
|
||||
match b {
|
||||
b'x' => {
|
||||
let (byte, rest) = backslash_x(s);
|
||||
s = rest;
|
||||
assert!(byte <= 0x80, "Invalid \\x byte in string literal");
|
||||
char::from_u32(u32::from(byte)).unwrap()
|
||||
}
|
||||
b'u' => {
|
||||
let (chr, rest) = backslash_u(s);
|
||||
s = rest;
|
||||
chr
|
||||
}
|
||||
b'n' => '\n',
|
||||
b'r' => '\r',
|
||||
b't' => '\t',
|
||||
b'\\' => '\\',
|
||||
b'0' => '\0',
|
||||
b'\'' => '\'',
|
||||
b'"' => '"',
|
||||
b'\r' | b'\n' => loop {
|
||||
let ch = next_chr(s);
|
||||
if ch.is_whitespace() {
|
||||
s = &s[ch.len_utf8()..];
|
||||
} else {
|
||||
continue 'outer;
|
||||
}
|
||||
},
|
||||
b => panic!("unexpected byte {b:?} after \\ character in byte literal"),
|
||||
}
|
||||
}
|
||||
b'\r' => {
|
||||
assert_eq!(byte(s, 1), b'\n', "Bare CR not allowed in string");
|
||||
s = &s[2..];
|
||||
'\n'
|
||||
}
|
||||
_ => {
|
||||
let ch = next_chr(s);
|
||||
s = &s[ch.len_utf8()..];
|
||||
ch
|
||||
}
|
||||
};
|
||||
content.push(ch);
|
||||
}
|
||||
|
||||
assert!(s.starts_with('"'));
|
||||
content
|
||||
}
|
||||
|
||||
fn parse_lit_str_raw(mut s: &str) -> String {
|
||||
assert_eq!(byte(s, 0), b'r');
|
||||
s = &s[1..];
|
||||
|
||||
let mut pounds = 0;
|
||||
while byte(s, pounds) == b'#' {
|
||||
pounds += 1;
|
||||
}
|
||||
assert_eq!(byte(s, pounds), b'"');
|
||||
let close = s.rfind('"').unwrap();
|
||||
for end in s[close + 1..close + 1 + pounds].bytes() {
|
||||
assert_eq!(end, b'#');
|
||||
}
|
||||
|
||||
s[pounds + 1..close].to_owned()
|
||||
}
|
||||
|
||||
fn backslash_x(s: &str) -> (u8, &str) {
|
||||
let mut ch = 0;
|
||||
let b0 = byte(s, 0);
|
||||
let b1 = byte(s, 1);
|
||||
ch += 0x10
|
||||
* match b0 {
|
||||
b'0'..=b'9' => b0 - b'0',
|
||||
b'a'..=b'f' => 10 + (b0 - b'a'),
|
||||
b'A'..=b'F' => 10 + (b0 - b'A'),
|
||||
_ => panic!("unexpected non-hex character after \\x"),
|
||||
};
|
||||
ch += match b1 {
|
||||
b'0'..=b'9' => b1 - b'0',
|
||||
b'a'..=b'f' => 10 + (b1 - b'a'),
|
||||
b'A'..=b'F' => 10 + (b1 - b'A'),
|
||||
_ => panic!("unexpected non-hex character after \\x"),
|
||||
};
|
||||
(ch, &s[2..])
|
||||
}
|
||||
|
||||
fn backslash_u(mut s: &str) -> (char, &str) {
|
||||
if byte(s, 0) != b'{' {
|
||||
panic!("{}", "expected { after \\u");
|
||||
}
|
||||
s = &s[1..];
|
||||
|
||||
let mut ch = 0;
|
||||
let mut digits = 0;
|
||||
loop {
|
||||
let b = byte(s, 0);
|
||||
let digit = match b {
|
||||
b'0'..=b'9' => b - b'0',
|
||||
b'a'..=b'f' => 10 + b - b'a',
|
||||
b'A'..=b'F' => 10 + b - b'A',
|
||||
b'_' if digits > 0 => {
|
||||
s = &s[1..];
|
||||
continue;
|
||||
}
|
||||
b'}' if digits == 0 => panic!("invalid empty unicode escape"),
|
||||
b'}' => break,
|
||||
_ => panic!("unexpected non-hex character after \\u"),
|
||||
};
|
||||
if digits == 6 {
|
||||
panic!("overlong unicode escape (must have at most 6 hex digits)");
|
||||
}
|
||||
ch *= 0x10;
|
||||
ch += u32::from(digit);
|
||||
digits += 1;
|
||||
s = &s[1..];
|
||||
}
|
||||
assert!(byte(s, 0) == b'}');
|
||||
s = &s[1..];
|
||||
|
||||
if let Some(ch) = char::from_u32(ch) {
|
||||
(ch, s)
|
||||
} else {
|
||||
panic!("character code {ch:x} is not a valid unicode character");
|
||||
}
|
||||
}
|
||||
|
||||
// End of code adapted from syn
|
||||
|
||||
pub fn parse_lit_str_token(mut stream: proc_macro::TokenStream) -> String {
|
||||
loop {
|
||||
let mut iter = stream.into_iter();
|
||||
let token = iter.next().expect("expected string argument");
|
||||
assert!(iter.next().is_none(), "unexpected trailing token");
|
||||
let literal = match token {
|
||||
proc_macro::TokenTree::Literal(literal) => literal,
|
||||
proc_macro::TokenTree::Group(group) => {
|
||||
stream = group.stream();
|
||||
continue;
|
||||
}
|
||||
_ => panic!("expected string argument found `{token:?}`"),
|
||||
};
|
||||
return parse_lit_str(&literal.to_string());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user