diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.rs | 723 | ||||
| -rw-r--r-- | src/parser.rs | 1048 |
2 files changed, 1090 insertions, 681 deletions
diff --git a/src/main.rs b/src/main.rs index d06fa9a..dd0041c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,6 @@ +mod parser; +use crate::parser::*; + use base64::prelude::*; use yaml_rust2::parser::Parser; use yaml_rust2::Event; @@ -16,6 +19,45 @@ Options: ); } +#[derive(Debug)] +enum AuthMethod { + Basic, + Bearer{realm: String, service: String, scope: String}, +} + +impl AuthMethod { + fn from_header(header: &ureq::http::HeaderValue) -> Option<Self> { + let header_str = header.to_str().unwrap(); + let (parse, _) = parse_challenge(&header_str.chars().collect::<Vec<char>>(), 0).unwrap(); + + if &header_str[parse.scheme.clone()] == "Basic" { + return Some(AuthMethod::Basic); + } + + if &header_str[parse.scheme.clone()] == "Bearer" { + let mut realm_param = None; + let mut scope_param = None; + let mut service_param = None; + for param in parse.params { + match &header_str[param.key.clone()] { + "realm" => realm_param = Some(param), + "scope" => scope_param = Some(param), + "service" => service_param = Some(param), + _ => {}, + } + } + + return Some(AuthMethod::Bearer{ + realm: header_str[realm_param.unwrap().value].to_string(), + scope: header_str[scope_param.unwrap().value].to_string(), + service: header_str[service_param.unwrap().value].to_string(), + }); + } + + return None; + } +} + struct AuthInfo { host: String, username: String, @@ -109,687 +151,6 @@ impl AuthState{ } } -#[derive(Debug)] -enum AuthMethod { - Basic, - Bearer{realm: String, service: String, scope: String}, -} - -#[derive(Debug)] -struct Challenge { - scheme: Range<usize>, - params: Vec<AuthParam>, -} - -fn parse_token(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - let mut pos = start; - while str.len() > pos { - match str[pos] { - 'a'..'z' | 'A'..'Z' => pos += 1, - _ => break, - } - } - - if pos == start { - return None; - } - - return Some((start..pos, pos)); -} - -fn parse_qdtext(str: &Vec<char>, start: usize) -> Option<usize> { - match str[start] { - '\x00'..'\x1F' | '"' | '\x7F' => return None, - _ => return Some(start + 1), - } -} - -fn parse_quotedpair(str: &Vec<char>, start: usize) -> Option<usize> { - if str[start] != '\\' { - return None; - } - - return Some(start + 2); -} - -fn parse_quotedstring(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - let mut pos = start; - if str[pos] != '"' { - return None; - } - pos += 1; - - loop { - if let Some(npos) = parse_qdtext(str, pos) { - pos = npos; - } else if let Some(npos) = parse_quotedpair(str, pos) { - pos = npos; - } else { - break; - } - } - - if str[pos] != '"' { - return None; - } - pos += 1; - - return Some((start+1..pos-1, pos)); -} - -fn parse_sp(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - if str.len() <= start { - return None; - } - - let mut pos = start; - match str[pos] { - ' ' => pos += 1, - _ => return None, - } - - if pos == start { - return None; - } - - return Some((start..pos, pos)); -} - -fn parse_crlf(str: &Vec<char>, start: usize) -> Option<usize> { - if str.len() - 2 < start { - return None; - } - - if str[start] == '\r' && str[start+1] == '\n' { - return Some(start + 2); - } - - return None; -} - -fn parse_lws(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - let mut pos = start; - - match parse_crlf(str, pos) { - Some(npos) => pos = npos, - None => {}, - } - - match parse_sp(str, pos) { - Some((_, npos)) => pos = npos, - None => return None, - } - loop { - match parse_sp(str, pos) { - Some((_, npos)) => pos = npos, - None => break, - } - } - - return Some((start..pos, pos)); -} - -#[derive(Debug)] -struct AuthParam { - key: Range<usize>, - value: Range<usize>, -} - -fn parse_param(str: &Vec<char>, start: usize) -> Option<(AuthParam, usize)> { - let pos = start; - let (param_range, pos) = parse_token(str, pos)?; - - let mut pos = pos; - if str[pos] == '=' { - pos += 1; - } else { - todo!(); - } - - let value_range; - if let Some((range, npos)) = parse_token(str, pos) { - value_range = range; - pos = npos; - } else if let Some((range, npos)) = parse_quotedstring(str, pos) { - value_range = range; - pos = npos; - } else { - todo!("{:?}", &str[pos..]); - } - - return Some((AuthParam{key: param_range, value: value_range}, pos)); -} - -fn parse_challenge(str: &Vec<char>, start: usize) -> Option<(Challenge, usize)> { - let pos = start; - let (scheme_range, pos) = parse_token(str, pos).unwrap(); - - let (_, pos) = parse_sp(str, pos).unwrap(); - let mut pos = pos; - loop { - match parse_sp(str, pos) { - Some((_, npos)) => pos = npos, - None => break, - } - } - - let mut params = vec!(); - - let mut pos = pos; - loop { - match parse_lws(str, pos) { - Some((_, npos)) => pos = npos, - None => break, - } - } - let (param, pos) = parse_param(str, pos).unwrap(); - params.push(param); - let mut pos = pos; - loop { - loop { - match parse_lws(str, pos) { - Some((_, npos)) => pos = npos, - None => break, - } - } - - if pos >= str.len() || str[pos] != ',' { - break; - } - pos += 1; - - loop { - match parse_lws(str, pos) { - Some((_, npos)) => pos = npos, - None => break, - } - } - - match parse_param(str, pos) { - Some((param, npos)) => { - pos = npos; - params.push(param); - }, - None => return None, - } - } - - if pos < str.len() { - return None; - } - return Some((Challenge {scheme: scheme_range, params}, pos)); -} - -impl AuthMethod { - fn from_header(header: &ureq::http::HeaderValue) -> Option<Self> { - let header_str = header.to_str().unwrap(); - let (parse, _) = parse_challenge(&header_str.chars().collect::<Vec<char>>(), 0).unwrap(); - - if &header_str[parse.scheme.clone()] == "Basic" { - return Some(AuthMethod::Basic); - } - - if &header_str[parse.scheme.clone()] == "Bearer" { - let mut realm_param = None; - let mut scope_param = None; - let mut service_param = None; - for param in parse.params { - match &header_str[param.key.clone()] { - "realm" => realm_param = Some(param), - "scope" => scope_param = Some(param), - "service" => service_param = Some(param), - _ => {}, - } - } - - return Some(AuthMethod::Bearer{ - realm: header_str[realm_param.unwrap().value].to_string(), - scope: header_str[scope_param.unwrap().value].to_string(), - service: header_str[service_param.unwrap().value].to_string(), - }); - } - - return None; - } -} - -#[derive(Debug)] -enum LinkParam { - Rel{relation: Range<usize>}, -} - -impl LinkParam { - fn value(&self) -> &Range<usize> { - match self { - LinkParam::Rel{relation} => &relation, - } - } -} - -#[derive(Debug)] -struct LinkValue { - uri: Range<usize>, - params: Vec<LinkParam>, -} - -fn parse_alpha(str: &Vec<char>, pos: usize) -> Option<(Range<usize>, usize)> { - return match str[pos] { - 'a'..'z' | 'A'..'Z' => Some((pos..pos+1, pos+1)), - _ => None, - } -} - -fn parse_digit(str: &Vec<char>, pos: usize) -> Option<(Range<usize>, usize)> { - return match str[pos] { - '0'..'9' => Some((pos..pos+1, pos+1)), - _ => None, - } -} - -fn parse_scheme(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - let mut pos = start; - - 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_authority(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - return None; -} - -fn parse_relative_part_1(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - let mut pos = start; - - if str[pos] != '/' && str[pos+1] != '/' { - return None; - } - pos += 2; - - let (_, npos) = parse_authority(str, pos)?; - pos = npos; - - return Some((start..pos, pos)); -} - -fn parse_unreserved(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - if let Some(x) = parse_alpha(str, start) { - return Some(x); - } - - if let Some(x) = parse_digit(str, start) { - return Some(x); - } - - match str[start] { - '-' | '.' | '_' | '~' => Some((start..start+1, start+1)), - _ => None - } -} - -fn parse_sub_delims(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - match str[start] { - '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | ';' | '=' => Some((start..start+1, start+1)), - _ => None - } - -} - -fn parse_pchar(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - if let Some(x) = parse_unreserved(str, start) { - return Some(x); - } - - if let Some(x) = parse_sub_delims(str, start) { - return Some(x); - } - - return None; -} - -fn parse_segment_nz(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - let mut pos = start; - - let (_, npos) = parse_pchar(str, pos)?; - pos = npos; - - loop { - if let Some((_, npos)) = parse_pchar(str, pos) { - pos = npos; - } else { - break; - } - } - - return Some((start..pos, pos)); -} - -fn parse_segment(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - let mut pos = start; - - loop { - if let Some((_, npos)) = parse_pchar(str, pos) { - pos = npos; - } else { - break; - } - } - - return Some((start..pos, pos)); -} - -fn parse_path_absolute(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - let mut pos = start; - - if str[pos] != '/' { - return None; - } - pos += 1; - - if let Some((_, npos)) = parse_segment_nz(str, pos) { - pos = npos; - - loop { - if str[pos] != '/' { - break; - } - - if let Some((_, npos)) = parse_segment(str, pos+1) { - pos = npos; - } else { - break; - } - } - } - - return Some((start..pos, pos)); -} - -fn parse_relative_part(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - let mut pos = start; - - if let Some((_, npos)) = parse_relative_part_1(str, pos) { - pos = npos; - return Some((start..pos, pos)); - } - - if let Some((_, npos)) = parse_path_absolute(str, pos) { - pos = npos; - return Some((start..pos, pos)); - } - - return None; -} - -fn parse_query(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - let mut pos = start; - - loop { - if let Some((_, npos)) = parse_pchar(str, pos) { - pos = npos; - } else if str[pos] == '/' { - pos += 1; - } else if str[pos] == '?' { - pos += 1; - } else { - break; - } - } - - return Some((start..pos, pos)); -} - -fn parse_fragment(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - return None; -} - -fn parse_relative_ref(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - let mut pos = start; - - let (_, npos) = parse_relative_part(str, pos)?; - pos = npos; - - if str[pos] == '?' && let Some((_, npos)) = parse_query(str, pos+1) { - pos = npos; - } - - if str[pos] == '#' && let Some((_, npos)) = parse_fragment(str, pos+1) { - pos = npos; - } - - return Some((start..pos, pos)); -} - -fn parse_uri(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - let mut pos = start; - - let(_, npos) = parse_scheme(str, pos)?; - pos = npos; - - return Some((start..pos, pos)); -} - -fn parse_exact_string(str: &Vec<char>, start: usize, to_match: &str) -> Option<(Range<usize>, usize)> { - let mut pos = start; - let end = pos + to_match.len(); - - if end > str.len() { - return None; - } - - if !to_match.chars().eq(str[start..start+to_match.len()].iter().map(|x| *x)) { - return None; - } - pos += to_match.len(); - - return Some((start..pos, pos)); -} - -fn parse_loalpha(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - match str[start] { - 'a'..'z' => Some((start..start+1, start+1)), - _ => None, - } -} - -fn parse_relation_type(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { - let mut pos = start; - - let (_, npos) = parse_loalpha(str, pos)?; - pos = npos; - - loop { - dbg!(&str[pos..]); - 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; - } - } - - 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) { - pos = npos; - - return Some((start..pos, pos)); - } else 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)); - } - } - } - - 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) { - 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)); - } - } - - return None; -} - -fn parse_link_value(str: &Vec<char>, start: usize) -> Option<(LinkValue, usize)> { - let mut pos = start; - - if str[pos] == '<' { - pos += 1; - } else { - return None; - } - - let uri_range; - let mut params = vec!(); - - if let Some((range, npos)) = parse_uri(str, pos) { - uri_range = range; - pos = npos; - } else if let Some((range, npos)) = parse_relative_ref(str, pos) { - uri_range = range; - pos = npos; - } else { - return None; - } - - if str[pos] == '>' { - pos += 1; - } else { - return None; - } - - loop { - if let Some((_, npos)) = parse_exact_string(str, pos, ";") { - pos = npos; - } else { - break; - } - - if let Some((param, npos)) = parse_link_param(str, pos) { - pos = npos; - params.push(param); - } else { - break; - } - } - - return Some((LinkValue{ uri: uri_range, params: params }, pos)); -} - -fn parse_link(str: &Vec<char>, start: usize) -> Option<(Vec<LinkValue>, usize)> { - let mut pos = start; - - let mut values = vec!(); - - loop { - match parse_lws(str, pos) { - Some((_, npos)) => pos = npos, - None => break, - } - } - - let (param, npos) = parse_link_value(str, pos)?; - values.push(param); - pos = npos; - - loop { - let mut ipos = pos; - loop { - match parse_lws(str, ipos) { - Some((_, npos)) => ipos = npos, - None => break, - } - } - - if ipos >= str.len() || str[ipos] != ',' { - break; - } - ipos += 1; - - loop { - match parse_lws(str, ipos) { - Some((_, npos)) => ipos = npos, - None => break, - } - } - - match parse_link_value(str, ipos) { - Some((param, npos)) => { - ipos = npos; - values.push(param); - }, - None => { - break; - }, - } - - pos = ipos; - } - - - return Some((values, pos)); -} - fn perform_registry_request(registry: &str, url: &str, auth: &mut Auth) -> Result<ureq::http::Response<ureq::Body>, ()> { let mut state = auth.states.get_mut(registry); diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..19f019a --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,1048 @@ +use std::ops::Range; + +#[derive(Debug)] +pub struct Challenge { + pub scheme: Range<usize>, + pub params: Vec<AuthParam>, +} + +// RFC 7230 +fn parse_token(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + let mut pos = start; + while str.len() > pos { + match str[pos] { + // RFC 5234 + 'a'..='z' | 'A'..='Z' => pos += 1, + '0'..='9' => pos += 1, + // RFC 7230 + '!' | '#' | '$' | '%' | '&' | '\'' | '*' | '+' | '-' | '.' | '^' | '_' | '`' | '|' | '~' + => pos += 1, + _ => break, + } + } + + // Must have matched 1 + if pos == start { + 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); + } +} + +fn parse_qdtext(str: &Vec<char>, start: usize) -> Option<usize> { + match str[start] { + '\x00'..='\x1F' | '"' | '\x7F' => return None, + _ => return Some(start + 1), + } +} + +fn parse_quotedpair(str: &Vec<char>, start: usize) -> Option<usize> { + if str[start] != '\\' { + return None; + } + + return Some(start + 2); +} + +fn parse_quotedstring(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + let mut pos = start; + if str[pos] != '"' { + return None; + } + pos += 1; + + loop { + if let Some(npos) = parse_qdtext(str, pos) { + pos = npos; + } else if let Some(npos) = parse_quotedpair(str, pos) { + pos = npos; + } else { + break; + } + } + + if str[pos] != '"' { + return None; + } + pos += 1; + + return Some((start+1..pos-1, pos)); +} + +fn parse_sp(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + if str.len() <= start { + return None; + } + + let mut pos = start; + match str[pos] { + ' ' => pos += 1, + _ => return None, + } + + if pos == start { + return None; + } + + return Some((start..pos, pos)); +} + +fn parse_crlf(str: &Vec<char>, start: usize) -> Option<usize> { + if str.len() - 2 < start { + return None; + } + + if str[start] == '\r' && str[start+1] == '\n' { + return Some(start + 2); + } + + return None; +} + +fn parse_lws(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + let mut pos = start; + + match parse_crlf(str, pos) { + Some(npos) => pos = npos, + None => {}, + } + + match parse_sp(str, pos) { + Some((_, npos)) => pos = npos, + None => return None, + } + loop { + match parse_sp(str, pos) { + Some((_, npos)) => pos = npos, + None => break, + } + } + + return Some((start..pos, pos)); +} + +#[derive(Debug)] +pub struct AuthParam { + pub key: Range<usize>, + pub value: Range<usize>, +} + +fn parse_param(str: &Vec<char>, start: usize) -> Option<(AuthParam, usize)> { + let pos = start; + let (param_range, pos) = parse_token(str, pos)?; + + let mut pos = pos; + if str[pos] == '=' { + pos += 1; + } else { + todo!(); + } + + let value_range; + if let Some((range, npos)) = parse_token(str, pos) { + value_range = range; + pos = npos; + } else if let Some((range, npos)) = parse_quotedstring(str, pos) { + value_range = range; + pos = npos; + } else { + todo!("{:?}", &str[pos..]); + } + + return Some((AuthParam{key: param_range, value: value_range}, 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(); + + let (_, pos) = parse_sp(str, pos).unwrap(); + let mut pos = pos; + loop { + match parse_sp(str, pos) { + Some((_, npos)) => pos = npos, + None => break, + } + } + + let mut params = vec!(); + + let mut pos = pos; + loop { + match parse_lws(str, pos) { + Some((_, npos)) => pos = npos, + None => break, + } + } + let (param, pos) = parse_param(str, pos).unwrap(); + params.push(param); + let mut pos = pos; + loop { + loop { + match parse_lws(str, pos) { + Some((_, npos)) => pos = npos, + None => break, + } + } + + if pos >= str.len() || str[pos] != ',' { + break; + } + pos += 1; + + loop { + match parse_lws(str, pos) { + Some((_, npos)) => pos = npos, + None => break, + } + } + + match parse_param(str, pos) { + Some((param, npos)) => { + pos = npos; + params.push(param); + }, + None => return None, + } + } + + 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, + } + } +} + +#[derive(Debug)] +pub struct LinkValue { + uri: Range<usize>, + pub params: Vec<LinkParam>, +} + +fn parse_alpha(str: &Vec<char>, pos: usize) -> Option<(Range<usize>, usize)> { + if pos >= str.len() { + return None; + } + + return match str[pos] { + 'a'..='z' | 'A'..='Z' => Some((pos..pos+1, pos+1)), + _ => None, + } +} + +fn parse_digit(str: &Vec<char>, pos: usize) -> Option<(Range<usize>, usize)> { + if pos >= str.len() { + return None; + } + + return match str[pos] { + '0'..='9' => Some((pos..pos+1, pos+1)), + _ => None, + } +} + +fn parse_scheme(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + let mut pos = start; + + 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<char>, start: usize) -> Option<(Range<usize>, 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_ip_literal(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + return None; +} + +fn parse_port(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + return None; +} + +fn parse_host(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + let mut pos = start; + + if let Some((_, npos)) = parse_ip_literal(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)); + } + + return None; +} + +fn parse_userinfo(str: &Vec<char>, start: usize) -> Option<(Range<usize>, 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 if let Some(ch) = str.get(pos) && *ch == ':' { + pos += 1; + } else { + break; + } + } + + return Some((start..pos, pos)); +} + +fn parse_authority(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + let mut pos = start; + + 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) { + pos = npos; + } else { + return None; + } + + if let Some(ch) = str.get(pos) && *ch == ':' { + if let Some((_, npos)) = parse_port(str, pos) { + pos = npos; + } + } + + return Some((start..pos, pos)); +} + +fn parse_relative_part_1(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + let mut pos = start; + + if str[pos] != '/' || str[pos+1] != '/' { + return None; + } + pos += 2; + + let (_, npos) = parse_authority(str, pos)?; + pos = npos; + + let (_, npos) = parse_path_abempty(str, pos)?; + pos = npos; + + return Some((start..pos, pos)); +} + +fn parse_unreserved(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + if let Some(x) = parse_alpha(str, start) { + return Some(x); + } + + if let Some(x) = parse_digit(str, start) { + return Some(x); + } + + if start >= str.len() { + return None; + } + match str[start] { + '-' | '.' | '_' | '~' => Some((start..start+1, start+1)), + _ => None + } +} + +fn parse_pct_encoded(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + return None; +} + +fn parse_sub_delims(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + if start >= str.len() { + return None; + } + + match str[start] { + '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | ';' | '=' => Some((start..start+1, start+1)), + _ => None + } + +} + +fn parse_pchar(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + if let Some(x) = parse_unreserved(str, start) { + return Some(x); + } + + if let Some(x) = parse_sub_delims(str, start) { + return Some(x); + } + + return None; +} + +fn parse_segment_nz(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + let mut pos = start; + + let (_, npos) = parse_pchar(str, pos)?; + pos = npos; + + loop { + if let Some((_, npos)) = parse_pchar(str, pos) { + pos = npos; + } else { + break; + } + } + + return Some((start..pos, pos)); +} + +fn parse_segment(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + let mut pos = start; + + loop { + if let Some((_, npos)) = parse_pchar(str, pos) { + pos = npos; + } else { + break; + } + } + + return Some((start..pos, pos)); +} + +fn parse_path_absolute(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + let mut pos = start; + + if str[pos] != '/' { + return None; + } + pos += 1; + + if let Some((_, npos)) = parse_segment_nz(str, pos) { + pos = npos; + + loop { + if str[pos] != '/' { + break; + } + + if let Some((_, npos)) = parse_segment(str, pos+1) { + pos = npos; + } else { + break; + } + } + } + + return Some((start..pos, pos)); +} + +fn parse_path_abempty(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + let mut pos = start; + + loop { + if let Some(ch) = str.get(pos) && *ch == '/' { + if let Some((_, npos)) = parse_segment(str, pos+1) { + pos = npos; + } else { + break; + } + } else { + break; + } + } + + return Some((start..pos, pos)); +} + +fn parse_relative_part(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + let mut pos = start; + + if let Some((_, npos)) = parse_relative_part_1(str, pos) { + pos = npos; + return Some((start..pos, pos)); + } + + if let Some((_, npos)) = parse_path_absolute(str, pos) { + pos = npos; + return Some((start..pos, 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)> { + 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 == '/' { + pos += 1; + } else if let Some(ch) = str.get(pos) && *ch == '?' { + pos += 1; + } else { + break; + } + } + + return Some((start..pos, pos)); +} + +fn parse_fragment(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + return None; +} + +// RFC 3986 +fn parse_relative_ref(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + let mut pos = start; + + 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) { + pos = npos; + } + + if let Some(ch) = str.get(pos) && *ch == '#' && let Some((_, npos)) = parse_fragment(str, pos+1) { + pos = npos; + } + + return Some((start..pos, 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); + } +} + +fn parse_uri(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + let mut pos = start; + + let(_, npos) = parse_scheme(str, pos)?; + pos = npos; + + return Some((start..pos, pos)); +} + +fn parse_exact_string(str: &Vec<char>, start: usize, to_match: &str) -> Option<(Range<usize>, usize)> { + let mut pos = start; + let end = pos + to_match.len(); + + if end > str.len() { + return None; + } + + if !to_match.chars().eq(str[start..start+to_match.len()].iter().map(|x| *x)) { + return None; + } + 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, + } +} + +fn parse_relation_type(str: &Vec<char>, start: usize) -> Option<(Range<usize>, usize)> { + let mut pos = start; + + let (_, npos) = parse_loalpha(str, pos)?; + pos = npos; + + 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; + } + } + + 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) { + pos = npos; + + return Some((start..pos, pos)); + } else 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)); + } + } + } + + 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) { + 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)); + } + } + + return None; +} + +fn parse_link_value(str: &Vec<char>, start: usize) -> Option<(LinkValue, usize)> { + let mut pos = start; + + 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; + pos = npos; + } else if let Some((range, npos)) = parse_relative_ref(str, pos) { + uri_range = range; + pos = npos; + } else { + return None; + } + + if let Some((_, npos)) = parse_exact_string(str, pos, ">") { + pos = npos; + } else { + return None; + } + + loop { + if let Some((_, npos)) = parse_exact_string(str, pos, ";") { + pos = npos; + } else { + break; + } + + if let Some((param, npos)) = parse_link_param(str, pos) { + pos = npos; + params.push(param); + } else { + break; + } + } + + return Some((LinkValue{ uri: uri_range, params: params }, pos)); +} + +pub fn parse_link(str: &Vec<char>, start: usize) -> Option<(Vec<LinkValue>, usize)> { + let mut pos = start; + + let mut values = vec!(); + + loop { + match parse_lws(str, pos) { + Some((_, npos)) => pos = npos, + None => break, + } + } + + let (param, npos) = parse_link_value(str, pos)?; + values.push(param); + pos = npos; + + loop { + let mut ipos = pos; + loop { + match parse_lws(str, ipos) { + Some((_, npos)) => ipos = npos, + None => break, + } + } + + if ipos >= str.len() || str[ipos] != ',' { + break; + } + ipos += 1; + + loop { + match parse_lws(str, ipos) { + Some((_, npos)) => ipos = npos, + None => break, + } + } + + match parse_link_value(str, ipos) { + Some((param, npos)) => { + ipos = npos; + values.push(param); + }, + None => { + break; + }, + } + + pos = ipos; + } + + + return Some((values, 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()); + } + + #[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(); + + 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), + } + } + + #[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(); + + let (links, pos) = parse_link(&data, 0).unwrap(); + + assert_eq!(pos, 53); + assert_eq!(links.len(), 1); + } + + #[test] + #[ignore = "percent-encoded paths are not supported"] + fn rejects_percent_encoded_path() { + let data: Vec<char> = "</pa%20th>".chars().collect(); + + let res = parse_link(&data, 0); + assert!(res.is_some()); + } +} |
