From 42e48a4704242b1f63092902b6b40e254c64cad7 Mon Sep 17 00:00:00 2001 From: Jesper Jensen Date: Sun, 28 Dec 2025 11:48:45 +0100 Subject: Add some more parser code --- src/parser.rs | 861 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 848 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/parser.rs b/src/parser.rs index 19f019a..1f028a0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -286,33 +286,734 @@ fn parse_digit(str: &Vec, pos: usize) -> Option<(Range, usize)> { } } +fn parse_dec_octet(str: &Vec, start: usize) -> Option<(Range, usize)> { + let mut pos = start; + + let mut len = 0; + let mut value: u16 = 0; + + while len < 3 { + if let Some((_, npos)) = parse_digit(str, pos) { + value = value * 10 + (str[pos] as u16 - '0' as u16); + pos = npos; + len += 1; + } else { + break; + } + } + + if len == 0 { + return None; + } + + if len > 1 && str[start] == '0' { + return None; + } + + if value > 255 { + return None; + } + + return Some((start..pos, pos)); +} + +fn parse_ipv4address(str: &Vec, start: usize) -> Option<(Range, usize)> { + let mut pos = start; + + let (_, npos) = parse_dec_octet(str, pos)?; + pos = npos; + + if let Some(ch) = str.get(pos) && *ch == '.' { + pos += 1; + } else { + return None; + } + + let (_, npos) = parse_dec_octet(str, pos)?; + pos = npos; + + if let Some(ch) = str.get(pos) && *ch == '.' { + pos += 1; + } else { + return None; + } + + let (_, npos) = parse_dec_octet(str, pos)?; + pos = npos; + + if let Some(ch) = str.get(pos) && *ch == '.' { + pos += 1; + } else { + return None; + } + + let (_, npos) = parse_dec_octet(str, pos)?; + pos = npos; + + return Some((start..pos, pos)); +} + +fn parse_hexdig(str: &Vec, pos: usize) -> Option<(Range, usize)> { + if pos >= str.len() { + return None; + } + + return match str[pos] { + '0'..='9' | 'A'..='F' | 'a'..='f' => Some((pos..pos+1, pos+1)), + _ => None, + } +} + fn parse_scheme(str: &Vec, start: usize) -> Option<(Range, usize)> { let mut pos = start; - let (_, npos) = parse_alpha(str, pos)?; + let (_, npos) = parse_alpha(str, pos)?; + pos = npos; + + loop { + if let Some((_, npos)) = parse_alpha(str, pos) { + pos = npos; + } else { + break; + } + }; + + return None; +} + +fn parse_reg_name(str: &Vec, start: usize) -> Option<(Range, usize)> { + let mut pos = start; + + loop { + if let Some((_, npos)) = parse_unreserved(str, pos) { + pos = npos; + } else if let Some((_, npos)) = parse_pct_encoded(str, pos) { + pos = npos; + } else if let Some((_, npos)) = parse_sub_delims(str, pos) { + pos = npos; + } else { + break; + } + } + + return Some((start..pos, pos)); +} + +fn parse_h16(str: &Vec, start: usize) -> Option<(Range, usize)> { + let mut pos = start; + + let (_, npos) = parse_hexdig(str, pos)?; + pos = npos; + + for _ in 0..3 { + if let Some((_, npos)) = parse_hexdig(str, pos) { + pos = npos; + } else { + break; + } + } + + return Some((start..pos, pos)); +} + +fn parse_ls32(str: &Vec, start: usize) -> Option<(Range, usize)> { + let mut pos = start; + + if let Some((_, npos)) = parse_h16(str, pos) { + if let Some((_, npos)) = parse_exact_string(str, npos, ":") { + if let Some((_, npos)) = parse_h16(str, npos) { + return Some((start..npos, npos)); + } + } + } + + if let Some((_, npos)) = parse_ipv4address(str, pos) { + return Some((start..npos, npos)); + } + + return None; +} + +fn parse_ipv6address(str: &Vec, start: usize) -> Option<(Range, usize)> { + let mut pos = start; + + { + let mut tpos = pos; + for _ in 0..6 { + if let Some((_, npos)) = parse_h16(str, tpos) { + if let Some((_, npos)) = parse_exact_string(str, npos, ":") { + tpos = npos; + continue; + } + } + + // We fail the match, Rollback and stop + tpos = pos; + break; + } + + if tpos != pos { + // We successfully matched the last part + if let Some((_, npos)) = parse_ls32(str, tpos) { + pos = npos; + } + } + } + + if pos == start { + let mut tpos = pos; + if let Some((_, npos)) = parse_exact_string(str, tpos, "::") { + tpos = npos; + } else { + tpos = pos; + } + + if tpos != pos { + for _ in 0..5 { + if let Some((_, npos)) = parse_h16(str, tpos) { + if let Some((_, npos)) = parse_exact_string(str, npos, ":") { + tpos = npos; + continue; + } + } + + // We fail the match, Rollback and stop + tpos = pos; + break; + } + } + + if tpos != pos { + // We successfully matched the last part + if let Some((_, npos)) = parse_ls32(str, tpos) { + pos = npos; + } + } + } + + if pos == start { + let mut tpos = pos; + + if let Some((_, npos)) = parse_h16(str, tpos) { + tpos = npos; + } + + if let Some((_, npos)) = parse_exact_string(str, tpos, "::") { + tpos = npos; + } else { + tpos = pos; + } + + if tpos != pos { + for _ in 0..4 { + if let Some((_, npos)) = parse_h16(str, tpos) { + if let Some((_, npos)) = parse_exact_string(str, npos, ":") { + tpos = npos; + continue; + } + } + + // We fail the match, Rollback and stop + tpos = pos; + break; + } + } + + if tpos != pos { + // We successfully matched the last part + if let Some((_, npos)) = parse_ls32(str, tpos) { + pos = npos; + } + } + } + + if pos == start { + let mut tpos = pos; + + for _ in 0..1 { + if let Some((_, npos)) = parse_h16(str, tpos) { + if let Some((_, npos)) = parse_exact_string(str, npos, ":") { + tpos = npos; + continue; + } + } + + // Match failed + break; + } + + if let Some((_, npos)) = parse_h16(str, tpos) { + tpos = npos; + } + + if let Some((_, npos)) = parse_exact_string(str, tpos, "::") { + tpos = npos; + } else { + tpos = pos; + } + + if tpos != pos { + for _ in 0..3 { + if let Some((_, npos)) = parse_h16(str, tpos) { + if let Some((_, npos)) = parse_exact_string(str, npos, ":") { + tpos = npos; + continue; + } + } + + // We fail the match, Rollback and stop + tpos = pos; + break; + } + } + + if tpos != pos { + // We successfully matched the last part + if let Some((_, npos)) = parse_ls32(str, tpos) { + pos = npos; + } + } + } + + if pos == start { + let mut tpos = pos; + + for _ in 0..2 { + if let Some((_, npos)) = parse_h16(str, tpos) { + if let Some((_, npos)) = parse_exact_string(str, npos, ":") { + tpos = npos; + continue; + } + } + + // Match failed + break; + } + + if let Some((_, npos)) = parse_h16(str, tpos) { + tpos = npos; + } + + if let Some((_, npos)) = parse_exact_string(str, tpos, "::") { + tpos = npos; + } else { + tpos = pos; + } + + if tpos != pos { + for _ in 0..2 { + if let Some((_, npos)) = parse_h16(str, tpos) { + if let Some((_, npos)) = parse_exact_string(str, npos, ":") { + tpos = npos; + continue; + } + } + + // We fail the match, Rollback and stop + tpos = pos; + break; + } + } + + if tpos != pos { + // We successfully matched the last part + if let Some((_, npos)) = parse_ls32(str, tpos) { + pos = npos; + } + } + } + + if pos == start { + let mut tpos = pos; + + for _ in 0..3 { + if let Some((_, npos)) = parse_h16(str, tpos) { + if let Some((_, npos)) = parse_exact_string(str, npos, ":") { + tpos = npos; + continue; + } + } + + // Match failed + break; + } + + if let Some((_, npos)) = parse_h16(str, tpos) { + tpos = npos; + } + + if let Some((_, npos)) = parse_exact_string(str, tpos, "::") { + tpos = npos; + } else { + tpos = pos; + } + + if tpos != pos { + for _ in 0..1 { + if let Some((_, npos)) = parse_h16(str, tpos) { + if let Some((_, npos)) = parse_exact_string(str, npos, ":") { + tpos = npos; + continue; + } + } + + // We fail the match, Rollback and stop + tpos = pos; + break; + } + } + + if tpos != pos { + // We successfully matched the last part + if let Some((_, npos)) = parse_ls32(str, tpos) { + pos = npos; + } + } + } + + if pos == start { + let mut tpos = pos; + + for _ in 0..4 { + if let Some((_, npos)) = parse_h16(str, tpos) { + if let Some((_, npos)) = parse_exact_string(str, npos, ":") { + tpos = npos; + continue; + } + } + + // Match failed + break; + } + + if let Some((_, npos)) = parse_h16(str, tpos) { + tpos = npos; + } + + if let Some((_, npos)) = parse_exact_string(str, tpos, "::") { + tpos = npos; + } else { + tpos = pos; + } + + if tpos != pos { + // We successfully matched the last part + if let Some((_, npos)) = parse_ls32(str, tpos) { + pos = npos; + } + } + } + + if pos == start { + let mut tpos = pos; + + for _ in 0..5 { + if let Some((_, npos)) = parse_h16(str, tpos) { + if let Some((_, npos)) = parse_exact_string(str, npos, ":") { + tpos = npos; + continue; + } + } + + // Match failed + break; + } + + if let Some((_, npos)) = parse_h16(str, tpos) { + tpos = npos; + } + + if let Some((_, npos)) = parse_exact_string(str, tpos, "::") { + tpos = npos; + } else { + tpos = pos; + } + + if tpos != pos { + // We successfully matched the last part + if let Some((_, npos)) = parse_h16(str, tpos) { + pos = npos; + } + } + } + + if pos == start { + let mut tpos = pos; + + for _ in 0..6 { + if let Some((_, npos)) = parse_h16(str, tpos) { + if let Some((_, npos)) = parse_exact_string(str, npos, ":") { + tpos = npos; + continue; + } + } + + // Match failed + break; + } + + if let Some((_, npos)) = parse_h16(str, tpos) { + tpos = npos; + } + + if let Some((_, npos)) = parse_exact_string(str, tpos, "::") { + tpos = npos; + } else { + tpos = pos; + } + + pos = tpos; + } + + if pos == start { + return None; + } + + return Some((start..pos, pos)); +} + +#[cfg(test)] +mod test_ipv6address { + use super::*; + + #[test] + fn parses_full_ipv6address() { + let data = "2001:0db8:85a3:0000:0000:8a2e:0370:7334".chars().collect(); + + let (range, pos) = parse_ipv6address(&data, 0).unwrap(); + + assert_eq!(pos, 39); + assert_eq!(range.start, 0); + assert_eq!(range.end, 39); + } + + #[test] + fn parses_7part_ipv6address() { + let data = "::0db8:85a3:0000:0000:8a2e:0370:7334".chars().collect(); + + let (range, pos) = parse_ipv6address(&data, 0).unwrap(); + + assert_eq!(pos, 36); + assert_eq!(range.start, 0); + assert_eq!(range.end, 36); + } + + #[test] + fn parses_6part_ipv6address() { + let data = "::85a3:0000:0000:8a2e:0370:7334".chars().collect(); + + let (range, pos) = parse_ipv6address(&data, 0).unwrap(); + + assert_eq!(pos, 31); + assert_eq!(range.start, 0); + assert_eq!(range.end, 31); + } + + #[test] + fn parses_5part_ipv6address() { + let data = "::0000:0000:8a2e:0370:7334".chars().collect(); + + let (range, pos) = parse_ipv6address(&data, 0).unwrap(); + + assert_eq!(pos, 26); + assert_eq!(range.start, 0); + assert_eq!(range.end, 26); + } + + #[test] + fn parses_4part_ipv6address() { + let data = "::0000:8a2e:0370:7334".chars().collect(); + + let (range, pos) = parse_ipv6address(&data, 0).unwrap(); + + assert_eq!(pos, 21); + assert_eq!(range.start, 0); + assert_eq!(range.end, 21); + } + + #[test] + fn parses_3part_ipv6address() { + let data = "::8a2e:0370:7334".chars().collect(); + + let (range, pos) = parse_ipv6address(&data, 0).unwrap(); + + assert_eq!(pos, 16); + assert_eq!(range.start, 0); + assert_eq!(range.end, 16); + } + + #[test] + fn parses_2part_ipv6address() { + let data = "::0370:7334".chars().collect(); + + let (range, pos) = parse_ipv6address(&data, 0).unwrap(); + + assert_eq!(pos, 11); + assert_eq!(range.start, 0); + assert_eq!(range.end, 11); + } + + #[test] + fn parses_1part_ipv6address() { + let data = "::7334".chars().collect(); + + let (range, pos) = parse_ipv6address(&data, 0).unwrap(); + + assert_eq!(pos, 6); + assert_eq!(range.start, 0); + assert_eq!(range.end, 6); + } + + #[test] + fn parses_0part_ipv6address() { + let data = "::".chars().collect(); + + let (range, pos) = parse_ipv6address(&data, 0).unwrap(); + + assert_eq!(pos, 2); + assert_eq!(range.start, 0); + assert_eq!(range.end, 2); + } + + #[test] + fn parses_full_ipv6address_trailing_ipv4() { + let data = "2001:0db8:85a3:0000:0000:8a2e:127.0.0.1".chars().collect(); + + let (range, pos) = parse_ipv6address(&data, 0).unwrap(); + + assert_eq!(pos, 39); + assert_eq!(range.start, 0); + assert_eq!(range.end, 39); + } + + #[test] + fn parses_compressed_ipv6_loopback() { + let data = "::1".chars().collect(); + + let (range, pos) = parse_ipv6address(&data, 0).unwrap(); + + assert_eq!(pos, 3); + assert_eq!(range.start, 0); + assert_eq!(range.end, 3); + } + + #[test] + fn parses_ipv6_with_embedded_ipv4() { + let data = "::ffff:192.0.2.128".chars().collect(); + + let (range, pos) = parse_ipv6address(&data, 0).unwrap(); + + assert_eq!(pos, 18); + assert_eq!(range.start, 0); + assert_eq!(range.end, 18); + } + + #[test] + fn rejects_invalid_ipv6_hex() { + let data = "2001:db8::g1".chars().collect(); + + let res = parse_ipv6address(&data, 0); + + assert!(res.is_none()); + } + + #[test] + fn rejects_multiple_compressions() { + let data = "2001::85a3::7334".chars().collect(); + + let res = parse_ipv6address(&data, 0); + + assert!(res.is_none()); + } + + #[test] + fn rejects_too_many_segments() { + let data = "1:2:3:4:5:6:7:8:9".chars().collect(); + + let res = parse_ipv6address(&data, 0); + + assert!(res.is_none()); + } + + #[test] + fn rejects_missing_segments_without_compression() { + let data = "2001:db8:85a3:0:0:8a2e:370".chars().collect(); + + let res = parse_ipv6address(&data, 0); + + assert!(res.is_none()); + } + + #[test] + fn parses_trailing_compression() { + let data = "2001:db8:85a3::".chars().collect(); + + let (range, pos) = parse_ipv6address(&data, 0).unwrap(); + + assert_eq!(pos, 15); + assert_eq!(range.start, 0); + assert_eq!(range.end, 15); + } + + #[test] + fn parses_compressed_ipv6_with_ipv4_tail() { + let data = "2001:db8::192.0.2.33".chars().collect(); + + let (range, pos) = parse_ipv6address(&data, 0).unwrap(); + + assert_eq!(pos, 21); + assert_eq!(range.start, 0); + assert_eq!(range.end, 21); + } + + #[test] + fn rejects_ipv4_tail_out_of_range() { + let data = "2001::256.0.0.1".chars().collect(); + + let res = parse_ipv6address(&data, 0); + + assert!(res.is_none()); + } +} + + +fn parse_ipvfuture(str: &Vec, start: usize) -> Option<(Range, usize)> { + let mut pos = start; + + let (_, npos) = parse_exact_string(str, pos, "v")?; + pos = npos; + + let (_, npos) = parse_hexdig(str, pos)?; pos = npos; loop { - if let Some((_, npos)) = parse_alpha(str, pos) { + if let Some((_, npos)) = parse_hexdig(str, pos) { pos = npos; } else { break; } - }; + } - return None; -} + let (_, npos) = parse_exact_string(str, pos, ".")?; + pos = npos; -fn parse_reg_name(str: &Vec, start: usize) -> Option<(Range, usize)> { - let mut pos = start; + if let Some((_, npos)) = parse_unreserved(str, pos) { + pos = npos; + } else if let Some((_, npos)) = parse_sub_delims(str, pos) { + pos = npos; + } else if let Some((_, npos)) = parse_exact_string(str, pos, ":") { + pos = npos; + } else { + return None; + } loop { if let Some((_, npos)) = parse_unreserved(str, pos) { pos = npos; - } else if let Some((_, npos)) = parse_pct_encoded(str, pos) { - pos = npos; } else if let Some((_, npos)) = parse_sub_delims(str, pos) { pos = npos; + } else if let Some((_, npos)) = parse_exact_string(str, pos, ":") { + pos = npos; } else { break; } @@ -321,12 +1022,79 @@ fn parse_reg_name(str: &Vec, start: usize) -> Option<(Range, usize) return Some((start..pos, pos)); } +#[cfg(test)] +mod test_ipvfuture { + use super::*; + + #[test] + fn parses_ipvfuture_with_colons_and_subdelims() { + let data = "vF.fe80::1-._~+!".chars().collect(); + + let (range, pos) = parse_ipvfuture(&data, 0).unwrap(); + + assert_eq!(pos, 16); + assert_eq!(range.start, 0); + assert_eq!(range.end, 16); + } + + #[test] + fn rejects_ipvfuture_missing_dot() { + let data = "v1fe80::1".chars().collect(); + + let res = parse_ipvfuture(&data, 0); + + assert!(res.is_none()); + } + + #[test] + fn rejects_ipvfuture_with_percent_encoded() { + let data = "v1.fe80::1%25".chars().collect(); + + let (range, pos) = parse_ipvfuture(&data, 0).unwrap(); + + assert_eq!(pos, 10); + assert_eq!(range.start, 0); + assert_eq!(range.end, 10); + } +} + + fn parse_ip_literal(str: &Vec, start: usize) -> Option<(Range, usize)> { + let mut pos = start; + + if let Some(ch) = str.get(pos) && *ch == '[' { + pos += 1; + } else { + return None; + } + + if let Some((_, npos)) = parse_ipv6address(str, pos) { + pos = npos; + } else if let Some((_, npos)) = parse_ipvfuture(str, pos) { + pos = npos; + } else { + return None; + } + + if let Some(ch) = str.get(pos) && *ch == ']' { + pos += 1; + return Some((start..pos, pos)); + } + return None; } fn parse_port(str: &Vec, start: usize) -> Option<(Range, usize)> { - return None; + let mut pos = start; + + while let Some(ch) = str.get(pos) { + match ch { + '0'..='9' => pos += 1, + _ => break, + } + } + + return Some((start..pos, pos)); } fn parse_host(str: &Vec, start: usize) -> Option<(Range, usize)> { @@ -337,6 +1105,11 @@ fn parse_host(str: &Vec, start: usize) -> Option<(Range, usize)> { return Some((start..pos, pos)); } + if let Some((_, npos)) = parse_ipv4address(str, pos) { + pos = npos; + return Some((start..pos, pos)); + } + if let Some((_, npos)) = parse_reg_name(str, pos) { pos = npos; return Some((start..pos, pos)); @@ -382,8 +1155,10 @@ fn parse_authority(str: &Vec, start: usize) -> Option<(Range, usize } if let Some(ch) = str.get(pos) && *ch == ':' { - if let Some((_, npos)) = parse_port(str, pos) { + if let Some((_, npos)) = parse_port(str, pos+1) { pos = npos; + } else { + return None; } } @@ -426,6 +1201,20 @@ fn parse_unreserved(str: &Vec, start: usize) -> Option<(Range, usiz } fn parse_pct_encoded(str: &Vec, start: usize) -> Option<(Range, usize)> { + if start + 2 >= str.len() { + return None; + } + + if str[start] != '%' { + return None; + } + + let is_hexdig = |ch: char| matches!(ch, '0'..='9' | 'A'..='F' | 'a'..='f'); + + if is_hexdig(str[start + 1]) && is_hexdig(str[start + 2]) { + return Some((start..start + 3, start + 3)); + } + return None; } @@ -446,10 +1235,21 @@ fn parse_pchar(str: &Vec, start: usize) -> Option<(Range, usize)> { return Some(x); } + if let Some(x) = parse_pct_encoded(str, start) { + return Some(x); + } + if let Some(x) = parse_sub_delims(str, start) { return Some(x); } + if let Some(ch) = str.get(start) { + match ch { + ':' | '@' => return Some((start..start + 1, start + 1)), + _ => {} + } + } + return None; } @@ -583,7 +1383,23 @@ fn parse_query(str: &Vec, start: usize) -> Option<(Range, usize)> { } fn parse_fragment(str: &Vec, start: usize) -> Option<(Range, usize)> { - return None; + let mut pos = start; + + if start >= str.len() { + return None; + } + + loop { + if let Some((_, npos)) = parse_pchar(str, pos) { + pos = npos; + } else if let Some(ch) = str.get(pos) && (*ch == '/' || *ch == '?') { + pos += 1; + } else { + break; + } + } + + return Some((start..pos, pos)); } // RFC 3986 @@ -635,6 +1451,26 @@ mod test_relative_ref { assert_eq!(range.start, 0); assert_eq!(range.end, 12); } + + #[test] + fn with_fragment() { + let data = "/foo#frag".chars().collect(); + + let (range, pos) = parse_relative_ref(&data, 0).unwrap(); + assert_eq!(pos, data.len()); + assert_eq!(range.start, 0); + assert_eq!(range.end, data.len()); + } + + #[test] + fn with_percent_encoded_fragment() { + let data = "/foo#fr%20ag".chars().collect(); + + let (range, pos) = parse_relative_ref(&data, 0).unwrap(); + assert_eq!(pos, data.len()); + assert_eq!(range.start, 0); + assert_eq!(range.end, data.len()); + } } fn parse_uri(str: &Vec, start: usize) -> Option<(Range, usize)> { @@ -1038,7 +1874,6 @@ mod test_link { } #[test] - #[ignore = "percent-encoded paths are not supported"] fn rejects_percent_encoded_path() { let data: Vec = "".chars().collect(); -- cgit v1.2.3