summaryrefslogtreecommitdiff
path: root/src/parser.rs
diff options
context:
space:
mode:
authorJesper Jensen <jesper@jnsn.dev>2025-12-28 11:48:45 +0100
committerJesper Jensen <jesper@jnsn.dev>2025-12-28 11:48:45 +0100
commit42e48a4704242b1f63092902b6b40e254c64cad7 (patch)
treea8b032fdffaf68bc45705af44314f037f232c897 /src/parser.rs
parent2f26047f850093374513b78ac2d7579b6f087bd9 (diff)
Add some more parser code
Diffstat (limited to 'src/parser.rs')
-rw-r--r--src/parser.rs843
1 files changed, 839 insertions, 4 deletions
diff --git a/src/parser.rs b/src/parser.rs
index 19f019a..1f028a0 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -286,6 +286,84 @@ fn parse_digit(str: &Vec<char>, pos: usize) -> Option<(Range<usize>, usize)> {
}
}
+fn parse_dec_octet(str: &Vec<char>, start: usize) -> Option<(Range<usize>, 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<char>, start: usize) -> Option<(Range<usize>, 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<char>, pos: usize) -> Option<(Range<usize>, 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<char>, start: usize) -> Option<(Range<usize>, usize)> {
let mut pos = start;
@@ -321,12 +399,702 @@ fn parse_reg_name(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)
return Some((start..pos, pos));
}
+fn parse_h16(str: &Vec<char>, start: usize) -> Option<(Range<usize>, 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<char>, start: usize) -> Option<(Range<usize>, 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<char>, start: usize) -> Option<(Range<usize>, 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<char>, start: usize) -> Option<(Range<usize>, 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_hexdig(str, pos) {
+ pos = npos;
+ } else {
+ break;
+ }
+ }
+
+ let (_, npos) = parse_exact_string(str, pos, ".")?;
+ pos = npos;
+
+ 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_sub_delims(str, pos) {
+ pos = npos;
+ } else if let Some((_, npos)) = parse_exact_string(str, pos, ":") {
+ pos = npos;
+ } else {
+ break;
+ }
+ }
+
+ 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<char>, start: usize) -> Option<(Range<usize>, 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<char>, start: usize) -> Option<(Range<usize>, 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<char>, start: usize) -> Option<(Range<usize>, usize)> {
@@ -337,6 +1105,11 @@ fn parse_host(str: &Vec<char>, start: usize) -> Option<(Range<usize>, 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<char>, start: usize) -> Option<(Range<usize>, 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<char>, start: usize) -> Option<(Range<usize>, usiz
}
fn parse_pct_encoded(str: &Vec<char>, start: usize) -> Option<(Range<usize>, 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<char>, start: usize) -> Option<(Range<usize>, 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<char>, start: usize) -> Option<(Range<usize>, usize)> {
}
fn parse_fragment(str: &Vec<char>, start: usize) -> Option<(Range<usize>, 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<char>, start: usize) -> Option<(Range<usize>, usize)> {
@@ -1038,7 +1874,6 @@ mod test_link {
}
#[test]
- #[ignore = "percent-encoded paths are not supported"]
fn rejects_percent_encoded_path() {
let data: Vec<char> = "</pa%20th>".chars().collect();