Kaynağa Gözat

Host parsing rules.

Jeremy Lempereur 7 yıl önce
ebeveyn
işleme
0586854c8b
4 değiştirilmiş dosya ile 127 ekleme ve 38 silme
  1. 3 6
      src/host.rs
  2. 18 3
      src/lib.rs
  3. 68 17
      src/parser.rs
  4. 38 12
      src/quirks.rs

+ 3 - 6
src/host.rs

@@ -24,13 +24,10 @@ pub(crate) enum HostInternal {
     Ipv6(Ipv6Addr),
 }
 
-impl<S> From<Host<S>> for HostInternal
-where
-    S: ToString,
-{
-    fn from(host: Host<S>) -> HostInternal {
+impl From<Host<String>> for HostInternal {
+    fn from(host: Host<String>) -> HostInternal {
         match host {
-            Host::Domain(ref s) if s.to_string().is_empty() => HostInternal::None,
+            Host::Domain(ref s) if s.is_empty() => HostInternal::None,
             Host::Domain(_) => HostInternal::Domain,
             Host::Ipv4(address) => HostInternal::Ipv4(address),
             Host::Ipv6(address) => HostInternal::Ipv6(address),

+ 18 - 3
src/lib.rs

@@ -690,7 +690,7 @@ impl Url {
     /// ```
     #[inline]
     pub fn cannot_be_a_base(&self) -> bool {
-        !self.slice(self.path_start..).starts_with('/')
+        !self.slice(self.scheme_end + 1..).starts_with('/')
     }
 
     /// Return the username for this URL (typically the empty string)
@@ -1642,10 +1642,25 @@ impl Url {
             if host == "" && SchemeType::from(self.scheme()).is_special() {
                 return Err(ParseError::EmptyHost);
             }
+            let mut host_substr = host;
+            // Otherwise, if c is U+003A (:) and the [] flag is unset, then
+            if !host.starts_with('[') || !host.ends_with(']') {
+                match host.find(':') {
+                    Some(0) => {
+                        // If buffer is the empty string, validation error, return failure.
+                        return Err(ParseError::InvalidDomainCharacter);
+                    }
+                    // Let host be the result of host parsing buffer
+                    Some(colon_index) => {
+                        host_substr = &host[..colon_index];
+                    }
+                    None => {}
+                }
+            }
             if SchemeType::from(self.scheme()).is_special() {
-                self.set_host_internal(Host::parse(host)?, None)
+                self.set_host_internal(Host::parse(host_substr)?, None);
             } else {
-                self.set_host_internal(Host::parse_opaque(host)?, None)
+                self.set_host_internal(Host::parse_opaque(host_substr)?, None);
             }
         } else if self.has_host() {
             if SchemeType::from(self.scheme()).is_special() {

+ 68 - 17
src/parser.rs

@@ -156,7 +156,7 @@ impl fmt::Display for SyntaxViolation {
     }
 }
 
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, PartialEq)]
 pub enum SchemeType {
     File,
     SpecialNotFile,
@@ -852,11 +852,16 @@ impl<'a> Parser<'a> {
         self.serialization.push('/');
         self.serialization.push('/');
         // authority state
+        let before_authority = self.serialization.len();
         let (username_end, remaining) = self.parse_userinfo(input, scheme_type)?;
+        let has_authority = before_authority != self.serialization.len();
         // host state
         let host_start = to_u32(self.serialization.len())?;
         let (host_end, host, port, remaining) =
             self.parse_host_and_port(remaining, scheme_end, scheme_type)?;
+        if host == HostInternal::None && has_authority {
+            return Err(ParseError::EmptyHost);
+        }
         // path state
         let path_start = to_u32(self.serialization.len())?;
         let remaining = self.parse_path_start(scheme_type, &mut true, remaining);
@@ -900,7 +905,18 @@ impl<'a> Parser<'a> {
         }
         let (mut userinfo_char_count, remaining) = match last_at {
             None => return Ok((to_u32(self.serialization.len())?, input)),
-            Some((0, remaining)) => return Ok((to_u32(self.serialization.len())?, remaining)),
+            Some((0, remaining)) => {
+                // Otherwise, if one of the following is true
+                // c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#)
+                // url is special and c is U+005C (\)
+                // If @ flag is set and buffer is the empty string, validation error, return failure.
+                if let (Some(c), _) = remaining.split_first() {
+                    if c == '/' || c == '?' || c == '#' || scheme_type.is_special() && c == '\\' {
+                        return Err(ParseError::EmptyHost);
+                    }
+                }
+                return Ok((to_u32(self.serialization.len())?, remaining));
+            }
             Some(x) => x,
         };
 
@@ -946,6 +962,18 @@ impl<'a> Parser<'a> {
         let (host, remaining) = Parser::parse_host(input, scheme_type)?;
         write!(&mut self.serialization, "{}", host).unwrap();
         let host_end = to_u32(self.serialization.len())?;
+        if let Host::Domain(h) = &host {
+            if h.is_empty() {
+                // Port with an empty host
+                if remaining.starts_with(":") {
+                    return Err(ParseError::EmptyHost);
+                }
+                if scheme_type.is_special() {
+                    return Err(ParseError::EmptyHost);
+                }
+            }
+        };
+
         let (port, remaining) = if let Some(remaining) = remaining.split_prefix(':') {
             let scheme = || default_port(&self.serialization[..scheme_end as usize]);
             Parser::parse_port(remaining, scheme, self.context)?
@@ -962,6 +990,9 @@ impl<'a> Parser<'a> {
         mut input: Input,
         scheme_type: SchemeType,
     ) -> ParseResult<(Host<String>, Input)> {
+        if scheme_type.is_file() {
+            return Parser::get_file_host(input);
+        }
         // Undo the Input abstraction here to avoid allocating in the common case
         // where the host part of the input does not contain any tab or newline
         let input_str = input.chars.as_str();
@@ -1012,10 +1043,41 @@ impl<'a> Parser<'a> {
         Ok((host, input))
     }
 
-    pub(crate) fn parse_file_host<'i>(
+    fn get_file_host<'i>(input: Input<'i>) -> ParseResult<(Host<String>, Input)> {
+        let (_, host_str, remaining) = Parser::file_host(input)?;
+        let host = match Host::parse(&host_str)? {
+            Host::Domain(ref d) if d == "localhost" => Host::Domain("".to_string()),
+            host => host,
+        };
+        Ok((host, remaining))
+    }
+
+    fn parse_file_host<'i>(
         &mut self,
         input: Input<'i>,
     ) -> ParseResult<(bool, HostInternal, Input<'i>)> {
+        let has_host;
+        let (_, host_str, remaining) = Parser::file_host(input)?;
+        let host = if host_str.is_empty() {
+            has_host = false;
+            HostInternal::None
+        } else {
+            match Host::parse(&host_str)? {
+                Host::Domain(ref d) if d == "localhost" => {
+                    has_host = false;
+                    HostInternal::None
+                }
+                host => {
+                    write!(&mut self.serialization, "{}", host).unwrap();
+                    has_host = true;
+                    host.into()
+                }
+            }
+        };
+        Ok((has_host, host, remaining))
+    }
+
+    pub fn file_host<'i>(input: Input<'i>) -> ParseResult<(bool, String, Input<'i>)> {
         // Undo the Input abstraction here to avoid allocating in the common case
         // where the host part of the input does not contain any tab or newline
         let input_str = input.chars.as_str();
@@ -1044,20 +1106,9 @@ impl<'a> Parser<'a> {
             }
         }
         if is_windows_drive_letter(host_str) {
-            return Ok((false, HostInternal::None, input));
+            return Ok((false, "".to_string(), input));
         }
-        let host = if host_str.is_empty() {
-            HostInternal::None
-        } else {
-            match Host::parse(host_str)? {
-                Host::Domain(ref d) if d == "localhost" => HostInternal::None,
-                host => {
-                    write!(&mut self.serialization, "{}", host).unwrap();
-                    host.into()
-                }
-            }
-        };
-        Ok((true, host, remaining))
+        Ok((true, host_str.to_string(), remaining))
     }
 
     pub fn parse_port<P>(
@@ -1492,7 +1543,7 @@ fn c0_control_or_space(ch: char) -> bool {
 
 /// https://infra.spec.whatwg.org/#ascii-tab-or-newline
 #[inline]
-pub fn ascii_tab_or_new_line(ch: char) -> bool {
+fn ascii_tab_or_new_line(ch: char) -> bool {
     matches!(ch, '\t' | '\r' | '\n')
 }
 

+ 38 - 12
src/quirks.rs

@@ -12,6 +12,8 @@
 //! you probably want to use `Url` method instead.
 
 use parser::{default_port, Context, Input, Parser, SchemeType};
+use std::cell::RefCell;
+use SyntaxViolation;
 use {idna, Host, ParseError, Position, Url};
 
 /// https://url.spec.whatwg.org/#dom-url-domaintoascii
@@ -110,19 +112,22 @@ pub fn set_host(url: &mut Url, new_host: &str) -> Result<(), ()> {
     let opt_port;
     {
         let scheme = url.scheme();
-        let result = Parser::parse_host(Input::new(new_host), SchemeType::from(scheme));
-        match result {
-            Ok((h, remaining)) => {
-                host = h;
-                opt_port = if let Some(remaining) = remaining.split_prefix(':') {
+        let scheme_type = SchemeType::from(scheme);
+        if let Ok((h, remaining)) = Parser::parse_host(input, scheme_type) {
+            host = h;
+            opt_port = if let Some(remaining) = remaining.split_prefix(':') {
+                if remaining.is_empty() {
+                    None
+                } else {
                     Parser::parse_port(remaining, || default_port(scheme), Context::Setter)
                         .ok()
                         .map(|(port, _remaining)| port)
-                } else {
-                    None
-                };
-            }
-            Err(_) => return Err(()),
+                }
+            } else {
+                None
+            };
+        } else {
+            return Err(());
         }
     }
     // Make sure we won't set an empty host to a url with a username or a port
@@ -154,8 +159,25 @@ pub fn set_hostname(url: &mut Url, new_hostname: &str) -> Result<(), ()> {
     if url.cannot_be_a_base() {
         return Err(());
     }
-    let result = Parser::parse_host(Input::new(new_hostname), SchemeType::from(url.scheme()));
-    if let Ok((host, _remaining)) = result {
+    // Host parsing rules are strict,
+    // We don't want to trim the input
+    let input = Input::no_trim(new_hostname);
+    let scheme_type = SchemeType::from(url.scheme());
+    if let Ok((host, _remaining)) = Parser::parse_host(input, scheme_type) {
+        if let Host::Domain(h) = &host {
+            if h.is_empty() {
+                // Empty host on special not file url
+                if SchemeType::from(url.scheme()) == SchemeType::SpecialNotFile
+                    // Port with an empty host
+                    ||!port(&url).is_empty()
+                    // Empty host with includes credentials
+                    || !url.username().is_empty()
+                    || !url.password().unwrap_or(&"").is_empty()
+                {
+                    return Err(());
+                }
+            }
+        }
         url.set_host_internal(host, None);
         Ok(())
     } else {
@@ -209,6 +231,10 @@ pub fn set_pathname(url: &mut Url, new_pathname: &str) {
         && Some('\\') == new_pathname.chars().nth(0)
     {
         url.set_path(new_pathname)
+    } else {
+        let mut path_to_set = String::from("/");
+        path_to_set.push_str(new_pathname);
+        url.set_path(&path_to_set)
     }
 }