Repository: servo/rust-url
Pull request #674 (2021-02-08 15:26:57)

url: add the authority method
This is a revisit of #15 to add a method to be able to get the authority on its own.

Much has changed in the meantime, and the method can now be implemented without needing an additional struct.

bors-servo: :umbrella: The latest upstream changes (presumably #703) made this pull request unmergeable. Please resolve the merge conflicts.
unleashed: Rebased to fix conflicts.
SimonAdameit: Hi, I'd be interested in having this. What's still required to get it merged?
unleashed: I don't know of any specific roadblock other than having a maintainer review and approve it.
pehala: I would like to revive this PR, is there anything required other than rebasing to the current main?
valenting: Rebase on the latest changes for the CI to run.
Then we can probably merge this.

Patch:
diff --git a/url/src/lib.rs b/url/src/lib.rs
index 6dc09d1..32c02e3 100644
--- a/url/src/lib.rs
+++ b/url/src/lib.rs
@@ -792,6 +792,8 @@ impl Url {
     /// URLs that do *not* are either path-only like `unix:/run/foo.socket`
     /// or cannot-be-a-base like `data:text/plain,Stuff`.
     ///
+    /// See also the `authority` method.
+    ///
     /// # Examples
     ///
     /// ```
@@ -817,6 +819,47 @@ impl Url {
         self.slice(self.scheme_end..).starts_with("://")
     }
 
+    /// Return the authority of this URL as an ASCII string.
+    ///
+    /// Non-ASCII domains are punycode-encoded per IDNA if this is the host
+    /// of a special URL, or percent encoded for non-special URLs.
+    /// IPv6 addresses are given between `[` and `]` brackets.
+    /// Ports are omitted if they match the well known port of a special URL.
+    ///
+    /// Username and password are percent-encoded.
+    ///
+    /// See also the `has_authority` method.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use url::Url;
+    /// # use url::ParseError;
+    ///
+    /// # fn run() -> Result<(), ParseError> {
+    /// let url = Url::parse("unix:/run/foo.socket")?;
+    /// assert_eq!(url.authority(), "");
+    /// let url = Url::parse("file:///tmp/foo")?;
+    /// assert_eq!(url.authority(), "");
+    /// let url = Url::parse("https://user:password@example.com/tmp/foo")?;
+    /// assert_eq!(url.authority(), "user:password@example.com");
+    /// let url = Url::parse("irc://àlex.рф.example.com:6667/foo")?;
+    /// assert_eq!(url.authority(), "%C3%A0lex.%D1%80%D1%84.example.com:6667");
+    /// let url = Url::parse("http://àlex.рф.example.com:80/foo")?;
+    /// assert_eq!(url.authority(), "xn--lex-8ka.xn--p1ai.example.com");
+    /// # Ok(())
+    /// # }
+    /// # run().unwrap();
+    /// ```
+    pub fn authority(&self) -> &str {
+        let scheme_separator_len = "://".len() as u32;
+        if self.has_authority() && self.path_start > self.scheme_end + scheme_separator_len {
+            self.slice(self.scheme_end + scheme_separator_len..self.path_start)
+        } else {
+            ""
+        }
+    }
+
     /// Return whether this URL is a cannot-be-a-base URL,
     /// meaning that parsing a relative URL string with this URL as the base will return an error.
     ///


Test patch:
diff --git a/url/tests/unit.rs b/url/tests/unit.rs
index 55ff59a..2993430 100644
--- a/url/tests/unit.rs
+++ b/url/tests/unit.rs
@@ -1162,3 +1162,47 @@ fn test_make_relative() {
         assert_eq!(make_relative, None, "base: {}, uri: {}", base, uri);
     }
 }
+
+#[test]
+fn test_has_authority() {
+    let url = Url::parse("mailto:joe@example.com").unwrap();
+    assert!(!url.has_authority());
+    let url = Url::parse("unix:/run/foo.socket").unwrap();
+    assert!(!url.has_authority());
+    let url = Url::parse("file:///tmp/foo").unwrap();
+    assert!(url.has_authority());
+    let url = Url::parse("http://example.com/tmp/foo").unwrap();
+    assert!(url.has_authority());
+}
+
+#[test]
+fn test_authority() {
+    let url = Url::parse("mailto:joe@example.com").unwrap();
+    assert_eq!(url.authority(), "");
+    let url = Url::parse("unix:/run/foo.socket").unwrap();
+    assert_eq!(url.authority(), "");
+    let url = Url::parse("file:///tmp/foo").unwrap();
+    assert_eq!(url.authority(), "");
+    let url = Url::parse("http://example.com/tmp/foo").unwrap();
+    assert_eq!(url.authority(), "example.com");
+    let url = Url::parse("ftp://127.0.0.1:21/").unwrap();
+    assert_eq!(url.authority(), "127.0.0.1");
+    let url = Url::parse("ftp://user@127.0.0.1:2121/").unwrap();
+    assert_eq!(url.authority(), "user@127.0.0.1:2121");
+    let url = Url::parse("https://:@example.com/").unwrap();
+    assert_eq!(url.authority(), "example.com");
+    let url = Url::parse("https://:password@[::1]:8080/").unwrap();
+    assert_eq!(url.authority(), ":password@[::1]:8080");
+    let url = Url::parse("gopher://user:@àlex.example.com:70").unwrap();
+    assert_eq!(url.authority(), "user@%C3%A0lex.example.com:70");
+    let url = Url::parse("irc://àlex:àlex@àlex.рф.example.com:6667/foo").unwrap();
+    assert_eq!(
+        url.authority(),
+        "%C3%A0lex:%C3%A0lex@%C3%A0lex.%D1%80%D1%84.example.com:6667"
+    );
+    let url = Url::parse("https://àlex:àlex@àlex.рф.example.com:443/foo").unwrap();
+    assert_eq!(
+        url.authority(),
+        "%C3%A0lex:%C3%A0lex@xn--lex-8ka.xn--p1ai.example.com"
+    );
+}