Преглед изворни кода

Username setter tests and fixes.

Simon Sapin пре 10 година
родитељ
комит
eb4d9b1386
4 измењених фајлова са 111 додато и 19 уклоњено
  1. 36 15
      src/lib.rs
  2. 3 3
      src/slicing.rs
  3. 2 0
      tests/data.rs
  4. 70 1
      tests/setters_tests.json

+ 36 - 15
src/lib.rs

@@ -353,9 +353,13 @@ impl Url {
         self.slice(..self.scheme_end)
     }
 
-    /// Return whether the URL has a host.
+    /// Return whether the URL has an 'authority',
+    /// which can contain a username, password, host, and port number.
+    ///
+    /// URLs that do *not* are either path-only like `unix:/run/foo.socket`
+    /// or cannot-be-a-base like `data:text/plain,Stuff`.
     #[inline]
-    pub fn has_host(&self) -> bool {
+    pub fn has_authority(&self) -> bool {
         debug_assert!(self.byte_at(self.scheme_end) == b':');
         self.slice(self.scheme_end + 1 ..).starts_with("//")
     }
@@ -373,7 +377,7 @@ impl Url {
     /// Return the username for this URL (typically the empty string)
     /// as a percent-encoded ASCII string.
     pub fn username(&self) -> &str {
-        if self.has_host() {
+        if self.has_authority() {
             self.slice(self.scheme_end + 3..self.username_end)
         } else {
             ""
@@ -384,7 +388,7 @@ impl Url {
     pub fn password(&self) -> Option<&str> {
         // This ':' is not the one marking a port number since a host can not be empty.
         // (Except for file: URLs, which do not have port numbers.)
-        if self.has_host() && self.byte_at(self.username_end) == b':' {
+        if self.has_authority() && self.byte_at(self.username_end) == b':' {
             debug_assert!(self.byte_at(self.host_start - 1) == b'@');
             Some(self.slice(self.username_end + 1..self.host_start - 1))
         } else {
@@ -392,6 +396,11 @@ impl Url {
         }
     }
 
+    /// Equivalent to `url.host().is_some()`.
+    pub fn has_host(&self) -> bool {
+        !matches!(self.host, HostInternal::None)
+    }
+
     /// Return the string representation of the host (domain or IP address) for this URL, if any.
     ///
     /// Non-ASCII domains are punycode-encoded per IDNA.
@@ -946,6 +955,7 @@ impl Url {
             return Err(())
         }
         let username_start = self.scheme_end + 3;
+        debug_assert!(self.slice(self.scheme_end..username_start) == "://");
         if self.slice(username_start..self.username_end) == username {
             return Ok(())
         }
@@ -953,24 +963,35 @@ impl Url {
         self.serialization.truncate(username_start as usize);
         self.serialization.extend(utf8_percent_encode(username, USERINFO_ENCODE_SET));
 
-        let old_username_end = self.username_end;
-        let new_username_end = to_u32(self.serialization.len()).unwrap();
+        let mut removed_bytes = self.username_end;
+        self.username_end = to_u32(self.serialization.len()).unwrap();
+        let mut added_bytes = self.username_end;
+
+        let new_username_is_empty = self.username_end == username_start;
+        match (new_username_is_empty, after_username.chars().next()) {
+            (true, Some('@')) => {
+                removed_bytes += 1;
+                self.serialization.push_str(&after_username[1..]);
+            }
+            (false, Some('@')) | (_, Some(':')) | (true, _) => {
+                self.serialization.push_str(&after_username);
+            }
+            (false, _) => {
+                added_bytes += 1;
+                self.serialization.push('@');
+                self.serialization.push_str(&after_username);
+            }
+        }
+
         let adjust = |index: &mut u32| {
-            *index -= old_username_end;
-            *index += new_username_end;
+            *index -= removed_bytes;
+            *index += added_bytes;
         };
-
-        self.username_end = new_username_end;
         adjust(&mut self.host_start);
         adjust(&mut self.host_end);
         adjust(&mut self.path_start);
         if let Some(ref mut index) = self.query_start { adjust(index) }
         if let Some(ref mut index) = self.fragment_start { adjust(index) }
-
-        if !after_username.starts_with(|c| matches!(c, '@' | ':')) {
-            self.serialization.push('@');
-        }
-        self.serialization.push_str(&after_username);
         Ok(())
     }
 

+ 3 - 3
src/slicing.rs

@@ -105,7 +105,7 @@ impl Url {
 
             Position::AfterScheme => self.scheme_end as usize,
 
-            Position::BeforeUsername => if self.has_host() {
+            Position::BeforeUsername => if self.has_authority() {
                 self.scheme_end as usize + "://".len()
             } else {
                 debug_assert!(self.byte_at(self.scheme_end) == b':');
@@ -115,7 +115,7 @@ impl Url {
 
             Position::AfterUsername => self.username_end as usize,
 
-            Position::BeforePassword => if self.has_host() &&
+            Position::BeforePassword => if self.has_authority() &&
                                            self.byte_at(self.username_end) == b':' {
                 self.username_end as usize + ":".len()
             } else {
@@ -123,7 +123,7 @@ impl Url {
                 self.username_end as usize
             },
 
-            Position::AfterPassword => if self.has_host() &&
+            Position::AfterPassword => if self.has_authority() &&
                                           self.byte_at(self.username_end) == b':' {
                 debug_assert!(self.byte_at(self.host_start - "@".len() as u32) == b'@');
                 self.host_start as usize - "@".len()

+ 2 - 0
tests/data.rs

@@ -144,9 +144,11 @@ fn collect_setters<F>(add_test: &mut F) where F: FnMut(String, test::TestFn) {
                 let mut expected = test.take("expected").unwrap();
                 add_test(name, test::TestFn::dyn_test_fn(move || {
                     let mut url = Url::parse(&href).unwrap();
+                    url.assert_invariants();
                     let _ = quirks::$setter(&mut url, &new_value);
                     assert_attributes!(url, expected,
                         href protocol username password host hostname port pathname search hash);
+                    url.assert_invariants();
                 }))
             }
         }}

+ 70 - 1
tests/setters_tests.json

@@ -138,7 +138,76 @@
             }
         }
     ],
-    "username": [],
+    "username": [
+        {
+            "comment": "No host means no username",
+            "href": "file:///home/you/index.html",
+            "new_value": "me",
+            "expected": {
+                "href": "file:///home/you/index.html",
+                "username": ""
+            }
+        },
+        {
+            "comment": "Cannot-be-a-base means no username",
+            "href": "mailto:you@example.net",
+            "new_value": "me",
+            "expected": {
+                "href": "mailto:you@example.net",
+                "username": ""
+            }
+        },
+        {
+            "href": "http://example.net",
+            "new_value": "me",
+            "expected": {
+                "href": "http://me@example.net/",
+                "username": "me"
+            }
+        },
+        {
+            "href": "http://:secret@example.net",
+            "new_value": "me",
+            "expected": {
+                "href": "http://me:secret@example.net/",
+                "username": "me"
+            }
+        },
+        {
+            "href": "http://me@example.net",
+            "new_value": "",
+            "expected": {
+                "href": "http://example.net/",
+                "username": ""
+            }
+        },
+        {
+            "href": "http://me:secret@example.net",
+            "new_value": "",
+            "expected": {
+                "href": "http://:secret@example.net/",
+                "username": ""
+            }
+        },
+        {
+            "comment": "UTF-8 percent encoding with the userinfo encode set.",
+            "href": "http://example.net",
+            "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé",
+            "expected": {
+                "href": "http://%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/",
+                "username": "%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9"
+            }
+        },
+        {
+            "comment": "Bytes already percent-encoded are left as-is.",
+            "href": "http://example.net",
+            "new_value": "%c3%89t%c3%a9",
+            "expected": {
+                "href": "http://%c3%89t%c3%a9@example.net/",
+                "username": "%c3%89t%c3%a9"
+            }
+        }
+    ],
     "password": [],
     "host": [],
     "hostname": [],