Simon Sapin 10 лет назад
Родитель
Сommit
42e608e236
3 измененных файлов с 337 добавлено и 15 удалено
  1. 16 11
      src/parser.rs
  2. 4 3
      src/quirks.rs
  3. 317 1
      tests/setters_tests.json

+ 16 - 11
src/parser.rs

@@ -108,7 +108,7 @@ pub struct Parser<'a> {
     pub context: Context,
 }
 
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Copy, Clone)]
 pub enum Context {
     UrlParser,
     Setter,
@@ -601,7 +601,7 @@ impl<'a> Parser<'a> {
         Ok((username_end, remaining))
     }
 
-    pub fn parse_host_and_port<'i>(&mut self, input: &'i str,
+    fn parse_host_and_port<'i>(&mut self, input: &'i str,
                                    scheme_end: u32, scheme_type: SchemeType)
                                    -> ParseResult<(u32, HostInternal, Option<u16>, &'i str)> {
         let (host, remaining) = try!(
@@ -611,7 +611,7 @@ impl<'a> Parser<'a> {
         let (port, remaining) = if remaining.starts_with(":") {
             let syntax_violation = |message| self.syntax_violation(message);
             let scheme = || default_port(&self.serialization[..scheme_end as usize]);
-            try!(Parser::parse_port(&remaining[1..], syntax_violation, scheme))
+            try!(Parser::parse_port(&remaining[1..], syntax_violation, scheme, self.context))
         } else {
             (None, remaining)
         };
@@ -705,10 +705,11 @@ impl<'a> Parser<'a> {
         Ok((true, host, &input[end..]))
     }
 
-    pub fn parse_port<'i, V, P>(input: &'i str, syntax_violation: V, default_port: P)
+    pub fn parse_port<'i, V, P>(input: &'i str, syntax_violation: V, default_port: P,
+                                context: Context)
                                 -> ParseResult<(Option<u16>, &'i str)>
                                 where V: Fn(&'static str), P: Fn() -> Option<u16> {
-        let mut port = 0;
+        let mut port: u32 = 0;
         let mut has_any_digit = false;
         let mut end = input.len();
         for (i, c) in input.char_indices() {
@@ -720,13 +721,17 @@ impl<'a> Parser<'a> {
                 has_any_digit = true;
             } else {
                 match c {
-                    '/' | '\\' | '?' | '#' => {
-                        end = i;
-                        break
-                    },
-                    '\t' | '\n' | '\r' => syntax_violation("invalid character"),
-                    _ => return Err(ParseError::InvalidPort)
+                    '\t' | '\n' | '\r' => {
+                        syntax_violation("invalid character");
+                        continue
+                    }
+                    '/' | '\\' | '?' | '#' => {}
+                    _ => if context == Context::UrlParser {
+                        return Err(ParseError::InvalidPort)
+                    }
                 }
+                end = i;
+                break
             }
         }
         let mut opt_port = Some(port as u16);

+ 4 - 3
src/quirks.rs

@@ -12,7 +12,7 @@
 //! you probably want to use `Url` method instead.
 
 use {Url, Position, Host, ParseError, idna};
-use parser::{Parser, SchemeType, default_port};
+use parser::{Parser, SchemeType, default_port, Context};
 
 /// https://url.spec.whatwg.org/#dom-url-domaintoascii
 pub fn domain_to_ascii(domain: &str) -> String {
@@ -107,7 +107,8 @@ pub fn set_host(url: &mut Url, new_host: &str) -> Result<(), ()> {
             Ok((h, remaining)) => {
                 host = h;
                 opt_port = if remaining.starts_with(':') {
-                    Parser::parse_port(remaining, |_| (), || default_port(scheme))
+                    Parser::parse_port(&remaining[1..], |_| (), || default_port(scheme),
+                                       Context::Setter)
                     .ok().map(|(port, _remaining)| port)
                 } else {
                     None
@@ -155,7 +156,7 @@ pub fn set_port(url: &mut Url, new_port: &str) -> Result<(), ()> {
         if !url.has_host() || scheme == "file" {
             return Err(())
         }
-        result = Parser::parse_port(new_port, |_| (), || default_port(scheme))
+        result = Parser::parse_port(new_port, |_| (), || default_port(scheme), Context::Setter)
     }
     if let Ok((new_port, _remaining)) = result {
         url.set_port_internal(new_port);

+ 317 - 1
tests/setters_tests.json

@@ -83,6 +83,15 @@
                 "protocol": "bc0+-.:"
             }
         },
+        {
+            "comment": "Only some punctuation is acceptable",
+            "href": "a://example.net",
+            "new_value": "b,c",
+            "expected": {
+                "href": "a://example.net/",
+                "protocol": "a:"
+            }
+        },
         {
             "comment": "Non-ASCII is rejected",
             "href": "a://example.net",
@@ -296,7 +305,314 @@
             }
         }
     ],
-    "host": [],
+    "host": [
+        {
+            "comment": "Cannot-be-a-base means no host",
+            "href": "mailto:me@example.net",
+            "new_value": "example.com",
+            "expected": {
+                "href": "mailto:me@example.net",
+                "host": ""
+            }
+        },
+        {
+            "comment": "Cannot-be-a-base means no password",
+            "href": "data:text/plain,Stuff",
+            "new_value": "example.net",
+            "expected": {
+                "href": "data:text/plain,Stuff",
+                "host": ""
+            }
+        },
+        {
+            "href": "http://example.net",
+            "new_value": "example.com:8080",
+            "expected": {
+                "href": "http://example.com:8080/",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Port number is unchanged if not specified in the new value",
+            "href": "http://example.net:8080",
+            "new_value": "example.com",
+            "expected": {
+                "href": "http://example.com:8080/",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Port number is removed if empty in the new value: https://github.com/whatwg/url/pull/113",
+            "href": "http://example.net:8080",
+            "new_value": "example.com:",
+            "expected": {
+                "href": "http://example.com/",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Port number can be removed",
+            "href": "http://example.net:8080",
+            "new_value": "example.com:",
+            "expected": {
+                "href": "http://example.com/",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "The empty host is not valid for special schemes",
+            "href": "http://example.net",
+            "new_value": "",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net"
+            }
+        },
+        {
+            "comment": "The empty host is OK for non-special schemes",
+            "href": "view-source+http://example.net/foo",
+            "new_value": "",
+            "expected": {
+                "href": "view-source+http:///foo",
+                "host": ""
+            }
+        },
+        {
+            "comment": "Path-only URLs can gain a host",
+            "href": "a:/foo",
+            "new_value": "example.net",
+            "expected": {
+                "href": "a://example.net/foo",
+                "host": "example.net"
+            }
+        },
+        {
+            "comment": "Path-only URLs can gain a host",
+            "href": "a:/foo",
+            "new_value": "example.net",
+            "expected": {
+                "href": "a://example.net/foo",
+                "host": "example.net"
+            }
+        },
+        {
+            "comment": "IPv4 address syntax is normalized",
+            "href": "http://example.net",
+            "new_value": "0x7F000001:8080",
+            "expected": {
+                "href": "http://127.0.0.1:8080/",
+                "host": "127.0.0.1:8080",
+                "hostname": "127.0.0.1",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "IPv6 address syntax is normalized",
+            "href": "http://example.net",
+            "new_value": "[::0:01]:2",
+            "expected": {
+                "href": "http://[::1]:2/",
+                "host": "[::1]:2",
+                "hostname": "[::1]",
+                "port": "2"
+            }
+        },
+        {
+            "comment": "Default port number is removed",
+            "href": "http://example.net",
+            "new_value": "example.com:80",
+            "expected": {
+                "href": "http://example.com/",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Default port number is removed",
+            "href": "https://example.net",
+            "new_value": "example.com:443",
+            "expected": {
+                "href": "https://example.com/",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Default port number is only removed for the relevant scheme",
+            "href": "https://example.net",
+            "new_value": "example.com:80",
+            "expected": {
+                "href": "https://example.com:80/",
+                "host": "example.com:80",
+                "hostname": "example.com",
+                "port": "80"
+            }
+        },
+        {
+            "comment": "Stuff after a / delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "example.com/stuff",
+            "expected": {
+                "href": "http://example.com/path",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Stuff after a / delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "example.com:8080/stuff",
+            "expected": {
+                "href": "http://example.com:8080/path",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Stuff after a ? delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "example.com?stuff",
+            "expected": {
+                "href": "http://example.com/path",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Stuff after a ? delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "example.com:8080?stuff",
+            "expected": {
+                "href": "http://example.com:8080/path",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Stuff after a # delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "example.com#stuff",
+            "expected": {
+                "href": "http://example.com/path",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Stuff after a # delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "example.com:8080#stuff",
+            "expected": {
+                "href": "http://example.com:8080/path",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Stuff after a \\ delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "example.com\\stuff",
+            "expected": {
+                "href": "http://example.com/path",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Stuff after a \\ delimiter is ignored for special schemes",
+            "href": "http://example.net/path",
+            "new_value": "example.com:8080\\stuff",
+            "expected": {
+                "href": "http://example.com:8080/path",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "\\ is not a delimiter for non-special schemes, and it’s invalid in a domain",
+            "href": "view-source+http://example.net/path",
+            "new_value": "example.com\\stuff",
+            "expected": {
+                "href": "view-source+http://example.net/path",
+                "host": "example.net",
+                "hostname": "example.net",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error",
+            "href": "view-source+http://example.net/path",
+            "new_value": "example.com:8080stuff2",
+            "expected": {
+                "href": "view-source+http://example.com:8080/path",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error",
+            "href": "http://example.net/path",
+            "new_value": "example.com:8080stuff2",
+            "expected": {
+                "href": "http://example.com:8080/path",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error",
+            "href": "http://example.net/path",
+            "new_value": "example.com:8080+2",
+            "expected": {
+                "href": "http://example.com:8080/path",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Port numbers are 16 bit integers",
+            "href": "http://example.net/path",
+            "new_value": "example.com:65535",
+            "expected": {
+                "href": "http://example.com:65535/path",
+                "host": "example.com:65535",
+                "hostname": "example.com",
+                "port": "65535"
+            }
+        },
+        {
+            "comment": "Port numbers are 16 bit integers, overflowing is an error. Hostname is still set, though.",
+            "href": "http://example.net/path",
+            "new_value": "example.com:65536",
+            "expected": {
+                "href": "http://example.com/path",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        }
+    ],
     "hostname": [],
     "port": [],
     "pathname": [],