diff options
-rw-r--r-- | rustfmt.toml | 33 | ||||
-rw-r--r-- | src/authority.rs | 130 | ||||
-rw-r--r-- | src/character_classes.rs | 154 | ||||
-rw-r--r-- | src/codec.rs | 35 | ||||
-rw-r--r-- | src/context.rs | 41 | ||||
-rw-r--r-- | src/error.rs | 1 | ||||
-rw-r--r-- | src/lib.rs | 22 | ||||
-rw-r--r-- | src/parse_host_port.rs | 117 | ||||
-rw-r--r-- | src/percent_encoded_character_decoder.rs | 17 | ||||
-rw-r--r-- | src/uri.rs | 814 | ||||
-rw-r--r-- | src/validate_ipv4_address.rs | 43 | ||||
-rw-r--r-- | src/validate_ipv6_address.rs | 133 |
12 files changed, 964 insertions, 576 deletions
diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..0acf7d0 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,33 @@ +# A lot of the styles we want can only be configured +# using unstable features. +# +# If you have trouble running rustfmt, try "cargo +nightly fmt". +unstable_features = true + +edition = "2018" +fn_args_layout = "Vertical" +format_code_in_doc_comments = true +imports_layout = "Vertical" +match_block_trailing_comma = true +max_width = 80 +merge_imports = true +normalize_comments = true +normalize_doc_attributes = true +overflow_delimited_expr = true +reorder_impl_items = true +struct_lit_single_line = false +use_field_init_shorthand = true +use_small_heuristics = "Off" +use_try_shorthand = true +wrap_comments = true + +# This one isn't released yet. +#fn_call_width = 40 + +# These are already set to the defaults, but they are things +# we really want to make sure are set, just in case defaults change. +reorder_imports = true +reorder_modules = true +space_after_colon = true +space_before_colon = false +tab_spaces = 4 diff --git a/src/authority.rs b/src/authority.rs index bde7a06..8ac8dd7 100644 --- a/src/authority.rs +++ b/src/authority.rs @@ -1,12 +1,17 @@ -use super::character_classes::{ - REG_NAME_NOT_PCT_ENCODED, - USER_INFO_NOT_PCT_ENCODED, +use super::{ + character_classes::{ + REG_NAME_NOT_PCT_ENCODED, + USER_INFO_NOT_PCT_ENCODED, + }, + codec::{ + decode_element, + encode_element, + }, + context::Context, + error::Error, + parse_host_port::parse_host_port, + validate_ipv6_address::validate_ipv6_address, }; -use super::codec::{decode_element, encode_element}; -use super::context::Context; -use super::error::Error; -use super::parse_host_port::parse_host_port; -use super::validate_ipv6_address::validate_ipv6_address; /// This is the optional part of a URI which governs the URI's namespace. It /// typically contains a host name or IP address, and may also include a port @@ -65,21 +70,30 @@ impl Authority { } /// Change the userinfo part of the Authority. - pub fn set_userinfo<T>(&mut self, userinfo: T) - where T: Into<Option<Vec<u8>>> + pub fn set_userinfo<T>( + &mut self, + userinfo: T, + ) where + T: Into<Option<Vec<u8>>>, { self.userinfo = userinfo.into(); } /// Change the host name part of the Authority. - pub fn set_host<T>(&mut self, host: T) - where T: Into<Vec<u8>> + pub fn set_host<T>( + &mut self, + host: T, + ) where + T: Into<Vec<u8>>, { self.host = host.into(); } /// Change the port number part of the Authority. - pub fn set_port(&mut self, port: Option<u16>) { + pub fn set_port( + &mut self, + port: Option<u16>, + ) { self.port = port; } @@ -100,50 +114,62 @@ impl Authority { /// [`Error`](enum.Error.html) type. #[must_use = "you parsed it; don't you want the results?"] pub fn parse<T>(authority_string: T) -> Result<Self, Error> - where T: AsRef<str> + where + T: AsRef<str>, { - let (userinfo, host_port_string) = Self::parse_userinfo(authority_string.as_ref())?; + let (userinfo, host_port_string) = + Self::parse_userinfo(authority_string.as_ref())?; let (host, port) = parse_host_port(host_port_string)?; - Ok(Self{ + Ok(Self { userinfo, host, port, }) } - fn parse_userinfo(authority: &str) -> Result<(Option<Vec<u8>>, &str), Error> { + fn parse_userinfo( + authority: &str + ) -> Result<(Option<Vec<u8>>, &str), Error> { Ok(match authority.find('@') { Some(delimiter) => ( - Some( - decode_element( - &authority[0..delimiter], - &USER_INFO_NOT_PCT_ENCODED, - Context::Userinfo - )? - ), - &authority[delimiter+1..] + Some(decode_element( + &authority[0..delimiter], + &USER_INFO_NOT_PCT_ENCODED, + Context::Userinfo, + )?), + &authority[delimiter + 1..], ), - None => ( - None, - authority - ) + None => (None, authority), }) } } impl std::fmt::Display for Authority { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { if let Some(userinfo) = &self.userinfo { - write!(f, "{}@", encode_element(&userinfo, &USER_INFO_NOT_PCT_ENCODED))?; + write!( + f, + "{}@", + encode_element(&userinfo, &USER_INFO_NOT_PCT_ENCODED) + )?; } let host_to_string = String::from_utf8(self.host.clone()); match host_to_string { - Ok(host_to_string) if validate_ipv6_address(&host_to_string).is_ok() => { + Ok(host_to_string) + if validate_ipv6_address(&host_to_string).is_ok() => + { write!(f, "[{}]", host_to_string.to_ascii_lowercase())?; - }, - _ => { - write!(f, "{}", encode_element(&self.host, ®_NAME_NOT_PCT_ENCODED))?; } + _ => { + write!( + f, + "{}", + encode_element(&self.host, ®_NAME_NOT_PCT_ENCODED) + )?; + }, } if let Some(port) = self.port { write!(f, ":{}", port)?; @@ -168,7 +194,8 @@ mod tests { let test_vectors: &[TestVector] = &[ ("www.example.com", None).into(), ("joe@www.example.com", Some("joe")).into(), - ("pepe:feelsbadman@www.example.com", Some("pepe:feelsbadman")).into(), + ("pepe:feelsbadman@www.example.com", Some("pepe:feelsbadman")) + .into(), ]; for test_vector in test_vectors { let authority = Authority::parse(test_vector.authority_string()); @@ -183,10 +210,7 @@ mod tests { #[test] fn userinfo_illegal_characters() { - let test_vectors = [ - "%X@www.example.com", - "{@www.example.com", - ]; + let test_vectors = ["%X@www.example.com", "{@www.example.com"]; for test_vector in &test_vectors { let authority = Authority::parse(test_vector); assert!(authority.is_err()); @@ -198,7 +222,7 @@ mod tests { named_tuple!( struct TestVector { uri_string: &'static str, - userinfo: &'static str + userinfo: &'static str, } ); let test_vectors: &[TestVector] = &[ @@ -223,11 +247,7 @@ mod tests { #[test] fn host_illegal_characters() { - let test_vectors = [ - "%X@www.example.com", - "@www:example.com", - "[vX.:]", - ]; + let test_vectors = ["%X@www.example.com", "@www:example.com", "[vX.:]"]; for test_vector in &test_vectors { let authority = Authority::parse(test_vector); assert!(authority.is_err()); @@ -239,7 +259,7 @@ mod tests { named_tuple!( struct TestVector { authority_string: &'static str, - host: &'static str + host: &'static str, } ); let test_vectors: &[TestVector] = &[ @@ -257,10 +277,7 @@ mod tests { let authority = Authority::parse(test_vector.authority_string()); assert!(authority.is_ok()); let authority = authority.unwrap(); - assert_eq!( - test_vector.host().as_bytes(), - authority.host() - ); + assert_eq!(test_vector.host().as_bytes(), authority.host()); } } @@ -269,10 +286,7 @@ mod tests { let authority = Authority::parse("example.com."); assert!(authority.is_ok()); let authority = authority.unwrap(); - assert_eq!( - b"example.com.", - authority.host() - ); + assert_eq!(b"example.com.", authority.host()); } #[test] @@ -289,11 +303,7 @@ mod tests { let authority = Authority::parse(*test_vector); assert!(authority.is_ok()); let authority = authority.unwrap(); - assert_eq!( - normalized_host.as_bytes(), - authority.host() - ); + assert_eq!(normalized_host.as_bytes(), authority.host()); } } - } diff --git a/src/character_classes.rs b/src/character_classes.rs index a9e7b37..72ec6c3 100644 --- a/src/character_classes.rs +++ b/src/character_classes.rs @@ -3,127 +3,117 @@ use std::collections::HashSet; // This is the character set containing just the alphabetic characters // from the ASCII character set. -pub static ALPHA: Lazy<HashSet<char>> = Lazy::new(|| - ('a'..='z') - .chain('A'..='Z') - .collect() -); +pub static ALPHA: Lazy<HashSet<char>> = + Lazy::new(|| ('a'..='z').chain('A'..='Z').collect()); // This is the character set containing just numbers. -pub static DIGIT: Lazy<HashSet<char>> = Lazy::new(|| - ('0'..='9') - .collect() -); +pub static DIGIT: Lazy<HashSet<char>> = Lazy::new(|| ('0'..='9').collect()); // This is the character set containing just the characters allowed // in a hexadecimal digit. -pub static HEXDIG: Lazy<HashSet<char>> = Lazy::new(|| - DIGIT.iter().copied() - .chain('A'..='F') - .chain('a'..='f') - .collect() -); +pub static HEXDIG: Lazy<HashSet<char>> = Lazy::new(|| { + DIGIT.iter().copied().chain('A'..='F').chain('a'..='f').collect() +}); // This is the character set corresponds to the "unreserved" syntax // specified in RFC 3986 (https://tools.ietf.org/html/rfc3986). -pub static UNRESERVED: Lazy<HashSet<char>> = Lazy::new(|| - ALPHA.iter() - .chain(DIGIT.iter()) - .chain(['-', '.', '_', '~'].iter()) - .copied() - .collect() -); +pub static UNRESERVED: Lazy<HashSet<char>> = Lazy::new(|| { + ALPHA + .iter() + .chain(DIGIT.iter()) + .chain(['-', '.', '_', '~'].iter()) + .copied() + .collect() +}); // This is the character set corresponds to the "sub-delims" syntax // specified in RFC 3986 (https://tools.ietf.org/html/rfc3986). -pub static SUB_DELIMS: Lazy<HashSet<char>> = Lazy::new(|| - [ - '!', '$', '&', '\'', '(', ')', - '*', '+', ',', ';', '=' - ] - .iter() - .copied() - .collect() -); +pub static SUB_DELIMS: Lazy<HashSet<char>> = Lazy::new(|| { + ['!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '='] + .iter() + .copied() + .collect() +}); // This is the character set corresponds to the second part // of the "scheme" syntax // specified in RFC 3986 (https://tools.ietf.org/html/rfc3986). -pub static SCHEME_NOT_FIRST: Lazy<HashSet<char>> = Lazy::new(|| - ALPHA.iter() - .chain(DIGIT.iter()) - .chain(['+', '-', '.'].iter()) - .copied() - .collect() -); +pub static SCHEME_NOT_FIRST: Lazy<HashSet<char>> = Lazy::new(|| { + ALPHA + .iter() + .chain(DIGIT.iter()) + .chain(['+', '-', '.'].iter()) + .copied() + .collect() +}); // This is the character set corresponds to the "pchar" syntax // specified in RFC 3986 (https://tools.ietf.org/html/rfc3986), // leaving out "pct-encoded". -pub static PCHAR_NOT_PCT_ENCODED: Lazy<HashSet<char>> = Lazy::new(|| - UNRESERVED.iter() - .chain(SUB_DELIMS.iter()) - .chain([':', '@'].iter()) - .copied() - .collect() -); +pub static PCHAR_NOT_PCT_ENCODED: Lazy<HashSet<char>> = Lazy::new(|| { + UNRESERVED + .iter() + .chain(SUB_DELIMS.iter()) + .chain([':', '@'].iter()) + .copied() + .collect() +}); // This is the character set corresponds to the "query" syntax // and the "fragment" syntax // specified in RFC 3986 (https://tools.ietf.org/html/rfc3986), // leaving out "pct-encoded". -pub static QUERY_OR_FRAGMENT_NOT_PCT_ENCODED: Lazy<HashSet<char>> = Lazy::new(|| - PCHAR_NOT_PCT_ENCODED.iter() - .chain(['/', '?'].iter()) - .copied() - .collect() -); +pub static QUERY_OR_FRAGMENT_NOT_PCT_ENCODED: Lazy<HashSet<char>> = + Lazy::new(|| { + PCHAR_NOT_PCT_ENCODED.iter().chain(['/', '?'].iter()).copied().collect() + }); // This is the character set almost corresponds to the "query" syntax // specified in RFC 3986 (https://tools.ietf.org/html/rfc3986), // leaving out "pct-encoded", except that '+' is also excluded, because // for some web services (e.g. AWS S3) a '+' is treated as // synonymous with a space (' ') and thus gets misinterpreted. -pub static QUERY_NOT_PCT_ENCODED_WITHOUT_PLUS: Lazy<HashSet<char>> = Lazy::new(|| - UNRESERVED.iter() - .chain([ - '!', '$', '&', '\'', '(', ')', - '*', ',', ';', '=', - ':', '@', - '/', '?' - ].iter()) - .copied() - .collect() -); +pub static QUERY_NOT_PCT_ENCODED_WITHOUT_PLUS: Lazy<HashSet<char>> = + Lazy::new(|| { + UNRESERVED + .iter() + .chain( + [ + '!', '$', '&', '\'', '(', ')', '*', ',', ';', '=', ':', + '@', '/', '?', + ] + .iter(), + ) + .copied() + .collect() + }); // This is the character set corresponds to the "userinfo" syntax // specified in RFC 3986 (https://tools.ietf.org/html/rfc3986), // leaving out "pct-encoded". -pub static USER_INFO_NOT_PCT_ENCODED: Lazy<HashSet<char>> = Lazy::new(|| - UNRESERVED.iter() - .chain(SUB_DELIMS.iter()) - .chain([':'].iter()) - .copied() - .collect() -); +pub static USER_INFO_NOT_PCT_ENCODED: Lazy<HashSet<char>> = Lazy::new(|| { + UNRESERVED + .iter() + .chain(SUB_DELIMS.iter()) + .chain([':'].iter()) + .copied() + .collect() +}); // This is the character set corresponds to the "reg-name" syntax // specified in RFC 3986 (https://tools.ietf.org/html/rfc3986), // leaving out "pct-encoded". -pub static REG_NAME_NOT_PCT_ENCODED: Lazy<HashSet<char>> = Lazy::new(|| - UNRESERVED.iter() - .chain(SUB_DELIMS.iter()) - .copied() - .collect() -); +pub static REG_NAME_NOT_PCT_ENCODED: Lazy<HashSet<char>> = + Lazy::new(|| UNRESERVED.iter().chain(SUB_DELIMS.iter()).copied().collect()); // This is the character set corresponds to the last part of // the "IPvFuture" syntax // specified in RFC 3986 (https://tools.ietf.org/html/rfc3986). -pub static IPV_FUTURE_LAST_PART: Lazy<HashSet<char>> = Lazy::new(|| - UNRESERVED.iter() - .chain(SUB_DELIMS.iter()) - .chain([':'].iter()) - .copied() - .collect() -); +pub static IPV_FUTURE_LAST_PART: Lazy<HashSet<char>> = Lazy::new(|| { + UNRESERVED + .iter() + .chain(SUB_DELIMS.iter()) + .chain([':'].iter()) + .copied() + .collect() +}); diff --git a/src/codec.rs b/src/codec.rs index 1002b8e..cbf0495 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -1,17 +1,22 @@ -use std::collections::HashSet; -use std::convert::TryFrom; -use std::fmt::Write; +use std::{ + collections::HashSet, + convert::TryFrom, + fmt::Write, +}; -use super::context::Context; -use super::error::Error; -use super::percent_encoded_character_decoder::PercentEncodedCharacterDecoder; +use super::{ + context::Context, + error::Error, + percent_encoded_character_decoder::PercentEncodedCharacterDecoder, +}; pub fn decode_element<T>( element: T, allowed_characters: &'static HashSet<char>, - context: Context + context: Context, ) -> Result<Vec<u8>, Error> - where T: AsRef<str> +where + T: AsRef<str>, { let mut decoding_pec = false; let mut pec_decoder = PercentEncodedCharacterDecoder::new(); @@ -20,14 +25,10 @@ pub fn decode_element<T>( .chars() .filter_map(|c| { if decoding_pec { - pec_decoder - .next(c) - .map_err(Into::into) - .transpose() - .map(|c| { - decoding_pec = false; - c - }) + pec_decoder.next(c).map_err(Into::into).transpose().map(|c| { + decoding_pec = false; + c + }) } else if c == '%' { decoding_pec = true; None @@ -42,7 +43,7 @@ pub fn decode_element<T>( pub fn encode_element( element: &[u8], - allowed_characters: &HashSet<char> + allowed_characters: &HashSet<char>, ) -> String { let mut encoding = String::with_capacity(element.len()); for ci in element { diff --git a/src/context.rs b/src/context.rs index bb6667e..4a50446 100644 --- a/src/context.rs +++ b/src/context.rs @@ -40,35 +40,20 @@ pub enum Context { } impl std::fmt::Display for Context { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { match self { - Context::Fragment => { - write!(f, "fragment") - }, - Context::Host => { - write!(f, "host") - }, - Context::Ipv4Address => { - write!(f, "IPv4 address") - }, - Context::Ipv6Address => { - write!(f, "IPv6 address") - }, - Context::IpvFuture => { - write!(f, "IPvFuture") - }, - Context::Path => { - write!(f, "path") - }, - Context::Query => { - write!(f, "query") - }, - Context::Scheme => { - write!(f, "scheme") - }, - Context::Userinfo => { - write!(f, "user info") - }, + Context::Fragment => write!(f, "fragment"), + Context::Host => write!(f, "host"), + Context::Ipv4Address => write!(f, "IPv4 address"), + Context::Ipv6Address => write!(f, "IPv6 address"), + Context::IpvFuture => write!(f, "IPvFuture"), + Context::Path => write!(f, "path"), + Context::Query => write!(f, "query"), + Context::Scheme => write!(f, "scheme"), + Context::Userinfo => write!(f, "user info"), } } } diff --git a/src/error.rs b/src/error.rs index 44bd8b6..dca1fc7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -59,4 +59,3 @@ pub enum Error { #[error("truncated host")] TruncatedHost, } - @@ -28,7 +28,10 @@ //! let uri = Uri::parse("http://www.example.com/foo?bar#baz").unwrap(); //! let authority = uri.authority().unwrap(); //! assert_eq!("www.example.com".as_bytes(), authority.host()); -//! assert_eq!(Some("www.example.com"), uri.host_to_string().unwrap().as_deref()); +//! assert_eq!( +//! Some("www.example.com"), +//! uri.host_to_string().unwrap().as_deref() +//! ); //! assert_eq!("/foo", uri.path_to_string().unwrap()); //! assert_eq!(Some("bar"), uri.query_to_string().unwrap().as_deref()); //! assert_eq!(Some("baz"), uri.fragment_to_string().unwrap().as_deref()); @@ -38,7 +41,10 @@ //! //! ```rust //! # extern crate rhymuri; -//! use rhymuri::{Authority, Uri}; +//! use rhymuri::{ +//! Authority, +//! Uri, +//! }; //! //! let mut uri = Uri::default(); //! assert!(uri.set_scheme(String::from("http")).is_ok()); @@ -68,11 +74,13 @@ mod context; mod error; mod parse_host_port; mod percent_encoded_character_decoder; +mod uri; mod validate_ipv4_address; mod validate_ipv6_address; -mod uri; -pub use crate::authority::Authority; -pub use crate::context::Context; -pub use crate::error::Error; -pub use crate::uri::Uri; +pub use crate::{ + authority::Authority, + context::Context, + error::Error, + uri::Uri, +}; diff --git a/src/parse_host_port.rs b/src/parse_host_port.rs index abb34e1..c5cdcc6 100644 --- a/src/parse_host_port.rs +++ b/src/parse_host_port.rs @@ -1,14 +1,16 @@ use std::convert::TryFrom; -use super::character_classes::{ - HEXDIG, - IPV_FUTURE_LAST_PART, - REG_NAME_NOT_PCT_ENCODED, +use super::{ + character_classes::{ + HEXDIG, + IPV_FUTURE_LAST_PART, + REG_NAME_NOT_PCT_ENCODED, + }, + context::Context, + error::Error, + percent_encoded_character_decoder::PercentEncodedCharacterDecoder, + validate_ipv6_address::validate_ipv6_address, }; -use super::context::Context; -use super::error::Error; -use super::percent_encoded_character_decoder::PercentEncodedCharacterDecoder; -use super::validate_ipv6_address::validate_ipv6_address; struct Shared { host: Vec<u8>, @@ -28,7 +30,7 @@ enum State { Port(Shared), } -impl State{ +impl State { fn finalize(self) -> Result<(Vec<u8>, Option<u16>), Error> { match self { Self::PercentEncodedCharacter(_) @@ -49,12 +51,10 @@ impl State{ None } else { match state.port_string.parse::<u16>() { - Ok(port) => { - Some(port) - }, + Ok(port) => Some(port), Err(error) => { return Err(Error::IllegalPortNumber(error)); - } + }, } }; Ok((state.host, port)) @@ -63,7 +63,7 @@ impl State{ } fn new(host_port_string: &str) -> (Self, &str) { - let mut shared = Shared{ + let mut shared = Shared { host: Vec::<u8>::new(), host_is_reg_name: false, ipv6_address: String::new(), @@ -74,38 +74,39 @@ impl State{ if host_port_string.starts_with("[v") { host_port_string = &host_port_string[2..]; shared.host.push(b'v'); - ( - Self::IpvFutureNumber(shared), - host_port_string - ) + (Self::IpvFutureNumber(shared), host_port_string) } else if host_port_string.starts_with('[') { host_port_string = &host_port_string[1..]; - ( - Self::Ipv6Address(shared), - host_port_string - ) + (Self::Ipv6Address(shared), host_port_string) } else { shared.host_is_reg_name = true; - ( - Self::NotIpLiteral(shared), - host_port_string - ) + (Self::NotIpLiteral(shared), host_port_string) } } - fn next(self, c: char) -> Result<Self, Error> { + fn next( + self, + c: char, + ) -> Result<Self, Error> { match self { Self::NotIpLiteral(state) => Self::next_not_ip_literal(state, c), - Self::PercentEncodedCharacter(state) => Self::next_percent_encoded_character(state, c), + Self::PercentEncodedCharacter(state) => { + Self::next_percent_encoded_character(state, c) + }, Self::Ipv6Address(state) => Self::next_ipv6_address(state, c), - Self::IpvFutureNumber(state) => Self::next_ipv_future_number(state, c), + Self::IpvFutureNumber(state) => { + Self::next_ipv_future_number(state, c) + }, Self::IpvFutureBody(state) => Self::next_ipv_future_body(state, c), Self::GarbageCheck(state) => Self::next_garbage_check(state, c), Self::Port(state) => Self::next_port(state, c), } } - fn next_not_ip_literal(state: Shared, c: char) -> Result<Self, Error> { + fn next_not_ip_literal( + state: Shared, + c: char, + ) -> Result<Self, Error> { let mut state = state; if c == '%' { Ok(Self::PercentEncodedCharacter(state)) @@ -119,7 +120,10 @@ impl State{ } } - fn next_percent_encoded_character(state: Shared, c: char) -> Result<Self, Error> { + fn next_percent_encoded_character( + state: Shared, + c: char, + ) -> Result<Self, Error> { let mut state = state; // We can't use `Option::map_or` (or `Option::map_or_else`, for similar // reasons) in this case because the closure would take ownership of @@ -134,13 +138,18 @@ impl State{ } } - fn next_ipv6_address(state: Shared, c: char) -> Result<Self, Error> { + fn next_ipv6_address( + state: Shared, + c: char, + ) -> Result<Self, Error> { let mut state = state; if c == ']' { validate_ipv6_address(&state.ipv6_address)?; - state.host = state.ipv6_address.chars().map( - |c| u8::try_from(c as u32).unwrap() - ).collect(); + state.host = state + .ipv6_address + .chars() + .map(|c| u8::try_from(c as u32).unwrap()) + .collect(); Ok(Self::GarbageCheck(state)) } else { state.ipv6_address.push(c); @@ -148,7 +157,10 @@ impl State{ } } - fn next_ipv_future_number(state: Shared, c: char) -> Result<Self, Error> { + fn next_ipv_future_number( + state: Shared, + c: char, + ) -> Result<Self, Error> { let mut state = state; if c == '.' { state.host.push(b'.'); @@ -163,7 +175,10 @@ impl State{ } } - fn next_ipv_future_body(state: Shared, c: char) -> Result<Self, Error> { + fn next_ipv_future_body( + state: Shared, + c: char, + ) -> Result<Self, Error> { let mut state = state; if c == ']' { Ok(Self::GarbageCheck(state)) @@ -175,7 +190,10 @@ impl State{ } } - fn next_garbage_check(state: Shared, c: char) -> Result<Self, Error> { + fn next_garbage_check( + state: Shared, + c: char, + ) -> Result<Self, Error> { // illegal to have anything else, unless it's a colon, // in which case it's a port delimiter if c == ':' { @@ -185,22 +203,26 @@ impl State{ } } - fn next_port(state: Shared, c: char) -> Result<Self, Error> { + fn next_port( + state: Shared, + c: char, + ) -> Result<Self, Error> { let mut state = state; state.port_string.push(c); Ok(Self::Port(state)) } } -pub fn parse_host_port<T>(host_port_string: T) -> Result<(Vec<u8>, Option<u16>), Error> - where T: AsRef<str> +pub fn parse_host_port<T>( + host_port_string: T +) -> Result<(Vec<u8>, Option<u16>), Error> +where + T: AsRef<str>, { let (machine, host_port_string) = State::new(host_port_string.as_ref()); host_port_string .chars() - .try_fold(machine, |machine, c| { - machine.next(c) - })? + .try_fold(machine, |machine, c| machine.next(c))? .finalize() } @@ -270,11 +292,7 @@ mod tests { #[test] fn truncated_host() { - let test_vectors = [ - "[::ffff:1.2.3.4/", - "[:]/", - "[v]/", - ]; + let test_vectors = ["[::ffff:1.2.3.4/", "[:]/", "[v]/"]; for test_vector in &test_vectors { assert_eq!( Err(Error::TruncatedHost), @@ -292,5 +310,4 @@ mod tests { Err(Error::IllegalPortNumber(_)) )); } - } diff --git a/src/percent_encoded_character_decoder.rs b/src/percent_encoded_character_decoder.rs index fc2e9ef..70a229e 100644 --- a/src/percent_encoded_character_decoder.rs +++ b/src/percent_encoded_character_decoder.rs @@ -9,7 +9,7 @@ pub struct PercentEncodedCharacterDecoder { impl PercentEncodedCharacterDecoder { pub fn new() -> Self { - Self{ + Self { decoded_character: 0, digits_left: 2, } @@ -17,7 +17,7 @@ impl PercentEncodedCharacterDecoder { pub fn next( &mut self, - c: char + c: char, ) -> Result<Option<u8>, Error> { self.shift_in_hex_digit(c)?; self.digits_left -= 1; @@ -37,7 +37,7 @@ impl PercentEncodedCharacterDecoder { fn shift_in_hex_digit( &mut self, - c: char + c: char, ) -> Result<(), Error> { self.decoded_character <<= 4; if let Some(ci) = c.to_digit(16) { @@ -48,7 +48,6 @@ impl PercentEncodedCharacterDecoder { } Ok(()) } - } #[cfg(test)] @@ -73,10 +72,7 @@ mod tests { ]; for test_vector in test_vectors { let mut pec = PercentEncodedCharacterDecoder::new(); - assert_eq!( - Ok(None), - pec.next(test_vector.sequence()[0]) - ); + assert_eq!(Ok(None), pec.next(test_vector.sequence()[0])); assert_eq!( Ok(Some(*test_vector.expected_output())), pec.next(test_vector.sequence()[1]) @@ -86,13 +82,10 @@ mod tests { #[test] fn bad_sequences() { - let test_vectors = [ - 'G', 'g', '.', 'z', '-', ' ', 'V', - ]; + let test_vectors = ['G', 'g', '.', 'z', '-', ' ', 'V']; for test_vector in &test_vectors { let mut pec = PercentEncodedCharacterDecoder::new(); assert!(pec.next(*test_vector).is_err()); } } - } @@ -1,15 +1,20 @@ use std::collections::HashSet; -use super::authority::Authority; -use super::codec::{decode_element, encode_element}; -use super::context::Context; -use super::error::Error; -use super::character_classes::{ - ALPHA, - SCHEME_NOT_FIRST, - PCHAR_NOT_PCT_ENCODED, - QUERY_OR_FRAGMENT_NOT_PCT_ENCODED, - QUERY_NOT_PCT_ENCODED_WITHOUT_PLUS, +use super::{ + authority::Authority, + character_classes::{ + ALPHA, + PCHAR_NOT_PCT_ENCODED, + QUERY_NOT_PCT_ENCODED_WITHOUT_PLUS, + QUERY_OR_FRAGMENT_NOT_PCT_ENCODED, + SCHEME_NOT_FIRST, + }, + codec::{ + decode_element, + encode_element, + }, + context::Context, + error::Error, }; /// This type is used to parse and generate URI strings to and from their @@ -55,7 +60,10 @@ use super::character_classes::{ /// /// ```rust /// # extern crate rhymuri; -/// use rhymuri::{Authority, Uri}; +/// use rhymuri::{ +/// Authority, +/// Uri, +/// }; /// /// let mut uri = Uri::default(); /// assert!(uri.set_scheme(String::from("http")).is_ok()); @@ -96,7 +104,8 @@ impl Uri { } fn can_navigate_path_up_one_level<T>(path: T) -> bool - where T: AsRef<[Vec<u8>]> + where + T: AsRef<[Vec<u8>]>, { let path = path.as_ref(); match path.first() { @@ -107,19 +116,18 @@ impl Uri { // Otherwise, we can navigate up as long as there is at least one // segment. Some(_) => true, - None => false + None => false, } } fn check_scheme<T>(scheme: T) -> Result<T, Error> - where T: AsRef<str> + where + T: AsRef<str>, { match scheme.as_ref() { "" => return Err(Error::EmptyScheme), - scheme => scheme - .chars() - .enumerate() - .try_fold((), |_, (i, c)| { + scheme => { + scheme.chars().enumerate().try_fold((), |_, (i, c)| { let valid_characters: &HashSet<char> = if i == 0 { &ALPHA } else { @@ -130,7 +138,8 @@ impl Uri { } else { Err(Error::IllegalCharacter(Context::Scheme)) } - })?, + })? + }, }; Ok(scheme) } @@ -146,12 +155,13 @@ impl Uri { query_or_fragment: T, context: Context, ) -> Result<Vec<u8>, Error> - where T: AsRef<str> + where + T: AsRef<str>, { decode_element( query_or_fragment, &QUERY_OR_FRAGMENT_NOT_PCT_ENCODED, - context + context, ) } @@ -166,13 +176,13 @@ impl Uri { /// # Errors /// /// Since fragments may contain non-UTF8 byte sequences, this function may - /// return [`Error::CannotExpressAsUtf8`](enum.Error.html#variant.CannotExpressAsUtf8). + /// return [`Error::CannotExpressAsUtf8`](enum.Error.html#variant. + /// CannotExpressAsUtf8). #[must_use = "use the fragment return value silly programmer"] pub fn fragment_to_string(&self) -> Result<Option<String>, Error> { self.fragment() .map(|fragment| { - String::from_utf8(fragment.to_vec()) - .map_err(Into::into) + String::from_utf8(fragment.to_vec()).map_err(Into::into) }) .transpose() } @@ -180,9 +190,7 @@ impl Uri { /// Borrow the host portion of the Authority (if any) of the URI. #[must_use = "why u no use host return value?"] pub fn host(&self) -> Option<&[u8]> { - self.authority - .as_ref() - .map(Authority::host) + self.authority.as_ref().map(Authority::host) } /// Convert the host portion of the Authority (if any) into a string. @@ -191,19 +199,18 @@ impl Uri { /// /// Since host names may contain non-UTF8 byte sequences, this function may /// return - /// [`Error::CannotExpressAsUtf8`](enum.Error.html#variant.CannotExpressAsUtf8). + /// [`Error::CannotExpressAsUtf8`](enum.Error.html#variant. + /// CannotExpressAsUtf8). #[must_use = "I made that host field into a string for you; don't you want it?"] pub fn host_to_string(&self) -> Result<Option<String>, Error> { self.host() - .map(|host| { - String::from_utf8(host.to_vec()) - .map_err(Into::into) - }) + .map(|host| String::from_utf8(host.to_vec()).map_err(Into::into)) .transpose() } fn is_path_absolute<T>(path: T) -> bool - where T: AsRef<[Vec<u8>]> + where + T: AsRef<[Vec<u8>]>, { matches!(path.as_ref(), [segment, ..] if segment.is_empty()) } @@ -241,7 +248,8 @@ impl Uri { } fn normalize_path<T>(original_path: T) -> Vec<Vec<u8>> - where T: AsRef<[Vec<u8>]> + where + T: AsRef<[Vec<u8>]>, { // Rebuild the path one segment // at a time, removing and applying special @@ -257,8 +265,7 @@ impl Uri { } else if segment == b".." { // Remove last path element // if we can navigate up a level. - if - !normalized_path.is_empty() + if !normalized_path.is_empty() && Self::can_navigate_path_up_one_level(&normalized_path) { normalized_path.pop(); @@ -286,7 +293,7 @@ impl Uri { (true, Some(segment)) if !segment.is_empty() => { normalized_path.push(vec![]); }, - _ => () + _ => (), } normalized_path } @@ -300,46 +307,45 @@ impl Uri { /// let you know what's up by returning a variant of the /// [`Error`](enum.Error.html) type. pub fn parse<T>(uri_string: T) -> Result<Self, Error> - where T: AsRef<str> + where + T: AsRef<str>, { let (scheme, rest) = Self::parse_scheme(uri_string.as_ref())?; - let path_end = rest - .find(&['?', '#'][..]) - .unwrap_or_else(|| rest.len()); + let path_end = rest.find(&['?', '#'][..]).unwrap_or_else(|| rest.len()); let authority_and_path_string = &rest[0..path_end]; let query_and_or_fragment = &rest[path_end..]; - let (authority, path) = Self::split_authority_from_path_and_parse_them(authority_and_path_string)?; - let (fragment, possible_query) = Self::parse_fragment(query_and_or_fragment)?; + let (authority, path) = Self::split_authority_from_path_and_parse_them( + authority_and_path_string, + )?; + let (fragment, possible_query) = + Self::parse_fragment(query_and_or_fragment)?; let query = Self::parse_query(possible_query)?; - Ok(Self{ + Ok(Self { scheme, authority, path, query, - fragment + fragment, }) } - fn parse_fragment(query_and_or_fragment: &str) -> Result<(Option<Vec<u8>>, &str), Error> { + fn parse_fragment( + query_and_or_fragment: &str + ) -> Result<(Option<Vec<u8>>, &str), Error> { if let Some(fragment_delimiter) = query_and_or_fragment.find('#') { let fragment = Self::decode_query_or_fragment( - &query_and_or_fragment[fragment_delimiter+1..], - Context::Fragment + &query_and_or_fragment[fragment_delimiter + 1..], + Context::Fragment, )?; - Ok(( - Some(fragment), - &query_and_or_fragment[0..fragment_delimiter] - )) + Ok((Some(fragment), &query_and_or_fragment[0..fragment_delimiter])) } else { - Ok(( - None, - query_and_or_fragment - )) + Ok((None, query_and_or_fragment)) } } fn parse_path<T>(path_string: T) -> Result<Vec<Vec<u8>>, Error> - where T: AsRef<str> + where + T: AsRef<str>, { match path_string.as_ref() { "/" => { @@ -355,23 +361,24 @@ impl Uri { Ok(vec![]) }, - path_string => { - path_string - .split('/') - .map(|segment| { - decode_element( - &segment, - &PCHAR_NOT_PCT_ENCODED, - Context::Path - ) - }) - .collect() - } + path_string => path_string + .split('/') + .map(|segment| { + decode_element( + &segment, + &PCHAR_NOT_PCT_ENCODED, + Context::Path, + ) + }) + .collect(), } } - fn parse_query<T>(query_and_or_fragment: T) -> Result<Option<Vec<u8>>, Error> - where T: AsRef<str> + fn parse_query<T>( + query_and_or_fragment: T + ) -> Result<Option<Vec<u8>>, Error> + where + T: AsRef<str>, { let query_and_or_fragment = query_and_or_fragment.as_ref(); if query_and_or_fragment.is_empty() { @@ -379,7 +386,7 @@ impl Uri { } else { let query = Self::decode_query_or_fragment( &query_and_or_fragment[1..], - Context::Query + Context::Query, )?; Ok(Some(query)) } @@ -390,12 +397,14 @@ impl Uri { // or path elements, because these may have the colon // character as well, which we might misinterpret // as the scheme delimiter. - let authority_or_path_delimiter_start = uri_string.find('/') - .unwrap_or_else(|| uri_string.len()); - if let Some(scheme_end) = &uri_string[0..authority_or_path_delimiter_start].find(':') { - let scheme = Self::check_scheme(&uri_string[0..*scheme_end])? - .to_lowercase(); - Ok((Some(scheme), &uri_string[*scheme_end+1..])) + let authority_or_path_delimiter_start = + uri_string.find('/').unwrap_or_else(|| uri_string.len()); + if let Some(scheme_end) = + &uri_string[0..authority_or_path_delimiter_start].find(':') + { + let scheme = + Self::check_scheme(&uri_string[0..*scheme_end])?.to_lowercase(); + Ok((Some(scheme), &uri_string[*scheme_end + 1..])) } else { Ok((None, uri_string)) } @@ -437,17 +446,13 @@ impl Uri { /// /// Since path segments may contain non-UTF8 byte sequences, this function /// may return - /// [`Error::CannotExpressAsUtf8`](enum.Error.html#variant.CannotExpressAsUtf8). + /// [`Error::CannotExpressAsUtf8`](enum.Error.html#variant. + /// CannotExpressAsUtf8). #[must_use = "we went through all that trouble to put the path into a string, and you don't want it?"] pub fn path_to_string(&self) -> Result<String, Error> { match &*self.path { [segment] if segment.is_empty() => Ok("/".to_string()), - path => Ok( - String::from_utf8( - path - .join(&b"/"[..]) - )? - ), + path => Ok(String::from_utf8(path.join(&b"/"[..]))?), } } @@ -468,14 +473,12 @@ impl Uri { /// # Errors /// /// Since queries may contain non-UTF8 byte sequences, this function may - /// return [`Error::CannotExpressAsUtf8`](enum.Error.html#variant.CannotExpressAsUtf8). + /// return [`Error::CannotExpressAsUtf8`](enum.Error.html#variant. + /// CannotExpressAsUtf8). #[must_use = "use the query return value silly programmer"] pub fn query_to_string(&self) -> Result<Option<String>, Error> { self.query() - .map(|query| { - String::from_utf8(query.to_vec()) - .map_err(Into::into) - }) + .map(|query| String::from_utf8(query.to_vec()).map_err(Into::into)) .transpose() } @@ -498,75 +501,79 @@ impl Uri { /// # } /// ``` #[must_use = "why go through all that effort to resolve the URI, when you're not going to use it?!"] - pub fn resolve(&self, relative_reference: &Self) -> Self { - let (scheme, authority, path, query) = if relative_reference.scheme.is_some() { - ( - relative_reference.scheme.clone(), - relative_reference.authority.clone(), - Self::normalize_path(&relative_reference.path), - relative_reference.query.clone() - ) - } else { - relative_reference.authority.as_ref().map_or_else( - || { - let scheme = self.scheme.clone(); - let authority = self.authority.clone(); - if relative_reference.path.is_empty() { - let path = self.path.clone(); - let query = if relative_reference.query.is_none() { - self.query.clone() - } else { - relative_reference.query.clone() - }; - ( - scheme, - authority, - path, - query - ) - } else { - let query = relative_reference.query.clone(); - - // RFC describes this as: - // "if (R.path starts-with "/") then" - if Self::is_path_absolute(&relative_reference.path) { - ( - scheme, - authority, - relative_reference.path.clone(), - query - ) + pub fn resolve( + &self, + relative_reference: &Self, + ) -> Self { + let (scheme, authority, path, query) = + if relative_reference.scheme.is_some() { + ( + relative_reference.scheme.clone(), + relative_reference.authority.clone(), + Self::normalize_path(&relative_reference.path), + relative_reference.query.clone(), + ) + } else { + relative_reference.authority.as_ref().map_or_else( + || { + let scheme = self.scheme.clone(); + let authority = self.authority.clone(); + if relative_reference.path.is_empty() { + let path = self.path.clone(); + let query = if relative_reference.query.is_none() { + self.query.clone() + } else { + relative_reference.query.clone() + }; + (scheme, authority, path, query) } else { + let query = relative_reference.query.clone(); + // RFC describes this as: - // "T.path = merge(Base.path, R.path);" - let mut path = self.path.clone(); - if path.len() > 1 { - path.pop(); + // "if (R.path starts-with "/") then" + if Self::is_path_absolute(&relative_reference.path) + { + ( + scheme, + authority, + relative_reference.path.clone(), + query, + ) + } else { + // RFC describes this as: + // "T.path = merge(Base.path, R.path);" + let mut path = self.path.clone(); + if path.len() > 1 { + path.pop(); + } + path.extend( + relative_reference.path.iter().cloned(), + ); + ( + scheme, + authority, + Self::normalize_path(&path), + query, + ) } - path.extend(relative_reference.path.iter().cloned()); - ( - scheme, - authority, - Self::normalize_path(&path), - query - ) } - } - }, - |authority| ( - self.scheme.clone(), - Some(authority.clone()), - Self::normalize_path(&relative_reference.path), - relative_reference.query.clone() + }, + |authority| { + ( + self.scheme.clone(), + Some(authority.clone()), + Self::normalize_path(&relative_reference.path), + relative_reference.query.clone(), + ) + }, ) - ) - }; - Self{ + }; + Self { scheme, authority, path, query, - fragment: relative_reference.fragment.clone() + fragment: relative_reference.fragment.clone(), } } @@ -584,15 +591,21 @@ impl Uri { } /// Change the authority of the URI. - pub fn set_authority<T>(&mut self, authority: T) - where T: Into<Option<Authority>> + pub fn set_authority<T>( + &mut self, + authority: T, + ) where + T: Into<Option<Authority>>, { self.authority = authority.into(); } /// Change the fragment of the URI. - pub fn set_fragment<T>(&mut self, fragment: T) - where T: Into<Option<Vec<u8>>> + pub fn set_fragment<T>( + &mut self, + fragment: T, + ) where + T: Into<Option<Vec<u8>>>, { self.fragment = fragment.into(); } @@ -601,8 +614,11 @@ impl Uri { /// /// Note: See [`path`](#method.path) for special notes about what the /// segments of the path mean. - pub fn set_path<T>(&mut self, path: T) - where T: Into<Vec<Vec<u8>>> + pub fn set_path<T>( + &mut self, + path: T, + ) where + T: Into<Vec<Vec<u8>>>, { self.path = path.into(); } @@ -612,23 +628,28 @@ impl Uri { /// /// Note: See [`path`](#method.path) for special notes about what the /// segments of the path mean. - pub fn set_path_from_str<T>(&mut self, path: T) - where T: AsRef<str> + pub fn set_path_from_str<T>( + &mut self, + path: T, + ) where + T: AsRef<str>, { match path.as_ref() { "" => self.set_path(vec![]), path => self.set_path( - path - .split('/') + path.split('/') .map(|segment| segment.as_bytes().to_vec()) - .collect::<Vec<Vec<u8>>>() + .collect::<Vec<Vec<u8>>>(), ), } } /// Change the query of the URI. - pub fn set_query<T>(&mut self, query: T) - where T: Into<Option<Vec<u8>>> + pub fn set_query<T>( + &mut self, + query: T, + ) where + T: Into<Option<Vec<u8>>>, { self.query = query.into(); } @@ -640,14 +661,18 @@ impl Uri { /// The set of characters allowed in the scheme of a URI is limited. /// [`Error::IllegalCharacter`](enum.Error.html#variant.IllegalCharacter) /// is returned if you try to use a character that isn't allowed. - pub fn set_scheme<T>(&mut self, scheme: T) -> Result<(), Error> - where T: Into<Option<String>> + pub fn set_scheme<T>( + &mut self, + scheme: T, + ) -> Result<(), Error> + where + T: Into<Option<String>>, { self.scheme = match scheme.into() { Some(scheme) => { Self::check_scheme(&scheme)?; Some(scheme) - } + }, None => None, }; Ok(()) @@ -656,7 +681,8 @@ impl Uri { fn split_authority_from_path_and_parse_them<T>( authority_and_path_string: T ) -> Result<(Option<Authority>, Vec<Vec<u8>>), Error> - where T: AsRef<str> + where + T: AsRef<str>, { // Split authority from path. If there is an authority, parse it. let authority_and_path_string = authority_and_path_string.as_ref(); @@ -665,7 +691,8 @@ impl Uri { let authority_and_path_string = &authority_and_path_string[2..]; // First separate the authority from the path. - let authority_end = authority_and_path_string.find('/') + let authority_end = authority_and_path_string + .find('/') .unwrap_or_else(|| authority_and_path_string.len()); let authority_string = &authority_and_path_string[0..authority_end]; let path_string = &authority_and_path_string[authority_end..]; @@ -723,20 +750,23 @@ impl Uri { /// # Errors /// /// Since fragments may contain non-UTF8 byte sequences, this function may - /// return [`Error::CannotExpressAsUtf8`](enum.Error.html#variant.CannotExpressAsUtf8). + /// return [`Error::CannotExpressAsUtf8`](enum.Error.html#variant. + /// CannotExpressAsUtf8). #[must_use = "come on, you intended to use that userinfo return value, didn't you?"] pub fn userinfo_to_string(&self) -> Result<Option<String>, Error> { self.userinfo() .map(|userinfo| { - String::from_utf8(userinfo.to_vec()) - .map_err(Into::into) + String::from_utf8(userinfo.to_vec()).map_err(Into::into) }) .transpose() } } impl std::fmt::Display for Uri { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { if let Some(scheme) = &self.scheme { write!(f, "{}:", scheme)?; } @@ -744,10 +774,7 @@ impl std::fmt::Display for Uri { write!(f, "//{}", authority)?; } // Special case: absolute but otherwise empty path. - if - Self::is_path_absolute(&self.path) - && self.path.len() == 1 - { + if Self::is_path_absolute(&self.path) && self.path.len() == 1 { write!(f, "/")?; } for (i, segment) in self.path.iter().enumerate() { @@ -757,10 +784,18 @@ impl std::fmt::Display for Uri { } } if let Some(query) = &self.query { - write!(f, "?{}", encode_element(query, &QUERY_NOT_PCT_ENCODED_WITHOUT_PLUS))?; + write!( + f, + "?{}", + encode_element(query, &QUERY_NOT_PCT_ENCODED_WITHOUT_PLUS) + )?; } if let Some(fragment) = &self.fragment { - write!(f, "#{}", encode_element(fragment, &QUERY_OR_FRAGMENT_NOT_PCT_ENCODED))?; + write!( + f, + "#{}", + encode_element(fragment, &QUERY_OR_FRAGMENT_NOT_PCT_ENCODED) + )?; } Ok(()) } @@ -788,7 +823,10 @@ mod tests { let uri = uri.unwrap(); assert_eq!(Some("http"), uri.scheme()); assert_eq!(Some(&b"www.example.com"[..]), uri.host()); - assert_eq!(Some("www.example.com"), uri.host_to_string().unwrap().as_deref()); + assert_eq!( + Some("www.example.com"), + uri.host_to_string().unwrap().as_deref() + ); assert_eq!(uri.path_to_string().unwrap(), "/foo/bar"); } @@ -835,7 +873,7 @@ mod tests { named_tuple!( struct TestVector { uri_string: &'static str, - is_relative_reference: bool + is_relative_reference: bool, } ); let test_vectors: &[TestVector] = &[ @@ -860,7 +898,7 @@ mod tests { named_tuple!( struct TestVector { uri_string: &'static str, - contains_relative_path: bool + contains_relative_path: bool, } ); let test_vectors: &[TestVector] = &[ @@ -868,7 +906,6 @@ mod tests { ("http://www.example.com", false).into(), ("/", false).into(), ("foo", true).into(), - // This is only a valid test vector if we understand // correctly that an empty string IS a valid // "relative reference" URI with an empty path. @@ -881,7 +918,8 @@ mod tests { assert_eq!( *test_vector.contains_relative_path(), uri.contains_relative_path(), - "{}", test_index + "{}", + test_index ); } } @@ -893,27 +931,56 @@ mod tests { uri_string: &'static str, host: &'static str, query: Option<&'static str>, - fragment: Option<&'static str> + fragment: Option<&'static str>, } ); let test_vectors: &[TestVector] = &[ ("http://www.example.com/", "www.example.com", None, None).into(), ("http://example.com?foo", "example.com", Some("foo"), None).into(), - ("http://www.example.com#foo", "www.example.com", None, Some("foo")).into(), - ("http://www.example.com?foo#bar", "www.example.com", Some("foo"), Some("bar")).into(), - ("http://www.example.com?earth?day#bar", "www.example.com", Some("earth?day"), Some("bar")).into(), - ("http://www.example.com/spam?foo#bar", "www.example.com", Some("foo"), Some("bar")).into(), - ("http://www.example.com/?", "www.example.com", Some(""), None).into(), + ( + "http://www.example.com#foo", + "www.example.com", + None, + Some("foo"), + ) + .into(), + ( + "http://www.example.com?foo#bar", + "www.example.com", + Some("foo"), + Some("bar"), + ) + .into(), + ( + "http://www.example.com?earth?day#bar", + "www.example.com", + Some("earth?day"), + Some("bar"), + ) + .into(), + ( + "http://www.example.com/spam?foo#bar", + "www.example.com", + Some("foo"), + Some("bar"), + ) + .into(), + ("http://www.example.com/?", "www.example.com", Some(""), None) + .into(), ]; for (test_index, test_vector) in test_vectors.iter().enumerate() { let uri = Uri::parse(test_vector.uri_string()); assert!(uri.is_ok()); let uri = uri.unwrap(); - assert_eq!(Some(*test_vector.host()), uri.host_to_string().unwrap().as_deref()); + assert_eq!( + Some(*test_vector.host()), + uri.host_to_string().unwrap().as_deref() + ); assert_eq!( *test_vector.query(), uri.query_to_string().unwrap().as_deref(), - "{}", test_index + "{}", + test_index ); assert_eq!( *test_vector.fragment(), @@ -943,7 +1010,7 @@ mod tests { named_tuple!( struct TestVector { uri_string: &'static str, - scheme: &'static str + scheme: &'static str, } ); let test_vectors: &[TestVector] = &[ @@ -963,7 +1030,7 @@ mod tests { } #[test] - fn scheme_mixed_case () { + fn scheme_mixed_case() { let test_vectors = [ "http://www.example.com/", "hTtp://www.example.com/", @@ -1032,7 +1099,7 @@ mod tests { named_tuple!( struct TestVector { uri_string: &'static str, - path: Vec<&'static [u8]> + path: Vec<&'static [u8]>, } ); let test_vectors: &[TestVector] = &[ @@ -1040,7 +1107,13 @@ mod tests { ("bob@/foo", vec![&b"bob@"[..], &b"foo"[..]]).into(), ("hello!", vec![&b"hello!"[..]]).into(), ("urn:hello,%20w%6Frld", vec![&b"hello, world"[..]]).into(), - ("//example.com/foo/(bar)/", vec![&b""[..], &b"foo"[..], &b"(bar)"[..], &b""[..]]).into(), + ("//example.com/foo/(bar)/", vec![ + &b""[..], + &b"foo"[..], + &b"(bar)"[..], + &b""[..], + ]) + .into(), ]; for test_vector in test_vectors { let uri = Uri::parse(test_vector.uri_string()); @@ -1085,7 +1158,7 @@ mod tests { named_tuple!( struct TestVector { uri_string: &'static str, - query: &'static str + query: &'static str, } ); let test_vectors: &[TestVector] = &[ @@ -1103,7 +1176,8 @@ mod tests { assert_eq!( Some(*test_vector.query()), uri.query_to_string().unwrap().as_deref(), - "{}", test_index + "{}", + test_index ); } } @@ -1143,7 +1217,7 @@ mod tests { named_tuple!( struct TestVector { uri_string: &'static str, - fragment: &'static str + fragment: &'static str, } ); let test_vectors: &[TestVector] = &[ @@ -1170,7 +1244,7 @@ mod tests { named_tuple!( struct TestVector { uri_string: &'static str, - path_first_segment: &'static [u8] + path_first_segment: &'static [u8], } ); let test_vectors: &[TestVector] = &[ @@ -1253,7 +1327,8 @@ mod tests { assert_eq!( *test_vector.normalized_path(), uri.path_to_string().unwrap(), - "{}", test_vector.uri_string() + "{}", + test_vector.uri_string() ); } } @@ -1279,7 +1354,7 @@ mod tests { struct TestVector { base_string: &'static str, relative_reference_string: &'static str, - target_string: &'static str + target_string: &'static str, } ); let test_vectors: &[TestVector] = &[ @@ -1307,7 +1382,6 @@ mod tests { ("http://a/b/c/d;p?q", "../..", "http://a").into(), ("http://a/b/c/d;p?q", "../../", "http://a").into(), ("http://a/b/c/d;p?q", "../../g", "http://a/g").into(), - // Here are some examples of our own. ("http://example.com", "foo", "http://example.com/foo").into(), ("http://example.com/", "foo", "http://example.com/foo").into(), @@ -1322,9 +1396,12 @@ mod tests { ]; for test_vector in test_vectors { let base_uri = Uri::parse(test_vector.base_string()).unwrap(); - let relative_reference_uri = Uri::parse(test_vector.relative_reference_string()).unwrap(); - let expected_target_uri = Uri::parse(test_vector.target_string()).unwrap(); - let actual_target_uri = dbg!(base_uri.resolve(&relative_reference_uri)); + let relative_reference_uri = + Uri::parse(test_vector.relative_reference_string()).unwrap(); + let expected_target_uri = + Uri::parse(test_vector.target_string()).unwrap(); + let actual_target_uri = + dbg!(base_uri.resolve(&relative_reference_uri)); assert_eq!(expected_target_uri, actual_target_uri); } } @@ -1362,56 +1439,278 @@ mod tests { path: &'static str, query: Option<&'static str>, fragment: Option<&'static str>, - expected_uri_string: &'static str + expected_uri_string: &'static str, } ); let test_vectors: &[TestVector] = &[ // general test vectors - // scheme userinfo host port path query fragment expected_uri_string - (Some("http"), Some("bob"), Some("www.example.com"), Some(8080), "/abc/def", Some("foobar"), Some("ch2"), "http://bob@www.example.com:8080/abc/def?foobar#ch2").into(), - (Some("http"), Some("bob"), Some("www.example.com"), Some(0), "", Some("foobar"), Some("ch2"), "http://bob@www.example.com:0?foobar#ch2").into(), - (Some("http"), Some("bob"), Some("www.example.com"), Some(0), "", Some("foobar"), Some(""), "http://bob@www.example.com:0?foobar#").into(), - (None, None, Some("example.com"), None, "", Some("bar"), None, "//example.com?bar").into(), - (None, None, Some("example.com"), None, "", Some(""), None, "//example.com?").into(), - (None, None, Some("example.com"), None, "", None, None, "//example.com").into(), - (None, None, Some("example.com"), None, "/", None, None, "//example.com/").into(), - (None, None, Some("example.com"), None, "/xyz", None, None, "//example.com/xyz").into(), - (None, None, Some("example.com"), None, "/xyz/", None, None, "//example.com/xyz/").into(), - (None, None, None, None, "/", None, None, "/").into(), - (None, None, None, None, "/xyz", None, None, "/xyz").into(), - (None, None, None, None, "/xyz/", None, None, "/xyz/").into(), - (None, None, None, None, "", None, None, "").into(), - (None, None, None, None, "xyz", None, None, "xyz").into(), - (None, None, None, None, "xyz/", None, None, "xyz/").into(), - (None, None, None, None, "", Some("bar"), None, "?bar").into(), - (Some("http"), None, None, None, "", Some("bar"), None, "http:?bar").into(), - (Some("http"), None, None, None, "", None, None, "http:").into(), - (Some("http"), None, Some("::1"), None, "", None, None, "http://[::1]").into(), - (Some("http"), None, Some("::1.2.3.4"), None, "", None, None, "http://[::1.2.3.4]").into(), - (Some("http"), None, Some("1.2.3.4"), None, "", None, None, "http://1.2.3.4").into(), - (None, None, None, None, "", None, None, "").into(), - (Some("http"), Some("bob"), None, None, "", Some("foobar"), None, "http://bob@?foobar").into(), - (None, Some("bob"), None, None, "", Some("foobar"), None, "//bob@?foobar").into(), - (None, Some("bob"), None, None, "", None, None, "//bob@").into(), - + // scheme userinfo host port + // path query fragment expected_uri_string + ( + Some("http"), + Some("bob"), + Some("www.example.com"), + Some(8080), + "/abc/def", + Some("foobar"), + Some("ch2"), + "http://bob@www.example.com:8080/abc/def?foobar#ch2", + ) + .into(), + ( + Some("http"), + Some("bob"), + Some("www.example.com"), + Some(0), + "", + Some("foobar"), + Some("ch2"), + "http://bob@www.example.com:0?foobar#ch2", + ) + .into(), + ( + Some("http"), + Some("bob"), + Some("www.example.com"), + Some(0), + "", + Some("foobar"), + Some(""), + "http://bob@www.example.com:0?foobar#", + ) + .into(), + ( + None, + None, + Some("example.com"), + None, + "", + Some("bar"), + None, + "//example.com?bar", + ) + .into(), + ( + None, + None, + Some("example.com"), + None, + "", + Some(""), + None, + "//example.com?", + ) + .into(), + ( + None, + None, + Some("example.com"), + None, + "", + None, + None, + "//example.com", + ) + .into(), + ( + None, + None, + Some("example.com"), + None, + "/", + None, + None, + "//example.com/", + ) + .into(), + ( + None, + None, + Some("example.com"), + None, + "/xyz", + None, + None, + "//example.com/xyz", + ) + .into(), + ( + None, + None, + Some("example.com"), + None, + "/xyz/", + None, + None, + "//example.com/xyz/", + ) + .into(), + (None, None, None, None, "/", None, None, "/").into(), + (None, None, None, None, "/xyz", None, None, "/xyz").into(), + (None, None, None, None, "/xyz/", None, None, "/xyz/").into(), + (None, None, None, None, "", None, None, "").into(), + (None, None, None, None, "xyz", None, None, "xyz").into(), + (None, None, None, None, "xyz/", None, None, "xyz/").into(), + (None, None, None, None, "", Some("bar"), None, "?bar").into(), + ( + Some("http"), + None, + None, + None, + "", + Some("bar"), + None, + "http:?bar", + ) + .into(), + (Some("http"), None, None, None, "", None, None, "http:").into(), + ( + Some("http"), + None, + Some("::1"), + None, + "", + None, + None, + "http://[::1]", + ) + .into(), + ( + Some("http"), + None, + Some("::1.2.3.4"), + None, + "", + None, + None, + "http://[::1.2.3.4]", + ) + .into(), + ( + Some("http"), + None, + Some("1.2.3.4"), + None, + "", + None, + None, + "http://1.2.3.4", + ) + .into(), + (None, None, None, None, "", None, None, "").into(), + ( + Some("http"), + Some("bob"), + None, + None, + "", + Some("foobar"), + None, + "http://bob@?foobar", + ) + .into(), + ( + None, + Some("bob"), + None, + None, + "", + Some("foobar"), + None, + "//bob@?foobar", + ) + .into(), + (None, Some("bob"), None, None, "", None, None, "//bob@").into(), // percent-encoded character test vectors - // scheme userinfo host port path query fragment expected_uri_string - (Some("http"), Some("b b"), Some("www.example.com"), Some(8080), "/abc/def", Some("foobar"), Some("ch2"), "http://b%20b@www.example.com:8080/abc/def?foobar#ch2").into(), - (Some("http"), Some("bob"), Some("www.e ample.com"), Some(8080), "/abc/def", Some("foobar"), Some("ch2"), "http://bob@www.e%20ample.com:8080/abc/def?foobar#ch2").into(), - (Some("http"), Some("bob"), Some("www.example.com"), Some(8080), "/a c/def", Some("foobar"), Some("ch2"), "http://bob@www.example.com:8080/a%20c/def?foobar#ch2").into(), - (Some("http"), Some("bob"), Some("www.example.com"), Some(8080), "/abc/def", Some("foo ar"), Some("ch2"), "http://bob@www.example.com:8080/abc/def?foo%20ar#ch2").into(), - (Some("http"), Some("bob"), Some("www.example.com"), Some(8080), "/abc/def", Some("foobar"), Some("c 2"), "http://bob@www.example.com:8080/abc/def?foobar#c%202").into(), - (Some("http"), Some("bob"), Some("ሴ.example.com"), Some(8080), "/abc/def", Some("foobar"), None, "http://bob@%E1%88%B4.example.com:8080/abc/def?foobar").into(), - + // scheme userinfo host port + // path query fragment expected_uri_string + ( + Some("http"), + Some("b b"), + Some("www.example.com"), + Some(8080), + "/abc/def", + Some("foobar"), + Some("ch2"), + "http://b%20b@www.example.com:8080/abc/def?foobar#ch2", + ) + .into(), + ( + Some("http"), + Some("bob"), + Some("www.e ample.com"), + Some(8080), + "/abc/def", + Some("foobar"), + Some("ch2"), + "http://bob@www.e%20ample.com:8080/abc/def?foobar#ch2", + ) + .into(), + ( + Some("http"), + Some("bob"), + Some("www.example.com"), + Some(8080), + "/a c/def", + Some("foobar"), + Some("ch2"), + "http://bob@www.example.com:8080/a%20c/def?foobar#ch2", + ) + .into(), + ( + Some("http"), + Some("bob"), + Some("www.example.com"), + Some(8080), + "/abc/def", + Some("foo ar"), + Some("ch2"), + "http://bob@www.example.com:8080/abc/def?foo%20ar#ch2", + ) + .into(), + ( + Some("http"), + Some("bob"), + Some("www.example.com"), + Some(8080), + "/abc/def", + Some("foobar"), + Some("c 2"), + "http://bob@www.example.com:8080/abc/def?foobar#c%202", + ) + .into(), + ( + Some("http"), + Some("bob"), + Some("ሴ.example.com"), + Some(8080), + "/abc/def", + Some("foobar"), + None, + "http://bob@%E1%88%B4.example.com:8080/abc/def?foobar", + ) + .into(), // normalization of IPv6 address hex digits - // scheme userinfo host port path query fragment expected_uri_string - (Some("http"), Some("bob"), Some("fFfF::1"), Some(8080), "/abc/def", Some("foobar"), Some("c 2"), "http://bob@[ffff::1]:8080/abc/def?foobar#c%202").into(), + // scheme userinfo host port path + // query fragment expected_uri_string + ( + Some("http"), + Some("bob"), + Some("fFfF::1"), + Some(8080), + "/abc/def", + Some("foobar"), + Some("c 2"), + "http://bob@[ffff::1]:8080/abc/def?foobar#c%202", + ) + .into(), ]; for test_vector in test_vectors { let mut uri = Uri::default(); - assert!(uri.set_scheme(test_vector.scheme().map(ToString::to_string)).is_ok()); - if - test_vector.userinfo().is_some() + assert!(uri + .set_scheme(test_vector.scheme().map(ToString::to_string)) + .is_ok()); + if test_vector.userinfo().is_some() || test_vector.host().is_some() || test_vector.port().is_some() { @@ -1426,10 +1725,7 @@ mod tests { uri.set_path_from_str(test_vector.path()); uri.set_query(test_vector.query().map(Into::into)); uri.set_fragment(test_vector.fragment().map(Into::into)); - assert_eq!( - *test_vector.expected_uri_string(), - uri.to_string() - ); + assert_eq!(*test_vector.expected_uri_string(), uri.to_string()); } } @@ -1513,23 +1809,13 @@ mod tests { for ci in 0_u8..31_u8 { let mut uri = Uri::default(); uri.set_query(Some(vec![ci])); - assert_eq!( - uri.to_string(), - format!("?%{:02X}", ci) - ); + assert_eq!(uri.to_string(), format!("?%{:02X}", ci)); } } #[test] fn set_illegal_schemes() { - let test_vectors = [ - "ab_de", - "ab/de", - "ab:de", - "", - "&", - "foo&bar", - ]; + let test_vectors = ["ab_de", "ab/de", "ab:de", "", "&", "foo&bar"]; for test_vector in &test_vectors { let mut uri = Uri::default(); assert!(uri.set_scheme(Some((*test_vector).to_string())).is_err()); @@ -1538,7 +1824,8 @@ mod tests { #[test] fn take_parts() { - let mut uri = Uri::parse("https://www.example.com/foo?bar#baz").unwrap(); + let mut uri = + Uri::parse("https://www.example.com/foo?bar#baz").unwrap(); assert_eq!(Some("https"), uri.take_scheme().as_deref()); assert_eq!("//www.example.com/foo?bar#baz", uri.to_string()); assert!(matches!( @@ -1554,5 +1841,4 @@ mod tests { assert_eq!("/foo", uri.to_string()); assert_eq!(None, uri.take_fragment().as_deref()); } - } diff --git a/src/validate_ipv4_address.rs b/src/validate_ipv4_address.rs index 98254df..3c524ce 100644 --- a/src/validate_ipv4_address.rs +++ b/src/validate_ipv4_address.rs @@ -1,10 +1,10 @@ #![warn(clippy::pedantic)] -use super::character_classes::{ - DIGIT, +use super::{ + character_classes::DIGIT, + context::Context, + error::Error, }; -use super::context::Context; -use super::error::Error; struct Shared { num_groups: usize, @@ -20,7 +20,9 @@ impl State { fn finalize(self) -> Result<(), Error> { match self { Self::NotInOctet(_) => Err(Error::TruncatedHost), - Self::ExpectDigitOrDot(state) => Self::finalize_expect_digit_or_dot(state), + Self::ExpectDigitOrDot(state) => { + Self::finalize_expect_digit_or_dot(state) + }, } } @@ -40,20 +42,28 @@ impl State { } fn new() -> Self { - Self::NotInOctet(Shared{ + Self::NotInOctet(Shared { num_groups: 0, octet_buffer: String::new(), }) } - fn next(self, c: char) -> Result<Self, Error> { + fn next( + self, + c: char, + ) -> Result<Self, Error> { match self { Self::NotInOctet(state) => Self::next_not_in_octet(state, c), - Self::ExpectDigitOrDot(state) => Self::next_expect_digit_or_dot(state, c), + Self::ExpectDigitOrDot(state) => { + Self::next_expect_digit_or_dot(state, c) + }, } } - fn next_not_in_octet(state: Shared, c: char) -> Result<Self, Error> { + fn next_not_in_octet( + state: Shared, + c: char, + ) -> Result<Self, Error> { let mut state = state; if DIGIT.contains(&c) { state.octet_buffer.push(c); @@ -63,7 +73,10 @@ impl State { } } - fn next_expect_digit_or_dot(state: Shared, c: char)-> Result<Self, Error> { + fn next_expect_digit_or_dot( + state: Shared, + c: char, + ) -> Result<Self, Error> { let mut state = state; if c == '.' { state.num_groups += 1; @@ -85,13 +98,13 @@ impl State { } pub fn validate_ipv4_address<T>(address: T) -> Result<(), Error> - where T: AsRef<str> +where + T: AsRef<str>, { - address.as_ref() + address + .as_ref() .chars() - .try_fold(State::new(), |machine, c| { - machine.next(c) - })? + .try_fold(State::new(), |machine, c| machine.next(c))? .finalize() } diff --git a/src/validate_ipv6_address.rs b/src/validate_ipv6_address.rs index 502da65..1f3961f 100644 --- a/src/validate_ipv6_address.rs +++ b/src/validate_ipv6_address.rs @@ -1,12 +1,14 @@ #![warn(clippy::pedantic)] -use super::character_classes::{ - DIGIT, - HEXDIG, +use super::{ + character_classes::{ + DIGIT, + HEXDIG, + }, + context::Context, + error::Error, + validate_ipv4_address::validate_ipv4_address, }; -use super::context::Context; -use super::error::Error; -use super::validate_ipv4_address::validate_ipv4_address; enum MachineExitStatus<'a> { Error(Error), @@ -40,20 +42,22 @@ enum State<'a> { impl<'a> State<'a> { fn finalize(mut self) -> Result<(), Error> { match &mut self { - Self::InGroupNotIpv4(state) - | Self::InGroupCouldBeIpv4(state) => { + Self::InGroupNotIpv4(state) | Self::InGroupCouldBeIpv4(state) => { // count trailing group state.num_groups += 1; }, Self::InGroupIpv4(state) => { - validate_ipv4_address(&state.address[state.potential_ipv4_address_start..])?; + validate_ipv4_address( + &state.address[state.potential_ipv4_address_start..], + )?; state.num_groups += 2; }, _ => {}, }; match self { - Self::ColonButNoGroupsYet(_) - | Self::ColonAfterGroup(_) => Err(Error::TruncatedHost), + Self::ColonButNoGroupsYet(_) | Self::ColonAfterGroup(_) => { + Err(Error::TruncatedHost) + }, Self::AfterDoubleColon(state) | Self::InGroupNotIpv4(state) @@ -66,12 +70,12 @@ impl<'a> State<'a> { (false, n) if n < 8 => Err(Error::TooFewAddressParts), (_, _) => Err(Error::TooManyAddressParts), } - } + }, } } fn new(address: &'a str) -> Self { - Self::NoGroupsYet(Shared{ + Self::NoGroupsYet(Shared { address, num_groups: 0, num_digits: 0, @@ -80,19 +84,37 @@ impl<'a> State<'a> { }) } - fn next(self, i: usize, c: char) -> Result<Self, MachineExitStatus<'a>> { + fn next( + self, + i: usize, + c: char, + ) -> Result<Self, MachineExitStatus<'a>> { match self { Self::NoGroupsYet(state) => Self::next_no_groups_yet(state, i, c), - Self::ColonButNoGroupsYet(state) => Self::next_colon_but_no_groups_yet(state, c), - Self::AfterDoubleColon(state) => Self::next_after_double_colon(state, i, c), - Self::InGroupNotIpv4(state) => Self::next_in_group_not_ipv4(state, c), - Self::InGroupCouldBeIpv4(state) => Self::next_in_group_could_be_ipv4(state, c), + Self::ColonButNoGroupsYet(state) => { + Self::next_colon_but_no_groups_yet(state, c) + }, + Self::AfterDoubleColon(state) => { + Self::next_after_double_colon(state, i, c) + }, + Self::InGroupNotIpv4(state) => { + Self::next_in_group_not_ipv4(state, c) + }, + Self::InGroupCouldBeIpv4(state) => { + Self::next_in_group_could_be_ipv4(state, c) + }, Self::InGroupIpv4(state) => Ok(Self::InGroupIpv4(state)), - Self::ColonAfterGroup(state) => Self::next_colon_after_group(state, i, c), + Self::ColonAfterGroup(state) => { + Self::next_colon_after_group(state, i, c) + }, } } - fn next_no_groups_yet(state: Shared<'a>, i: usize, c: char) -> Result<Self, MachineExitStatus> { + fn next_no_groups_yet( + state: Shared<'a>, + i: usize, + c: char, + ) -> Result<Self, MachineExitStatus> { let mut state = state; if c == ':' { Ok(Self::ColonButNoGroupsYet(state)) @@ -108,7 +130,10 @@ impl<'a> State<'a> { } } - fn next_colon_but_no_groups_yet(state: Shared<'a>, c: char) -> Result<Self, MachineExitStatus> { + fn next_colon_but_no_groups_yet( + state: Shared<'a>, + c: char, + ) -> Result<Self, MachineExitStatus> { let mut state = state; if c == ':' { state.double_colon_encountered = true; @@ -118,7 +143,11 @@ impl<'a> State<'a> { } } - fn next_after_double_colon(state: Shared<'a>, i: usize, c: char) -> Result<Self, MachineExitStatus> { + fn next_after_double_colon( + state: Shared<'a>, + i: usize, + c: char, + ) -> Result<Self, MachineExitStatus> { let mut state = state; state.num_digits += 1; if state.num_digits > 4 { @@ -133,7 +162,10 @@ impl<'a> State<'a> { } } - fn next_in_group_not_ipv4(state: Shared<'a>, c: char) -> Result<Self, MachineExitStatus> { + fn next_in_group_not_ipv4( + state: Shared<'a>, + c: char, + ) -> Result<Self, MachineExitStatus> { let mut state = state; if c == ':' { state.num_digits = 0; @@ -151,7 +183,10 @@ impl<'a> State<'a> { } } - fn next_in_group_could_be_ipv4(state: Shared<'a>, c: char) -> Result<Self, MachineExitStatus> { + fn next_in_group_could_be_ipv4( + state: Shared<'a>, + c: char, + ) -> Result<Self, MachineExitStatus> { let mut state = state; if c == ':' { state.num_digits = 0; @@ -173,7 +208,11 @@ impl<'a> State<'a> { } } - fn next_colon_after_group(state: Shared<'a>, i: usize, c: char) -> Result<Self, MachineExitStatus> { + fn next_colon_after_group( + state: Shared<'a>, + i: usize, + c: char, + ) -> Result<Self, MachineExitStatus> { let mut state = state; if c == ':' { if state.double_colon_encountered { @@ -196,17 +235,18 @@ impl<'a> State<'a> { } pub fn validate_ipv6_address<T>(address: T) -> Result<(), Error> - where T: AsRef<str> +where + T: AsRef<str>, { let address = address.as_ref(); address .char_indices() - .try_fold(State::new(address), |machine, (i, c)| { - machine.next(i, c) - }) + .try_fold(State::new(address), |machine, (i, c)| machine.next(i, c)) .or_else(|machine_exit_status| match machine_exit_status { - MachineExitStatus::Ipv4Trailer(state) => Ok(State::InGroupIpv4(state)), - MachineExitStatus::Error(error) => Err(error) + MachineExitStatus::Ipv4Trailer(state) => { + Ok(State::InGroupIpv4(state)) + }, + MachineExitStatus::Error(error) => Err(error), })? .finalize() } @@ -245,18 +285,32 @@ mod tests { ); let test_vectors: &[TestVector] = &[ ("::fFfF::1", Error::TooManyDoubleColons).into(), - ("::ffff:1.2.x.4", Error::IllegalCharacter(Context::Ipv4Address)).into(), + ("::ffff:1.2.x.4", Error::IllegalCharacter(Context::Ipv4Address)) + .into(), ("::ffff:1.2.3.4.8", Error::TooManyAddressParts).into(), ("::ffff:1.2.3", Error::TooFewAddressParts).into(), ("::ffff:1.2.3.", Error::TruncatedHost).into(), ("::ffff:1.2.3.256", Error::InvalidDecimalOctet).into(), - ("::fxff:1.2.3.4", Error::IllegalCharacter(Context::Ipv6Address)).into(), - ("::ffff:1.2.3.-4", Error::IllegalCharacter(Context::Ipv4Address)).into(), - ("::ffff:1.2.3. 4", Error::IllegalCharacter(Context::Ipv4Address)).into(), - ("::ffff:1.2.3.4 ", Error::IllegalCharacter(Context::Ipv4Address)).into(), - ("2001:db8:85a3:8d3:1319:8a2e:370:7348:0000", Error::TooManyAddressParts).into(), - ("2001:db8:85a3:8d3:1319:8a2e:370:7348::1", Error::TooManyAddressParts).into(), - ("2001:db8:85a3:8d3:1319:8a2e:370::1", Error::TooManyAddressParts).into(), + ("::fxff:1.2.3.4", Error::IllegalCharacter(Context::Ipv6Address)) + .into(), + ("::ffff:1.2.3.-4", Error::IllegalCharacter(Context::Ipv4Address)) + .into(), + ("::ffff:1.2.3. 4", Error::IllegalCharacter(Context::Ipv4Address)) + .into(), + ("::ffff:1.2.3.4 ", Error::IllegalCharacter(Context::Ipv4Address)) + .into(), + ( + "2001:db8:85a3:8d3:1319:8a2e:370:7348:0000", + Error::TooManyAddressParts, + ) + .into(), + ( + "2001:db8:85a3:8d3:1319:8a2e:370:7348::1", + Error::TooManyAddressParts, + ) + .into(), + ("2001:db8:85a3:8d3:1319:8a2e:370::1", Error::TooManyAddressParts) + .into(), ("2001:db8:85a3::8a2e:0:", Error::TruncatedHost).into(), ("2001:db8:85a3::8a2e::", Error::TooManyDoubleColons).into(), ("20001:db8:85a3::1", Error::TooManyDigits).into(), @@ -273,5 +327,4 @@ mod tests { ); } } - } |