summaryrefslogtreecommitdiff
path: root/vendor/iri-string/src/parser/validate
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/iri-string/src/parser/validate')
-rw-r--r--vendor/iri-string/src/parser/validate/authority.rs296
-rw-r--r--vendor/iri-string/src/parser/validate/path.rs91
2 files changed, 387 insertions, 0 deletions
diff --git a/vendor/iri-string/src/parser/validate/authority.rs b/vendor/iri-string/src/parser/validate/authority.rs
new file mode 100644
index 00000000..fb41085e
--- /dev/null
+++ b/vendor/iri-string/src/parser/validate/authority.rs
@@ -0,0 +1,296 @@
+//! Parsers for authority.
+
+use core::mem;
+
+use crate::parser::char;
+use crate::parser::str::{
+ find_split_hole, get_wrapped_inner, rfind_split_hole, satisfy_chars_with_pct_encoded,
+ strip_ascii_char_prefix,
+};
+use crate::spec::Spec;
+use crate::validate::Error;
+
+/// Returns `Ok(_)` if the string matches `userinfo` or `iuserinfo`.
+pub(crate) fn validate_userinfo<S: Spec>(i: &str) -> Result<(), Error> {
+ let is_valid = satisfy_chars_with_pct_encoded(
+ i,
+ char::is_ascii_userinfo_ipvfutureaddr,
+ char::is_nonascii_userinfo::<S>,
+ );
+ if is_valid {
+ Ok(())
+ } else {
+ Err(Error::new())
+ }
+}
+
+/// Returns `true` if the string matches `dec-octet`.
+///
+/// In other words, this tests whether the string is decimal "0" to "255".
+#[must_use]
+fn is_dec_octet(i: &str) -> bool {
+ matches!(
+ i.as_bytes(),
+ [b'0'..=b'9']
+ | [b'1'..=b'9', b'0'..=b'9']
+ | [b'1', b'0'..=b'9', b'0'..=b'9']
+ | [b'2', b'0'..=b'4', b'0'..=b'9']
+ | [b'2', b'5', b'0'..=b'5']
+ )
+}
+
+/// Returns `Ok(_)` if the string matches `IPv4address`.
+fn validate_ipv4address(i: &str) -> Result<(), Error> {
+ let (first, rest) = find_split_hole(i, b'.').ok_or_else(Error::new)?;
+ if !is_dec_octet(first) {
+ return Err(Error::new());
+ }
+ let (second, rest) = find_split_hole(rest, b'.').ok_or_else(Error::new)?;
+ if !is_dec_octet(second) {
+ return Err(Error::new());
+ }
+ let (third, fourth) = find_split_hole(rest, b'.').ok_or_else(Error::new)?;
+ if is_dec_octet(third) && is_dec_octet(fourth) {
+ Ok(())
+ } else {
+ Err(Error::new())
+ }
+}
+
+/// A part of IPv6 addr.
+#[derive(Clone, Copy)]
+enum V6AddrPart {
+ /// `[0-9a-fA-F]{1,4}::`.
+ H16Omit,
+ /// `[0-9a-fA-F]{1,4}:`.
+ H16Cont,
+ /// `[0-9a-fA-F]{1,4}`.
+ H16End,
+ /// IPv4 address.
+ V4,
+ /// `::`.
+ Omit,
+}
+
+/// Splits the IPv6 address string into the next component and the rest substring.
+fn split_v6_addr_part(i: &str) -> Result<(&str, V6AddrPart), Error> {
+ debug_assert!(!i.is_empty());
+ match find_split_hole(i, b':') {
+ Some((prefix, rest)) => {
+ if prefix.len() >= 5 {
+ return Err(Error::new());
+ }
+
+ if prefix.is_empty() {
+ return match strip_ascii_char_prefix(rest, b':') {
+ Some(rest) => Ok((rest, V6AddrPart::Omit)),
+ None => Err(Error::new()),
+ };
+ }
+
+ // Should be `h16`.
+ debug_assert!((1..=4).contains(&prefix.len()));
+ if !prefix.bytes().all(|b| b.is_ascii_hexdigit()) {
+ return Err(Error::new());
+ }
+ match strip_ascii_char_prefix(rest, b':') {
+ Some(rest) => Ok((rest, V6AddrPart::H16Omit)),
+ None => Ok((rest, V6AddrPart::H16Cont)),
+ }
+ }
+ None => {
+ if i.len() >= 5 {
+ // Possibly `IPv4address`.
+ validate_ipv4address(i)?;
+ return Ok(("", V6AddrPart::V4));
+ }
+ if i.bytes().all(|b| b.is_ascii_hexdigit()) {
+ Ok(("", V6AddrPart::H16End))
+ } else {
+ Err(Error::new())
+ }
+ }
+ }
+}
+
+/// Returns `Ok(_)` if the string matches `IPv6address`.
+fn validate_ipv6address(mut i: &str) -> Result<(), Error> {
+ let mut h16_count = 0;
+ let mut is_omitted = false;
+ while !i.is_empty() {
+ let (rest, part) = split_v6_addr_part(i)?;
+ match part {
+ V6AddrPart::H16Omit => {
+ h16_count += 1;
+ if mem::replace(&mut is_omitted, true) {
+ // Omitted twice.
+ return Err(Error::new());
+ }
+ }
+ V6AddrPart::H16Cont => {
+ h16_count += 1;
+ if rest.is_empty() {
+ // `H16Cont` cannot be the last part of an IPv6 address.
+ return Err(Error::new());
+ }
+ }
+ V6AddrPart::H16End => {
+ h16_count += 1;
+ break;
+ }
+ V6AddrPart::V4 => {
+ debug_assert!(rest.is_empty());
+ h16_count += 2;
+ break;
+ }
+ V6AddrPart::Omit => {
+ if mem::replace(&mut is_omitted, true) {
+ // Omitted twice.
+ return Err(Error::new());
+ }
+ }
+ }
+ if h16_count > 8 {
+ return Err(Error::new());
+ }
+ i = rest;
+ }
+ let is_valid = if is_omitted {
+ h16_count < 8
+ } else {
+ h16_count == 8
+ };
+ if is_valid {
+ Ok(())
+ } else {
+ Err(Error::new())
+ }
+}
+
+/// Returns `Ok(_)` if the string matches `authority` or `iauthority`.
+pub(super) fn validate_authority<S: Spec>(i: &str) -> Result<(), Error> {
+ // Strip and validate `userinfo`.
+ let (i, _userinfo) = match find_split_hole(i, b'@') {
+ Some((maybe_userinfo, i)) => {
+ validate_userinfo::<S>(maybe_userinfo)?;
+ (i, Some(maybe_userinfo))
+ }
+ None => (i, None),
+ };
+ // `host` can contain colons, but `port` cannot.
+ // Strip and validate `port`.
+ let (maybe_host, _port) = match rfind_split_hole(i, b':') {
+ Some((maybe_host, maybe_port)) => {
+ if maybe_port.bytes().all(|b| b.is_ascii_digit()) {
+ (maybe_host, Some(maybe_port))
+ } else {
+ (i, None)
+ }
+ }
+ None => (i, None),
+ };
+ // Validate `host`.
+ validate_host::<S>(maybe_host)
+}
+
+/// Validates `host`.
+pub(crate) fn validate_host<S: Spec>(i: &str) -> Result<(), Error> {
+ match get_wrapped_inner(i, b'[', b']') {
+ Some(maybe_addr) => {
+ // `IP-literal`.
+ // Note that `v` here is case insensitive. See RFC 3987 section 3.2.2.
+ if let Some(maybe_addr_rest) = strip_ascii_char_prefix(maybe_addr, b'v')
+ .or_else(|| strip_ascii_char_prefix(maybe_addr, b'V'))
+ {
+ // `IPvFuture`.
+ let (maybe_ver, maybe_addr) =
+ find_split_hole(maybe_addr_rest, b'.').ok_or_else(Error::new)?;
+ // Validate version.
+ if maybe_ver.is_empty() || !maybe_ver.bytes().all(|b| b.is_ascii_hexdigit()) {
+ return Err(Error::new());
+ }
+ // Validate address.
+ if !maybe_addr.is_empty()
+ && maybe_addr.is_ascii()
+ && maybe_addr
+ .bytes()
+ .all(char::is_ascii_userinfo_ipvfutureaddr)
+ {
+ Ok(())
+ } else {
+ Err(Error::new())
+ }
+ } else {
+ // `IPv6address`.
+ validate_ipv6address(maybe_addr)
+ }
+ }
+ None => {
+ // `IPv4address` or `reg-name`. No need to distinguish them here.
+ let is_valid = satisfy_chars_with_pct_encoded(
+ i,
+ char::is_ascii_regname,
+ char::is_nonascii_regname::<S>,
+ );
+ if is_valid {
+ Ok(())
+ } else {
+ Err(Error::new())
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+#[cfg(feature = "alloc")]
+mod tests {
+ use super::*;
+
+ use alloc::format;
+
+ macro_rules! assert_validate {
+ ($parser:expr, $($input:expr),* $(,)?) => {{
+ $({
+ let input = $input;
+ let input: &str = input.as_ref();
+ assert!($parser(input).is_ok(), "input={:?}", input);
+ })*
+ }};
+ }
+
+ #[test]
+ fn test_ipv6address() {
+ use core::cmp::Ordering;
+
+ assert_validate!(validate_ipv6address, "a:bB:cCc:dDdD:e:F:a:B");
+ assert_validate!(validate_ipv6address, "1:1:1:1:1:1:1:1");
+ assert_validate!(validate_ipv6address, "1:1:1:1:1:1:1.1.1.1");
+ assert_validate!(validate_ipv6address, "2001:db8::7");
+
+ // Generate IPv6 addresses with `::`.
+ let make_sub = |n: usize| {
+ let mut s = "1:".repeat(n);
+ s.pop();
+ s
+ };
+ for len_pref in 0..=7 {
+ let prefix = make_sub(len_pref);
+ for len_suf in 1..=(7 - len_pref) {
+ assert_validate!(
+ validate_ipv6address,
+ &format!("{}::{}", prefix, make_sub(len_suf))
+ );
+ match len_suf.cmp(&2) {
+ Ordering::Greater => assert_validate!(
+ validate_ipv6address,
+ &format!("{}::{}:1.1.1.1", prefix, make_sub(len_suf - 2))
+ ),
+ Ordering::Equal => {
+ assert_validate!(validate_ipv6address, &format!("{}::1.1.1.1", prefix))
+ }
+ Ordering::Less => {}
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/iri-string/src/parser/validate/path.rs b/vendor/iri-string/src/parser/validate/path.rs
new file mode 100644
index 00000000..1b09c84b
--- /dev/null
+++ b/vendor/iri-string/src/parser/validate/path.rs
@@ -0,0 +1,91 @@
+//! Parsers for path.
+
+use crate::parser::char;
+use crate::parser::str::{find_split2_hole, satisfy_chars_with_pct_encoded};
+use crate::spec::Spec;
+use crate::validate::Error;
+
+/// Returns `Ok(_)` if the string matches `path-abempty` or `ipath-abempty`.
+pub(super) fn validate_path_abempty<S: Spec>(i: &str) -> Result<(), Error> {
+ if i.is_empty() {
+ return Ok(());
+ }
+ let i = match i.strip_prefix('/') {
+ Some(rest) => rest,
+ None => return Err(Error::new()),
+ };
+ let is_valid = satisfy_chars_with_pct_encoded(
+ i,
+ char::is_ascii_pchar_slash,
+ S::is_nonascii_char_unreserved,
+ );
+ if is_valid {
+ Ok(())
+ } else {
+ Err(Error::new())
+ }
+}
+
+/// Returns `Ok(_)` if the string matches `hier-part` or `ihier-part` modulo
+/// `"//" authority path-abempty`.
+pub(super) fn validate_path_absolute_authority_absent<S: Spec>(i: &str) -> Result<(), Error> {
+ if i.is_empty() {
+ // `path-empty`.
+ return Ok(());
+ }
+ if i.starts_with("//") {
+ unreachable!("this case should be handled by the caller");
+ }
+ let is_valid = satisfy_chars_with_pct_encoded(
+ i,
+ char::is_ascii_pchar_slash,
+ S::is_nonascii_char_unreserved,
+ );
+ if is_valid {
+ Ok(())
+ } else {
+ Err(Error::new())
+ }
+}
+
+/// Returns `Ok(_)` if the string matches `relative-part` or `irelative-part` modulo
+/// `"//" authority path-abempty`.
+pub(super) fn validate_path_relative_authority_absent<S: Spec>(i: &str) -> Result<(), Error> {
+ if i.starts_with("//") {
+ unreachable!("this case should be handled by the caller");
+ }
+ let is_valid = match find_split2_hole(i, b'/', b':') {
+ Some((_, b'/', _)) | None => satisfy_chars_with_pct_encoded(
+ i,
+ char::is_ascii_pchar_slash,
+ S::is_nonascii_char_unreserved,
+ ),
+ Some((_, c, _)) => {
+ debug_assert_eq!(c, b':');
+ // `foo:bar`-style. This does not match `path-noscheme`.
+ return Err(Error::new());
+ }
+ };
+ if is_valid {
+ Ok(())
+ } else {
+ Err(Error::new())
+ }
+}
+
+/// Returns `Ok(_)` if the string matches `path`/`ipath` rules.
+pub(crate) fn validate_path<S: Spec>(i: &str) -> Result<(), Error> {
+ if i.starts_with("//") {
+ return Err(Error::new());
+ }
+ let is_valid = satisfy_chars_with_pct_encoded(
+ i,
+ char::is_ascii_pchar_slash,
+ S::is_nonascii_char_unreserved,
+ );
+ if is_valid {
+ Ok(())
+ } else {
+ Err(Error::new())
+ }
+}