diff options
| author | Jesper Jensen <jesper@jnsn.dev> | 2026-01-19 21:05:38 +0100 |
|---|---|---|
| committer | Jesper Jensen <jesper@jnsn.dev> | 2026-01-19 21:05:38 +0100 |
| commit | ef21d01f08dc94ba355f596ddb23e1e32c43c8d4 (patch) | |
| tree | 6044255f32f894ea52c28c63e7a56d347386b618 /src/parser.rs | |
| parent | 42e48a4704242b1f63092902b6b40e254c64cad7 (diff) | |
INITIAL COMMIT (again)
Diffstat (limited to 'src/parser.rs')
| -rw-r--r-- | src/parser.rs | 1474 |
1 files changed, 464 insertions, 1010 deletions
diff --git a/src/parser.rs b/src/parser.rs index 1f028a0..38fa3fe 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,11 +3,32 @@ use std::ops::Range; #[derive(Debug)] pub struct Challenge { pub scheme: Range<usize>, - pub params: Vec<AuthParam>, + pub realm: Option<Range<usize>>, + pub scope: Option<Range<usize>>, + pub service: Option<Range<usize>>, +} + +#[derive(Default)] +pub struct PartialChallenge { + pub scheme: Option<Range<usize>>, + pub realm: Option<Range<usize>>, + pub scope: Option<Range<usize>>, + pub service: Option<Range<usize>>, +} + +impl Into<Challenge> for PartialChallenge { + fn into(self) -> Challenge { + return Challenge { + scheme: self.scheme.unwrap(), + realm: self.realm, + scope: self.scope, + service: self.service, + } + } } // RFC 7230 -fn parse_token(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_token(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; while str.len() > pos { match str[pos] { @@ -26,40 +47,7 @@ fn parse_token(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { return None; } - return Some((start..pos, pos)); -} - -#[cfg(test)] -mod test_token { - use super::*; - - #[test] - fn rejects_nontchar() { - let data = vec!['>']; - - let res = parse_token(&data, 0); - assert!(res.is_none()); - } - - #[test] - fn matches_letter() { - let data = vec!['a']; - - let (range, pos) = parse_token(&data, 0).unwrap(); - assert_eq!(pos, 1); - assert_eq!(range.start, 0); - assert_eq!(range.end, 1); - } - - #[test] - fn matches_multiple_letters() { - let data = vec!['a', '1', '|']; - - let (range, pos) = parse_token(&data, 0).unwrap(); - assert_eq!(pos, 3); - assert_eq!(range.start, 0); - assert_eq!(range.end, 3); - } + return Some(pos); } fn parse_qdtext(str: &Vec<char>, start: usize) -> Option<usize> { @@ -77,13 +65,14 @@ fn parse_quotedpair(str: &Vec<char>, start: usize) -> Option<usize> { return Some(start + 2); } -fn parse_quotedstring(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_quotedstring(str: &Vec<char>, start: usize, range: &mut Option<&mut Range<usize>>) -> Option<usize> { let mut pos = start; if str[pos] != '"' { return None; } pos += 1; + range.as_mut().map(|x| x.start = pos); loop { if let Some(npos) = parse_qdtext(str, pos) { pos = npos; @@ -93,16 +82,17 @@ fn parse_quotedstring(str: &Vec<char>, start: usize) -> Option<(Range<usize>, us break; } } + range.as_mut().map(|x| x.end = pos); if str[pos] != '"' { return None; } pos += 1; - return Some((start+1..pos-1, pos)); + return Some(pos); } -fn parse_sp(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_sp(str: &Vec<char>, start: usize) -> Option<usize> { if str.len() <= start { return None; } @@ -117,7 +107,20 @@ fn parse_sp(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { return None; } - return Some((start..pos, pos)); + return Some(pos); +} + +fn parse_ows(str: &Vec<char>, start: usize) -> Option<usize> { + // @COMPL: This is not right + let mut pos = start; + loop { + match parse_sp(str, pos) { + Some(npos) => pos = npos, + None => break, + } + } + + return Some(pos); } fn parse_crlf(str: &Vec<char>, start: usize) -> Option<usize> { @@ -132,7 +135,7 @@ fn parse_crlf(str: &Vec<char>, start: usize) -> Option<usize> { return None; } -fn parse_lws(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_lws(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; match parse_crlf(str, pos) { @@ -141,28 +144,47 @@ fn parse_lws(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { } match parse_sp(str, pos) { - Some((_, npos)) => pos = npos, + Some(npos) => pos = npos, None => return None, } loop { match parse_sp(str, pos) { - Some((_, npos)) => pos = npos, + Some(npos) => pos = npos, None => break, } } - return Some((start..pos, pos)); + return Some(pos); } -#[derive(Debug)] -pub struct AuthParam { - pub key: Range<usize>, - pub value: Range<usize>, -} +fn parse_param(str: &Vec<char>, start: usize, partial: &mut PartialChallenge) -> Option<usize> { + let mut pos = start; -fn parse_param(str: &Vec<char>, start: usize) -> Option<(AuthParam, usize)> { - let pos = start; - let (param_range, pos) = parse_token(str, pos)?; + let mut value_range_slot = None; + + if let Some(npos) = parse_exact_string(str, pos, "realm") { + partial.realm = Some(Range { + ..Default::default() + }); + value_range_slot = partial.realm.as_mut(); + pos = npos; + } else if let Some(npos) = parse_exact_string(str, pos, "scope") { + partial.scope = Some(Range { + ..Default::default() + }); + value_range_slot = partial.scope.as_mut(); + pos = npos; + } else if let Some(npos) = parse_exact_string(str, pos, "service") { + partial.service = Some(Range { + ..Default::default() + }); + value_range_slot = partial.service.as_mut(); + pos = npos; + } else if let Some(npos) = parse_token(str, pos) { + pos = npos; + } else { + return None; + } let mut pos = pos; if str[pos] == '=' { @@ -171,49 +193,65 @@ fn parse_param(str: &Vec<char>, start: usize) -> Option<(AuthParam, usize)> { todo!(); } - let value_range; - if let Some((range, npos)) = parse_token(str, pos) { - value_range = range; + value_range_slot.as_mut().map(|x| x.start = pos); + if let Some(npos) = parse_token(str, pos) { + value_range_slot.map(|x| x.end = npos); pos = npos; - } else if let Some((range, npos)) = parse_quotedstring(str, pos) { - value_range = range; + } else if let Some(npos) = parse_quotedstring(str, pos, &mut value_range_slot) { pos = npos; } else { todo!("{:?}", &str[pos..]); } - return Some((AuthParam{key: param_range, value: value_range}, pos)); + return Some(pos); } -pub fn parse_challenge(str: &Vec<char>, start: usize) -> Option<(Challenge, usize)> { - let pos = start; - let (scheme_range, pos) = parse_token(str, pos).unwrap(); +pub fn parse_authenticate_header(header: &str) -> Option<Challenge> { + let mut partial: PartialChallenge = Default::default(); + let data = header.chars().collect(); + + parse_challenge(&data, 0, &mut partial)?; - let (_, pos) = parse_sp(str, pos).unwrap(); + return Some(partial.into()); +} + +fn parse_challenge(str: &Vec<char>, start: usize, partial: &mut PartialChallenge) -> Option<usize> { + let mut pos = start; + + { + let mut scheme_range = Range { + start: pos, + ..Default::default() + }; + + pos = parse_token(str, pos).unwrap(); + + scheme_range.end = pos; + partial.scheme = Some(scheme_range); + } + + let pos = parse_sp(str, pos).unwrap(); let mut pos = pos; loop { match parse_sp(str, pos) { - Some((_, npos)) => pos = npos, + Some(npos) => pos = npos, None => break, } } - let mut params = vec!(); - let mut pos = pos; loop { match parse_lws(str, pos) { - Some((_, npos)) => pos = npos, + Some(npos) => pos = npos, None => break, } } - let (param, pos) = parse_param(str, pos).unwrap(); - params.push(param); + let pos = parse_param(str, pos, partial).unwrap(); let mut pos = pos; loop { loop { match parse_lws(str, pos) { - Some((_, npos)) => pos = npos, + Some(npos) => pos = npos, None => break, } } @@ -225,15 +263,14 @@ pub fn parse_challenge(str: &Vec<char>, start: usize) -> Option<(Challenge, usiz loop { match parse_lws(str, pos) { - Some((_, npos)) => pos = npos, + Some(npos) => pos = npos, None => break, } } - match parse_param(str, pos) { - Some((param, npos)) => { + match parse_param(str, pos, partial) { + Some(npos) => { pos = npos; - params.push(param); }, None => return None, } @@ -242,58 +279,39 @@ pub fn parse_challenge(str: &Vec<char>, start: usize) -> Option<(Challenge, usiz if pos < str.len() { return None; } - return Some((Challenge {scheme: scheme_range, params}, pos)); -} - -#[derive(Debug)] -pub enum LinkParam { - Rel{relation: Range<usize>}, -} - -impl LinkParam { - pub fn value(&self) -> &Range<usize> { - match self { - LinkParam::Rel{relation} => &relation, - } - } + return Some(pos); } -#[derive(Debug)] -pub struct LinkValue { - uri: Range<usize>, - pub params: Vec<LinkParam>, -} - -fn parse_alpha(str: &Vec<char>, pos: usize) -> Option<(Range<usize>, usize)> { +fn parse_alpha(str: &Vec<char>, pos: usize) -> Option<usize> { if pos >= str.len() { return None; } return match str[pos] { - 'a'..='z' | 'A'..='Z' => Some((pos..pos+1, pos+1)), + 'a'..='z' | 'A'..='Z' => Some(pos+1), _ => None, } } -fn parse_digit(str: &Vec<char>, pos: usize) -> Option<(Range<usize>, usize)> { +fn parse_digit(str: &Vec<char>, pos: usize) -> Option<usize> { if pos >= str.len() { return None; } return match str[pos] { - '0'..='9' => Some((pos..pos+1, pos+1)), + '0'..='9' => Some(pos+1), _ => None, } } -fn parse_dec_octet(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_dec_octet(str: &Vec<char>, start: usize) -> Option<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) { + if let Some(npos) = parse_digit(str, pos) { value = value * 10 + (str[pos] as u16 - '0' as u16); pos = npos; len += 1; @@ -314,13 +332,13 @@ fn parse_dec_octet(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize return None; } - return Some((start..pos, pos)); + return Some(pos); } -fn parse_ipv4address(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_ipv4address(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; - let (_, npos) = parse_dec_octet(str, pos)?; + let npos = parse_dec_octet(str, pos)?; pos = npos; if let Some(ch) = str.get(pos) && *ch == '.' { @@ -329,7 +347,7 @@ fn parse_ipv4address(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usi return None; } - let (_, npos) = parse_dec_octet(str, pos)?; + let npos = parse_dec_octet(str, pos)?; pos = npos; if let Some(ch) = str.get(pos) && *ch == '.' { @@ -338,7 +356,7 @@ fn parse_ipv4address(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usi return None; } - let (_, npos) = parse_dec_octet(str, pos)?; + let npos = parse_dec_octet(str, pos)?; pos = npos; if let Some(ch) = str.get(pos) && *ch == '.' { @@ -347,31 +365,31 @@ fn parse_ipv4address(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usi return None; } - let (_, npos) = parse_dec_octet(str, pos)?; + let npos = parse_dec_octet(str, pos)?; pos = npos; - return Some((start..pos, pos)); + return Some(pos); } -fn parse_hexdig(str: &Vec<char>, pos: usize) -> Option<(Range<usize>, usize)> { +fn parse_hexdig(str: &Vec<char>, pos: usize) -> Option<usize> { if pos >= str.len() { return None; } return match str[pos] { - '0'..='9' | 'A'..='F' | 'a'..='f' => Some((pos..pos+1, pos+1)), + '0'..='9' | 'A'..='F' | 'a'..='f' => Some(pos+1), _ => None, } } -fn parse_scheme(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_scheme(str: &Vec<char>, start: usize) -> Option<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) { + if let Some(npos) = parse_alpha(str, pos) { pos = npos; } else { break; @@ -381,685 +399,404 @@ fn parse_scheme(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> return None; } -fn parse_reg_name(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_reg_name(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; loop { - if let Some((_, npos)) = parse_unreserved(str, pos) { + if let Some(npos) = parse_unreserved(str, pos) { pos = npos; - } else if let Some((_, npos)) = parse_pct_encoded(str, pos) { + } else if let Some(npos) = parse_pct_encoded(str, pos) { pos = npos; - } else if let Some((_, npos)) = parse_sub_delims(str, pos) { + } else if let Some(npos) = parse_sub_delims(str, pos) { pos = npos; } else { break; } } - return Some((start..pos, pos)); + return Some(pos); } -fn parse_h16(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_h16(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; - let (_, npos) = parse_hexdig(str, pos)?; + let npos = parse_hexdig(str, pos)?; pos = npos; for _ in 0..3 { - if let Some((_, npos)) = parse_hexdig(str, pos) { + if let Some(npos) = parse_hexdig(str, pos) { pos = npos; } else { break; } } - return Some((start..pos, pos)); + return Some(pos); } -fn parse_ls32(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_ls32(str: &Vec<char>, start: usize) -> Option<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_h16(str, pos) { + if let Some(npos) = parse_exact_string(str, npos, ":") { + if let Some(npos) = parse_h16(str, npos) { + return Some(npos); } } } - if let Some((_, npos)) = parse_ipv4address(str, pos) { - return Some((start..npos, npos)); + if let Some(npos) = parse_ipv4address(str, pos) { + return Some(npos); } return None; } -fn parse_ipv6address(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_first_part_ipv6(str: &Vec<char>, start: usize, max_reps: usize) -> Option<usize> { + let mut pos; + + if let Some(npos) = parse_h16(str, start) { + pos = Some(npos); + } else { + pos = Some(start); + } + + for _ in 0..max_reps { + if let Some(npos) = parse_exact_string(str, pos.unwrap(), ":") { + if let Some(npos) = parse_h16(str, npos) { + pos = Some(npos); + continue; + } + } + + break; + } + + if let Some(npos) = parse_exact_string(str, pos.unwrap(), "::") { + pos = Some(npos); + } else { + pos = None; + } + + return pos; +} + +fn parse_ipv6address(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; - { - let mut tpos = pos; + // 6( h16 ":" ) ls32 + if pos == start { // Just for symmetry, this is always true + let mut tpos = Some(pos); + for _ in 0..6 { - if let Some((_, npos)) = parse_h16(str, tpos) { - if let Some((_, npos)) = parse_exact_string(str, npos, ":") { - tpos = npos; + if let Some(npos) = parse_h16(str, tpos.unwrap()) { + if let Some(npos) = parse_exact_string(str, npos, ":") { + tpos = Some(npos); continue; } } // We fail the match, Rollback and stop - tpos = pos; + tpos = None; break; } - if tpos != pos { - // We successfully matched the last part - if let Some((_, npos)) = parse_ls32(str, tpos) { - pos = npos; + if let Some(ref mut tpos_val) = tpos { + if let Some(npos) = parse_ls32(str, *tpos_val) { + *tpos_val = npos; + } else { + tpos = None; } } + + if let Some(tpos_val) = tpos { + pos = tpos_val; + } } + // "::" 5( h16 ":" ) ls32 if pos == start { - let mut tpos = pos; - if let Some((_, npos)) = parse_exact_string(str, tpos, "::") { - tpos = npos; + let mut tpos; + + if let Some(npos) = parse_exact_string(str, pos, "::") { + tpos = Some(npos); } else { - tpos = pos; + tpos = None; } - if tpos != pos { + if let Some(ref mut tpos_val) = tpos { for _ in 0..5 { - if let Some((_, npos)) = parse_h16(str, tpos) { - if let Some((_, npos)) = parse_exact_string(str, npos, ":") { - tpos = npos; + if let Some(npos) = parse_h16(str, *tpos_val) { + if let Some(npos) = parse_exact_string(str, npos, ":") { + *tpos_val = npos; continue; } } // We fail the match, Rollback and stop - tpos = pos; + tpos = None; break; } } - if tpos != pos { - // We successfully matched the last part - if let Some((_, npos)) = parse_ls32(str, tpos) { - pos = npos; + if let Some(ref mut tpos_val) = tpos { + if let Some(npos) = parse_ls32(str, *tpos_val) { + *tpos_val = npos; + } else { + tpos = None; } } + + if let Some(tpos_val) = tpos { + pos = tpos_val; + } } + // [ h16 ] "::" 4( h16 ":" ) ls32 if pos == start { - let mut tpos = pos; + let mut tpos; - if let Some((_, npos)) = parse_h16(str, tpos) { - tpos = npos; + if let Some(npos) = parse_h16(str, pos) { + tpos = Some(npos); + } else { + tpos = Some(pos); } - if let Some((_, npos)) = parse_exact_string(str, tpos, "::") { - tpos = npos; + if let Some(npos) = parse_exact_string(str, tpos.unwrap(), "::") { + tpos = Some(npos); } else { - tpos = pos; + tpos = None; } - if tpos != pos { + if let Some(ref mut tpos_val) = tpos { for _ in 0..4 { - if let Some((_, npos)) = parse_h16(str, tpos) { - if let Some((_, npos)) = parse_exact_string(str, npos, ":") { - tpos = npos; + if let Some(npos) = parse_h16(str, *tpos_val) { + if let Some(npos) = parse_exact_string(str, npos, ":") { + *tpos_val = npos; continue; } } // We fail the match, Rollback and stop - tpos = pos; + tpos = None; 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; - } + if let Some(ref mut tpos_val) = tpos { + if let Some(npos) = parse_ls32(str, *tpos_val) { + *tpos_val = npos; + } else { + tpos = None; } - - // Match failed - break; } - if let Some((_, npos)) = parse_h16(str, tpos) { - tpos = npos; + if let Some(tpos_val) = tpos { + pos = tpos_val; } + } - if let Some((_, npos)) = parse_exact_string(str, tpos, "::") { - tpos = npos; - } else { - tpos = pos; - } + // [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + // Implementing this parsing rule sucks. When reading the first repeating block ( h16 ":" ) we + // have to speculatively read those two tokens, but we can't commit them since we don't know if + // they're valid until we then read ANOTHER h16 followed by another colon, which can then of + // course ALSO be part of the repetition if not followed by a second colon. I don't want to + // write that lookahead/rollback code. Instead we can reformulate that production into: + // [ h16 *1( ":" h16 ) ] "::" 3( h16 ":" ) ls32 + // This is much easier to implement, since we now just have to try and read a colon followed by + // an h16, and if we don't find that fall through to the double colon or fail. + // @COMPL We have to be a little careful, since this may interact differently with whitespace + // and such, but that's for later. + if pos == start { + let mut tpos = parse_first_part_ipv6(str, pos, 1); - if tpos != pos { + if let Some(ref mut tpos_val) = tpos { for _ in 0..3 { - if let Some((_, npos)) = parse_h16(str, tpos) { - if let Some((_, npos)) = parse_exact_string(str, npos, ":") { - tpos = npos; + if let Some(npos) = parse_h16(str, *tpos_val) { + if let Some(npos) = parse_exact_string(str, npos, ":") { + *tpos_val = npos; continue; } } // We fail the match, Rollback and stop - tpos = pos; + tpos = None; 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; - } + if let Some(ref mut tpos_val) = tpos { + if let Some(npos) = parse_ls32(str, *tpos_val) { + *tpos_val = npos; + } else { + tpos = None } - - // Match failed - break; } - if let Some((_, npos)) = parse_h16(str, tpos) { - tpos = npos; + if let Some(tpos_val) = tpos { + pos = tpos_val; } + } - if let Some((_, npos)) = parse_exact_string(str, tpos, "::") { - tpos = npos; - } else { - tpos = pos; - } + // [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + if pos == start { + let mut tpos = parse_first_part_ipv6(str, pos, 2); - if tpos != pos { + if let Some(ref mut tpos_val) = tpos { for _ in 0..2 { - if let Some((_, npos)) = parse_h16(str, tpos) { - if let Some((_, npos)) = parse_exact_string(str, npos, ":") { - tpos = npos; + if let Some(npos) = parse_h16(str, *tpos_val) { + if let Some(npos) = parse_exact_string(str, npos, ":") { + *tpos_val = npos; continue; } } // We fail the match, Rollback and stop - tpos = pos; + tpos = None; break; } } - if tpos != pos { - // We successfully matched the last part - if let Some((_, npos)) = parse_ls32(str, tpos) { - pos = npos; + if let Some(ref mut tpos_val) = tpos { + if let Some(npos) = parse_ls32(str, *tpos_val) { + *tpos_val = npos; + } else { + tpos = None } } - } - - 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(tpos_val) = tpos { + pos = tpos_val; } + } - if let Some((_, npos)) = parse_exact_string(str, tpos, "::") { - tpos = npos; - } else { - tpos = pos; - } + // [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + if pos == start { + let mut tpos = parse_first_part_ipv6(str, pos, 3); - if tpos != pos { + if let Some(ref mut tpos_val) = tpos { for _ in 0..1 { - if let Some((_, npos)) = parse_h16(str, tpos) { - if let Some((_, npos)) = parse_exact_string(str, npos, ":") { - tpos = npos; + if let Some(npos) = parse_h16(str, *tpos_val) { + if let Some(npos) = parse_exact_string(str, npos, ":") { + *tpos_val = npos; continue; } } // We fail the match, Rollback and stop - tpos = pos; + tpos = None; break; } } - if tpos != pos { - // We successfully matched the last part - if let Some((_, npos)) = parse_ls32(str, tpos) { - pos = npos; + if let Some(ref mut tpos_val) = tpos { + if let Some(npos) = parse_ls32(str, *tpos_val) { + *tpos_val = npos; + } else { + tpos = None } } + + if let Some(tpos_val) = tpos { + pos = tpos_val; + } } + // [ *4( h16 ":" ) h16 ] "::" ls32 if pos == start { - let mut tpos = pos; + let mut tpos = parse_first_part_ipv6(str, pos, 4); - for _ in 0..4 { - if let Some((_, npos)) = parse_h16(str, tpos) { - if let Some((_, npos)) = parse_exact_string(str, npos, ":") { - tpos = npos; - continue; - } + if let Some(ref mut tpos_val) = tpos { + if let Some(npos) = parse_ls32(str, *tpos_val) { + *tpos_val = npos; + } else { + tpos = None } - - // 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 let Some(tpos_val) = tpos { + pos = tpos_val; } } + // [ *5( h16 ":" ) h16 ] "::" h16 if pos == start { - let mut tpos = pos; + let mut tpos = parse_first_part_ipv6(str, pos, 5); - for _ in 0..5 { - if let Some((_, npos)) = parse_h16(str, tpos) { - if let Some((_, npos)) = parse_exact_string(str, npos, ":") { - tpos = npos; - continue; - } + if let Some(ref mut tpos_val) = tpos { + if let Some(npos) = parse_h16(str, *tpos_val) { + *tpos_val = npos; + } else { + tpos = None } - - // 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 let Some(tpos_val) = tpos { + pos = tpos_val; } } 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; - } - } + let tpos = parse_first_part_ipv6(str, pos, 6); - // Match failed - break; - } - - if let Some((_, npos)) = parse_h16(str, tpos) { - tpos = npos; + if let Some(tpos_val) = tpos { + pos = tpos_val; } - - 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()); - } + return Some(pos); } - -fn parse_ipvfuture(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_ipvfuture(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; - let (_, npos) = parse_exact_string(str, pos, "v")?; + let npos = parse_exact_string(str, pos, "v")?; pos = npos; - let (_, npos) = parse_hexdig(str, pos)?; + let npos = parse_hexdig(str, pos)?; pos = npos; loop { - if let Some((_, npos)) = parse_hexdig(str, pos) { + if let Some(npos) = parse_hexdig(str, pos) { pos = npos; } else { break; } } - let (_, npos) = parse_exact_string(str, pos, ".")?; + let npos = parse_exact_string(str, pos, ".")?; pos = npos; - if let Some((_, npos)) = parse_unreserved(str, pos) { + if let Some(npos) = parse_unreserved(str, pos) { pos = npos; - } else if let Some((_, npos)) = parse_sub_delims(str, pos) { + } else if let Some(npos) = parse_sub_delims(str, pos) { pos = npos; - } else if let Some((_, npos)) = parse_exact_string(str, pos, ":") { + } else if let Some(npos) = parse_exact_string(str, pos, ":") { pos = npos; } else { return None; } loop { - if let Some((_, npos)) = parse_unreserved(str, pos) { + if let Some(npos) = parse_unreserved(str, pos) { pos = npos; - } else if let Some((_, npos)) = parse_sub_delims(str, pos) { + } else if let Some(npos) = parse_sub_delims(str, pos) { pos = npos; - } else if let Some((_, npos)) = parse_exact_string(str, pos, ":") { + } 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); - } + return Some(pos); } - -fn parse_ip_literal(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_ip_literal(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; if let Some(ch) = str.get(pos) && *ch == '[' { @@ -1068,9 +805,9 @@ fn parse_ip_literal(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usiz return None; } - if let Some((_, npos)) = parse_ipv6address(str, pos) { + if let Some(npos) = parse_ipv6address(str, pos) { pos = npos; - } else if let Some((_, npos)) = parse_ipvfuture(str, pos) { + } else if let Some(npos) = parse_ipvfuture(str, pos) { pos = npos; } else { return None; @@ -1078,13 +815,13 @@ fn parse_ip_literal(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usiz if let Some(ch) = str.get(pos) && *ch == ']' { pos += 1; - return Some((start..pos, pos)); + return Some(pos); } return None; } -fn parse_port(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_port(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; while let Some(ch) = str.get(pos) { @@ -1094,39 +831,39 @@ fn parse_port(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { } } - return Some((start..pos, pos)); + return Some(pos); } -fn parse_host(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_host(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; - if let Some((_, npos)) = parse_ip_literal(str, pos) { + if let Some(npos) = parse_ip_literal(str, pos) { pos = npos; - return Some((start..pos, pos)); + return Some(pos); } - if let Some((_, npos)) = parse_ipv4address(str, pos) { + if let Some(npos) = parse_ipv4address(str, pos) { pos = npos; - return Some((start..pos, pos)); + return Some(pos); } - if let Some((_, npos)) = parse_reg_name(str, pos) { + if let Some(npos) = parse_reg_name(str, pos) { pos = npos; - return Some((start..pos, pos)); + return Some(pos); } return None; } -fn parse_userinfo(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_userinfo(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; loop { - if let Some((_, npos)) = parse_unreserved(str, pos) { + if let Some(npos) = parse_unreserved(str, pos) { pos = npos; - } else if let Some((_, npos)) = parse_pct_encoded(str, pos) { + } else if let Some(npos) = parse_pct_encoded(str, pos) { pos = npos; - } else if let Some((_, npos)) = parse_sub_delims(str, pos) { + } else if let Some(npos) = parse_sub_delims(str, pos) { pos = npos; } else if let Some(ch) = str.get(pos) && *ch == ':' { pos += 1; @@ -1135,37 +872,37 @@ fn parse_userinfo(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize) } } - return Some((start..pos, pos)); + return Some(pos); } -fn parse_authority(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_authority(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; - if let Some((_, npos)) = parse_userinfo(str, pos) { + if let Some(npos) = parse_userinfo(str, pos) { if let Some(ch) = str.get(npos) && *ch == '@' { pos = npos; } } // If the character after the userinfo isn't a @ we roll back that parse and try it as a host. - if let Some((_, npos)) = parse_host(str, pos) { + if let Some(npos) = parse_host(str, pos) { pos = npos; } else { return None; } if let Some(ch) = str.get(pos) && *ch == ':' { - if let Some((_, npos)) = parse_port(str, pos+1) { + if let Some(npos) = parse_port(str, pos+1) { pos = npos; } else { return None; } } - return Some((start..pos, pos)); + return Some(pos); } -fn parse_relative_part_1(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_relative_part_1(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; if str[pos] != '/' || str[pos+1] != '/' { @@ -1173,16 +910,16 @@ fn parse_relative_part_1(str: &Vec<char>, start: usize) -> Option<(Range<usize>, } pos += 2; - let (_, npos) = parse_authority(str, pos)?; + let npos = parse_authority(str, pos)?; pos = npos; - let (_, npos) = parse_path_abempty(str, pos)?; + let npos = parse_path_abempty(str, pos)?; pos = npos; - return Some((start..pos, pos)); + return Some(pos); } -fn parse_unreserved(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_unreserved(str: &Vec<char>, start: usize) -> Option<usize> { if let Some(x) = parse_alpha(str, start) { return Some(x); } @@ -1195,12 +932,12 @@ fn parse_unreserved(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usiz return None; } match str[start] { - '-' | '.' | '_' | '~' => Some((start..start+1, start+1)), + '-' | '.' | '_' | '~' => Some(start+1), _ => None } } -fn parse_pct_encoded(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_pct_encoded(str: &Vec<char>, start: usize) -> Option<usize> { if start + 2 >= str.len() { return None; } @@ -1212,25 +949,25 @@ fn parse_pct_encoded(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usi 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 Some(start + 3); } return None; } -fn parse_sub_delims(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_sub_delims(str: &Vec<char>, start: usize) -> Option<usize> { if start >= str.len() { return None; } match str[start] { - '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | ';' | '=' => Some((start..start+1, start+1)), + '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | ';' | '=' => Some(start+1), _ => None } } -fn parse_pchar(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_pchar(str: &Vec<char>, start: usize) -> Option<usize> { if let Some(x) = parse_unreserved(str, start) { return Some(x); } @@ -1245,7 +982,7 @@ fn parse_pchar(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { if let Some(ch) = str.get(start) { match ch { - ':' | '@' => return Some((start..start + 1, start + 1)), + ':' | '@' => return Some(start + 1), _ => {} } } @@ -1253,38 +990,38 @@ fn parse_pchar(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { return None; } -fn parse_segment_nz(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_segment_nz(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; - let (_, npos) = parse_pchar(str, pos)?; + let npos = parse_pchar(str, pos)?; pos = npos; loop { - if let Some((_, npos)) = parse_pchar(str, pos) { + if let Some(npos) = parse_pchar(str, pos) { pos = npos; } else { break; } } - return Some((start..pos, pos)); + return Some(pos); } -fn parse_segment(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_segment(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; loop { - if let Some((_, npos)) = parse_pchar(str, pos) { + if let Some(npos) = parse_pchar(str, pos) { pos = npos; } else { break; } } - return Some((start..pos, pos)); + return Some(pos); } -fn parse_path_absolute(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_path_absolute(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; if str[pos] != '/' { @@ -1292,7 +1029,7 @@ fn parse_path_absolute(str: &Vec<char>, start: usize) -> Option<(Range<usize>, u } pos += 1; - if let Some((_, npos)) = parse_segment_nz(str, pos) { + if let Some(npos) = parse_segment_nz(str, pos) { pos = npos; loop { @@ -1300,7 +1037,7 @@ fn parse_path_absolute(str: &Vec<char>, start: usize) -> Option<(Range<usize>, u break; } - if let Some((_, npos)) = parse_segment(str, pos+1) { + if let Some(npos) = parse_segment(str, pos+1) { pos = npos; } else { break; @@ -1308,15 +1045,15 @@ fn parse_path_absolute(str: &Vec<char>, start: usize) -> Option<(Range<usize>, u } } - return Some((start..pos, pos)); + return Some(pos); } -fn parse_path_abempty(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_path_abempty(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; loop { if let Some(ch) = str.get(pos) && *ch == '/' { - if let Some((_, npos)) = parse_segment(str, pos+1) { + if let Some(npos) = parse_segment(str, pos+1) { pos = npos; } else { break; @@ -1326,41 +1063,26 @@ fn parse_path_abempty(str: &Vec<char>, start: usize) -> Option<(Range<usize>, us } } - return Some((start..pos, pos)); + return Some(pos); } -fn parse_relative_part(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_relative_part(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; - if let Some((_, npos)) = parse_relative_part_1(str, pos) { + if let Some(npos) = parse_relative_part_1(str, pos) { pos = npos; - return Some((start..pos, pos)); + return Some(pos); } - if let Some((_, npos)) = parse_path_absolute(str, pos) { + if let Some(npos) = parse_path_absolute(str, pos) { pos = npos; - return Some((start..pos, pos)); + return Some(pos); } return None; } -#[cfg(test)] -mod test_relative_part { - use super::*; - - #[test] - fn absolute_path() { - let data = "/foo?".chars().collect(); - - let (range, pos) = parse_relative_part(&data, 0).unwrap(); - assert_eq!(pos, 4); - assert_eq!(range.start, 0); - assert_eq!(range.end, 4); - } -} - -fn parse_query(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_query(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; if start >= str.len() { @@ -1368,7 +1090,7 @@ fn parse_query(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { } loop { - if let Some((_, npos)) = parse_pchar(str, pos) { + if let Some(npos) = parse_pchar(str, pos) { pos = npos; } else if let Some(ch) = str.get(pos) && *ch == '/' { pos += 1; @@ -1379,10 +1101,10 @@ fn parse_query(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { } } - return Some((start..pos, pos)); + return Some(pos); } -fn parse_fragment(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_fragment(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; if start >= str.len() { @@ -1390,7 +1112,7 @@ fn parse_fragment(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize) } loop { - if let Some((_, npos)) = parse_pchar(str, pos) { + if let Some(npos) = parse_pchar(str, pos) { pos = npos; } else if let Some(ch) = str.get(pos) && (*ch == '/' || *ch == '?') { pos += 1; @@ -1399,90 +1121,37 @@ fn parse_fragment(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize) } } - return Some((start..pos, pos)); + return Some(pos); } // RFC 3986 -fn parse_relative_ref(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_relative_ref(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; - let (_, npos) = parse_relative_part(str, pos)?; + let npos = parse_relative_part(str, pos)?; pos = npos; - if let Some(ch) = str.get(pos) && *ch == '?' && let Some((_, npos)) = parse_query(str, pos+1) { + if let Some(ch) = str.get(pos) && *ch == '?' && let Some(npos) = parse_query(str, pos+1) { pos = npos; } - if let Some(ch) = str.get(pos) && *ch == '#' && let Some((_, npos)) = parse_fragment(str, pos+1) { + if let Some(ch) = str.get(pos) && *ch == '#' && let Some(npos) = parse_fragment(str, pos+1) { pos = npos; } - return Some((start..pos, pos)); + return Some(pos); } -#[cfg(test)] -mod test_relative_ref { - use super::*; - - #[test] - fn reject_not_starting_slash() { - let data = vec!['?']; - - let res = parse_relative_ref(&data, 0); - assert!(res.is_none()); - } - - #[test] - fn simple() { - let data = vec!['/', '/', 'h', 'e', 'l', 'l', 'o', '/']; - - let (range, pos) = parse_relative_ref(&data, 0).unwrap(); - assert_eq!(pos, 8); - assert_eq!(range.start, 0); - assert_eq!(range.end, 8); - } - - #[test] - fn with_query_parameter() { - let data = "/foo?bar=baz".chars().collect(); - - let (range, pos) = parse_relative_ref(&data, 0).unwrap(); - assert_eq!(pos, 12); - 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)> { +fn parse_uri(str: &Vec<char>, start: usize) -> Option<usize> { let mut pos = start; - let(_, npos) = parse_scheme(str, pos)?; + let npos = parse_scheme(str, pos)?; pos = npos; - return Some((start..pos, pos)); + return Some(pos); } -fn parse_exact_string(str: &Vec<char>, start: usize, to_match: &str) -> Option<(Range<usize>, usize)> { +fn parse_exact_string(str: &Vec<char>, start: usize, to_match: &str) -> Option<usize> { let mut pos = start; let end = pos + to_match.len(); @@ -1495,171 +1164,117 @@ fn parse_exact_string(str: &Vec<char>, start: usize, to_match: &str) -> Option<( } pos += to_match.len(); - return Some((start..pos, pos)); -} - -fn parse_loalpha(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - match str.get(start) { - Some('a'..='z') => Some((start..start+1, start+1)), - _ => None, - } + return Some(pos); } -fn parse_relation_type(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { +fn parse_link_param(str: &Vec<char>, start: usize, res: &mut PartialNextPage) -> Option<usize> { let mut pos = start; - let (_, npos) = parse_loalpha(str, pos)?; - pos = npos; + let mut is_rel = false; - loop { - if let Some((_, npos)) = parse_loalpha(str, pos) { - pos = npos; - continue; - } else if let Some((_, npos)) = parse_digit(str, pos) { - pos = npos; - continue; - } else if let Some((_, npos)) = parse_exact_string(str, pos, ".") { - pos = npos; - continue; - } else if let Some((_, npos)) = parse_exact_string(str, pos, "-") { - pos = npos; - continue; - } else { - break; - } + if let Some(npos) = parse_exact_string(str, pos, "rel") { + is_rel = true; + pos = npos; + } else if let Some(npos) = parse_token(str, pos) { + pos = npos; + } else { + return None; } - return Some((start..pos, pos)); -} - -fn parse_relation_types(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - let mut pos = start; - - if let Some((_, npos)) = parse_relation_type(str, pos) { + // @CORRECT This part is actually supposed to be optional + if let Some(npos) = parse_ows(str, pos) { pos = npos; + } - return Some((start..pos, pos)); - } else if let Some((_, npos)) = parse_exact_string(str, pos, "\"") { + if let Some(npos) = parse_exact_string(str, pos, "=") { pos = npos; - - if let Some((_, npos)) = parse_relation_type(str, pos) { - pos = npos; - - loop { - if let Some((_, npos)) = parse_sp(str, pos) { - - loop { - if let Some((_, npos)) = parse_sp(str, pos) { - pos = npos; - } else { - break; - } - } - - pos = npos; - if let Some((_, npos)) = parse_relation_type(str, pos) { - pos = npos; - continue; - } - } - break; - } - - if let Some((_, npos)) = parse_exact_string(str, pos, "\"") { - pos = npos; - return Some((start+1..pos-1, pos)); - } - } + } else { + return None; } - return None; -} - -fn parse_link_param(str: &Vec<char>, start: usize) -> Option<(LinkParam, usize)> { - let mut pos = start; - - // Non-standard but seen in the wild - if let Some((_, npos)) = parse_sp(str, pos) { + if let Some(npos) = parse_ows(str, pos) { pos = npos; } - if let Some((_, npos)) = parse_exact_string(str, pos, "rel=") { - if let Some((relation, npos)) = parse_relation_types(str, npos) { - pos = npos; - return Some((LinkParam::Rel{relation}, pos)); - } + // @INCOMPLETE This is not exhaustive + if is_rel && let Some(npos) = parse_exact_string(str, pos, "\"next\"") { + res.found_next_uri = Some(res.current_uri.clone()); + pos = npos; + } else if let Some(npos) = parse_token(str, pos) { + pos = npos; + } else if let Some(npos) = parse_quotedstring(str, pos, &mut None) { + pos = npos; + } else { + return None; } - return None; + return Some(pos); } -fn parse_link_value(str: &Vec<char>, start: usize) -> Option<(LinkValue, usize)> { +fn parse_link_value(str: &Vec<char>, start: usize, res: &mut PartialNextPage) -> Option<usize> { let mut pos = start; - if let Some((_, npos)) = parse_exact_string(str, pos, "<") { + if let Some(npos) = parse_exact_string(str, pos, "<") { pos = npos; } else { return None; } - let uri_range; - let mut params = vec!(); - - if let Some((range, npos)) = parse_uri(str, pos) { - uri_range = range; + res.current_uri.start = pos; + if let Some(npos) = parse_uri(str, pos) { pos = npos; - } else if let Some((range, npos)) = parse_relative_ref(str, pos) { - uri_range = range; + } else if let Some(npos) = parse_relative_ref(str, pos) { pos = npos; } else { return None; } + res.current_uri.end = pos; - if let Some((_, npos)) = parse_exact_string(str, pos, ">") { + if let Some(npos) = parse_exact_string(str, pos, ">") { pos = npos; } else { return None; } loop { - if let Some((_, npos)) = parse_exact_string(str, pos, ";") { + if let Some(npos) = parse_exact_string(str, pos, ";") { pos = npos; } else { break; } - if let Some((param, npos)) = parse_link_param(str, pos) { + if let Some(npos) = parse_ows(str, pos) { + pos = npos; + } + + if let Some(npos) = parse_link_param(str, pos, res) { pos = npos; - params.push(param); } else { break; } } - return Some((LinkValue{ uri: uri_range, params: params }, pos)); + return Some(pos); } -pub fn parse_link(str: &Vec<char>, start: usize) -> Option<(Vec<LinkValue>, usize)> { +fn parse_link_into(str: &Vec<char>, start: usize, res: &mut PartialNextPage) -> Option<usize> { let mut pos = start; - let mut values = vec!(); - loop { match parse_lws(str, pos) { - Some((_, npos)) => pos = npos, + Some(npos) => pos = npos, None => break, } } - let (param, npos) = parse_link_value(str, pos)?; - values.push(param); + let npos = parse_link_value(str, pos, res)?; pos = npos; loop { let mut ipos = pos; loop { match parse_lws(str, ipos) { - Some((_, npos)) => ipos = npos, + Some(npos) => ipos = npos, None => break, } } @@ -1671,15 +1286,14 @@ pub fn parse_link(str: &Vec<char>, start: usize) -> Option<(Vec<LinkValue>, usiz loop { match parse_lws(str, ipos) { - Some((_, npos)) => ipos = npos, + Some(npos) => ipos = npos, None => break, } } - match parse_link_value(str, ipos) { - Some((param, npos)) => { + match parse_link_value(str, ipos, res) { + Some(npos) => { ipos = npos; - values.push(param); }, None => { break; @@ -1690,194 +1304,34 @@ pub fn parse_link(str: &Vec<char>, start: usize) -> Option<(Vec<LinkValue>, usiz } - return Some((values, pos)); + return Some(pos); } -#[cfg(test)] -mod test_link { - use super::*; - - #[test] - fn parses_relative_authority_only() { - let data: Vec<char> = "<//example.com>".chars().collect(); - - let (links, pos) = parse_link(&data, 0).unwrap(); - - assert_eq!(pos, 15); - assert_eq!(links.len(), 1); - assert_eq!(links[0].uri, 1..14); - assert_eq!(links[0].params.len(), 0); - } - - #[test] - fn parses_relative_absolute_path() { - let data: Vec<char> = "</path/to/resource>".chars().collect(); - - let (links, pos) = parse_link(&data, 0).unwrap(); - - assert_eq!(pos, 19); - assert_eq!(links.len(), 1); - assert_eq!(links[0].uri, 1..18); - } - - #[test] - fn parses_relative_with_query() { - let data: Vec<char> = "</foo?bar=baz>".chars().collect(); - - let (links, pos) = parse_link(&data, 0).unwrap(); - - assert_eq!(pos, 14); - assert_eq!(links.len(), 1); - assert_eq!(links[0].uri, 1..13); - } - - #[test] - fn parses_rel_param_unquoted() { - let data: Vec<char> = "<//example.com>; rel=preconnect".chars().collect(); - - let (links, pos) = parse_link(&data, 0).unwrap(); - - assert_eq!(pos, 31); - - match &links[0].params[0] { - LinkParam::Rel { relation } => { - assert_eq!(relation.clone(), 21..31); - } - _ => assert!(false), - } - } - - #[test] - fn parses_multiple_links_with_lws() { - let data: Vec<char> = "<//a>; rel=preconnect, <//b>".chars().collect(); - - let (links, pos) = parse_link(&data, 0).unwrap(); - - assert_eq!(pos, 28); - assert_eq!(links.len(), 2); - - match &links[0].params[0] { - LinkParam::Rel { relation } => { - assert_eq!(relation.clone(), 11..21); - } - } - - assert_eq!(links[1].params.len(), 0); - assert_eq!(links[0].uri, 1..4); - assert_eq!(links[1].uri, 24..27); - } - - #[test] - fn parses_lws_between_link_values() { - let data: Vec<char> = "<//a>,\r\n <//b>".chars().collect(); - - let (links, pos) = parse_link(&data, 0).unwrap(); - - assert_eq!(pos, 14); - assert_eq!(links.len(), 2); - assert_eq!(links[0].uri, 1..4); - assert_eq!(links[1].uri, 10..13); - } - - #[test] - // Is this correct? - fn rejects_absolute_uri() { - let data: Vec<char> = "<http://example.com>".chars().collect(); - - let res = parse_link(&data, 0); - assert!(res.is_none()); - } - - #[test] - fn rejects_missing_closing_delimiter() { - let data: Vec<char> = "<//example.com".chars().collect(); - - let res = parse_link(&data, 0); - assert!(res.is_none()); - } +#[derive(Default)] +struct PartialNextPage { + current_uri: Range<usize>, - #[test] - fn parses_quoted_rel_multiple_values() { - let data: Vec<char> = "<//example.com>; rel=\"preconnect dns-prefetch\"".chars().collect(); - - let (links, pos) = parse_link(&data, 0).unwrap(); - - assert_eq!(pos, data.len()); - assert_eq!(links.len(), 1); - assert_eq!(links[0].params.len(), 1); - - match &links[0].params[0] { - LinkParam::Rel { relation } => { - assert_eq!(relation.clone(), 22..45); - } - } - } - - #[test] - #[ignore = "absolute URI targets are not parsed yet"] - fn parses_absolute_uri_with_rel() { - let data: Vec<char> = "<http://example.com>; rel=preconnect".chars().collect(); - - let (links, pos) = parse_link(&data, 0).unwrap(); + found_next_uri: Option<Range<usize>>, +} - assert_eq!(pos, 35); - assert_eq!(links.len(), 1); - assert_eq!(links[0].uri, 1..19); - match &links[0].params[0] { - LinkParam::Rel { relation } => assert_eq!(relation.clone(), 22..32), +impl Into<NextPage> for PartialNextPage { + fn into(self) -> NextPage { + return NextPage { + next_uri: self.found_next_uri, } } +} - #[test] - #[ignore = "additional parameters beyond rel= are not parsed"] - fn parses_title_parameter() { - let data: Vec<char> = "<//example.com>; rel=preconnect; title=\"Example\"".chars().collect(); - - let (links, pos) = parse_link(&data, 0).unwrap(); - - assert_eq!(pos, 57); - assert_eq!(links.len(), 1); - assert_eq!(links[0].params.len(), 2); - } - - #[test] - #[ignore = "extended title* parameter not supported"] - fn parses_title_star_parameter() { - let data: Vec<char> = "<//example.com>; rel=preconnect; title*=UTF-8''Ex%C3%A4mple".chars().collect(); - - let (links, pos) = parse_link(&data, 0).unwrap(); - - assert_eq!(pos, 65); - assert_eq!(links.len(), 1); - } - - #[test] - #[ignore = "anchor parameter not parsed"] - fn parses_anchor_parameter() { - let data: Vec<char> = "<//example.com>; rel=preconnect; anchor=\"/base\"".chars().collect(); - - let (links, pos) = parse_link(&data, 0).unwrap(); - - assert_eq!(pos, 56); - assert_eq!(links.len(), 1); - } - - #[test] - #[ignore = "hreflang parameter not parsed"] - fn parses_hreflang_parameter() { - let data: Vec<char> = "<//example.com>; rel=preconnect; hreflang=en".chars().collect(); +pub struct NextPage { + pub next_uri: Option<Range<usize>> +} - let (links, pos) = parse_link(&data, 0).unwrap(); +pub fn extract_next_page(header: &str) -> Option<NextPage> { + let mut partial = Default::default(); - assert_eq!(pos, 53); - assert_eq!(links.len(), 1); - } + let data = header.chars().collect(); - #[test] - fn rejects_percent_encoded_path() { - let data: Vec<char> = "</pa%20th>".chars().collect(); + parse_link_into(&data, 0, &mut partial)?; - let res = parse_link(&data, 0); - assert!(res.is_some()); - } + return Some(partial.into()); } |
