Update to 2.2.2

Test: atest
Change-Id: I45105d8fe4acfba4a507c3506c2e1f5583f7a442
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index da07783..fb5f61f 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,5 +1,5 @@
 {
   "git": {
-    "sha1": "7bb95a183d0b236fa756dbd1e0f0ee210bbd2b35"
+    "sha1": "6c22912c313064a8c5a6fa043882c9ad55dba162"
   }
 }
diff --git a/Cargo.toml b/Cargo.toml
index 6e0b2ba..108b149 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@
 [package]
 edition = "2018"
 name = "url"
-version = "2.2.1"
+version = "2.2.2"
 authors = ["The rust-url developers"]
 include = ["src/**/*", "LICENSE-*", "README.md", "tests/**"]
 description = "URL library for Rust, based on the WHATWG URL Standard"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index 5f7a0fe..e384659 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -2,7 +2,7 @@
 
 name = "url"
 # When updating version, also modify html_root_url in the lib.rs
-version = "2.2.1"
+version = "2.2.2"
 authors = ["The rust-url developers"]
 
 description = "URL library for Rust, based on the WHATWG URL Standard"
diff --git a/METADATA b/METADATA
index 14ae2a5..742a22a 100644
--- a/METADATA
+++ b/METADATA
@@ -7,13 +7,13 @@
   }
   url {
     type: ARCHIVE
-    value: "https://static.crates.io/crates/url/url-2.2.1.crate"
+    value: "https://static.crates.io/crates/url/url-2.2.2.crate"
   }
-  version: "2.2.1"
+  version: "2.2.2"
   license_type: NOTICE
   last_upgrade_date {
     year: 2021
-    month: 2
-    day: 18
+    month: 5
+    day: 7
   }
 }
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 39f2e10..be39865 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -2,16 +2,19 @@
 {
   "presubmit": [
     {
-      "name": "url_device_test_tests_unit"
-    },
-    {
-      "name": "url_device_test_tests_data"
+      "name": "doh_unit_test"
     },
     {
       "name": "quiche_device_test_src_lib"
     },
     {
       "name": "url_device_test_src_lib"
+    },
+    {
+      "name": "url_device_test_tests_data"
+    },
+    {
+      "name": "url_device_test_tests_unit"
     }
   ]
 }
diff --git a/src/lib.rs b/src/lib.rs
index afe511e..42793cf 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -120,7 +120,7 @@
 ```
 */
 
-#![doc(html_root_url = "https://docs.rs/url/2.2.1")]
+#![doc(html_root_url = "https://docs.rs/url/2.2.2")]
 
 #[macro_use]
 extern crate matches;
@@ -320,6 +320,8 @@
 
     /// Parse a string as an URL, with this URL as the base URL.
     ///
+    /// The inverse of this is [`make_relative`].
+    ///
     /// Note: a trailing slash is significant.
     /// Without it, the last path component is considered to be a “file” name
     /// to be removed to get at the “directory” that is used as the base:
@@ -349,11 +351,144 @@
     /// with this URL as the base URL, a [`ParseError`] variant will be returned.
     ///
     /// [`ParseError`]: enum.ParseError.html
+    /// [`make_relative`]: #method.make_relative
     #[inline]
     pub fn join(&self, input: &str) -> Result<Url, crate::ParseError> {
         Url::options().base_url(Some(self)).parse(input)
     }
 
+    /// Creates a relative URL if possible, with this URL as the base URL.
+    ///
+    /// This is the inverse of [`join`].
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use url::Url;
+    /// # use url::ParseError;
+    ///
+    /// # fn run() -> Result<(), ParseError> {
+    /// let base = Url::parse("https://example.net/a/b.html")?;
+    /// let url = Url::parse("https://example.net/a/c.png")?;
+    /// let relative = base.make_relative(&url);
+    /// assert_eq!(relative.as_ref().map(|s| s.as_str()), Some("c.png"));
+    ///
+    /// let base = Url::parse("https://example.net/a/b/")?;
+    /// let url = Url::parse("https://example.net/a/b/c.png")?;
+    /// let relative = base.make_relative(&url);
+    /// assert_eq!(relative.as_ref().map(|s| s.as_str()), Some("c.png"));
+    ///
+    /// let base = Url::parse("https://example.net/a/b/")?;
+    /// let url = Url::parse("https://example.net/a/d/c.png")?;
+    /// let relative = base.make_relative(&url);
+    /// assert_eq!(relative.as_ref().map(|s| s.as_str()), Some("../d/c.png"));
+    ///
+    /// let base = Url::parse("https://example.net/a/b.html?c=d")?;
+    /// let url = Url::parse("https://example.net/a/b.html?e=f")?;
+    /// let relative = base.make_relative(&url);
+    /// assert_eq!(relative.as_ref().map(|s| s.as_str()), Some("?e=f"));
+    /// # Ok(())
+    /// # }
+    /// # run().unwrap();
+    /// ```
+    ///
+    /// # Errors
+    ///
+    /// If this URL can't be a base for the given URL, `None` is returned.
+    /// This is for example the case if the scheme, host or port are not the same.
+    ///
+    /// [`join`]: #method.join
+    pub fn make_relative(&self, url: &Url) -> Option<String> {
+        if self.cannot_be_a_base() {
+            return None;
+        }
+
+        // Scheme, host and port need to be the same
+        if self.scheme() != url.scheme() || self.host() != url.host() || self.port() != url.port() {
+            return None;
+        }
+
+        // We ignore username/password at this point
+
+        // The path has to be transformed
+        let mut relative = String::new();
+
+        // Extract the filename of both URIs, these need to be handled separately
+        fn extract_path_filename(s: &str) -> (&str, &str) {
+            let last_slash_idx = s.rfind('/').unwrap_or(0);
+            let (path, filename) = s.split_at(last_slash_idx);
+            if filename.is_empty() {
+                (path, "")
+            } else {
+                (path, &filename[1..])
+            }
+        }
+
+        let (base_path, base_filename) = extract_path_filename(self.path());
+        let (url_path, url_filename) = extract_path_filename(url.path());
+
+        let mut base_path = base_path.split('/').peekable();
+        let mut url_path = url_path.split('/').peekable();
+
+        // Skip over the common prefix
+        while base_path.peek().is_some() && base_path.peek() == url_path.peek() {
+            base_path.next();
+            url_path.next();
+        }
+
+        // Add `..` segments for the remainder of the base path
+        for base_path_segment in base_path {
+            // Skip empty last segments
+            if base_path_segment.is_empty() {
+                break;
+            }
+
+            if !relative.is_empty() {
+                relative.push('/');
+            }
+
+            relative.push_str("..");
+        }
+
+        // Append the remainder of the other URI
+        for url_path_segment in url_path {
+            if !relative.is_empty() {
+                relative.push('/');
+            }
+
+            relative.push_str(url_path_segment);
+        }
+
+        // Add the filename if they are not the same
+        if base_filename != url_filename {
+            // If the URIs filename is empty this means that it was a directory
+            // so we'll have to append a '/'.
+            //
+            // Otherwise append it directly as the new filename.
+            if url_filename.is_empty() {
+                relative.push('/');
+            } else {
+                if !relative.is_empty() {
+                    relative.push('/');
+                }
+                relative.push_str(url_filename);
+            }
+        }
+
+        // Query and fragment are only taken from the other URI
+        if let Some(query) = url.query() {
+            relative.push('?');
+            relative.push_str(query);
+        }
+
+        if let Some(fragment) = url.fragment() {
+            relative.push('#');
+            relative.push_str(fragment);
+        }
+
+        Some(relative)
+    }
+
     /// Return a default `ParseOptions` that can fully configure the URL parser.
     ///
     /// # Examples
@@ -417,14 +552,15 @@
     /// # fn run() -> Result<(), ParseError> {
     /// let url_str = "https://example.net/";
     /// let url = Url::parse(url_str)?;
-    /// assert_eq!(url.into_string(), url_str);
+    /// assert_eq!(String::from(url), url_str);
     /// # Ok(())
     /// # }
     /// # run().unwrap();
     /// ```
     #[inline]
+    #[deprecated(since = "2.3.0", note = "use Into<String>")]
     pub fn into_string(self) -> String {
-        self.serialization
+        self.into()
     }
 
     /// For internal testing, not part of the public API.
@@ -1433,7 +1569,7 @@
     /// Return an object with methods to manipulate this URL’s path segments.
     ///
     /// Return `Err(())` if this URL is cannot-be-a-base.
-    #[allow(clippy::clippy::result_unit_err)]
+    #[allow(clippy::result_unit_err)]
     pub fn path_segments_mut(&mut self) -> Result<PathSegmentsMut<'_>, ()> {
         if self.cannot_be_a_base() {
             Err(())
@@ -1517,7 +1653,7 @@
     /// # }
     /// # run().unwrap();
     /// ```
-    #[allow(clippy::clippy::result_unit_err)]
+    #[allow(clippy::result_unit_err)]
     pub fn set_port(&mut self, mut port: Option<u16>) -> Result<(), ()> {
         // has_host implies !cannot_be_a_base
         if !self.has_host() || self.host() == Some(Host::Domain("")) || self.scheme() == "file" {
@@ -1788,7 +1924,7 @@
     /// # run().unwrap();
     /// ```
     ///
-    #[allow(clippy::clippy::result_unit_err)]
+    #[allow(clippy::result_unit_err)]
     pub fn set_ip_host(&mut self, address: IpAddr) -> Result<(), ()> {
         if self.cannot_be_a_base() {
             return Err(());
@@ -1828,7 +1964,7 @@
     /// # }
     /// # run().unwrap();
     /// ```
-    #[allow(clippy::clippy::result_unit_err)]
+    #[allow(clippy::result_unit_err)]
     pub fn set_password(&mut self, password: Option<&str>) -> Result<(), ()> {
         // has_host implies !cannot_be_a_base
         if !self.has_host() || self.host() == Some(Host::Domain("")) || self.scheme() == "file" {
@@ -1921,7 +2057,7 @@
     /// # }
     /// # run().unwrap();
     /// ```
-    #[allow(clippy::clippy::result_unit_err)]
+    #[allow(clippy::result_unit_err)]
     pub fn set_username(&mut self, username: &str) -> Result<(), ()> {
         // has_host implies !cannot_be_a_base
         if !self.has_host() || self.host() == Some(Host::Domain("")) || self.scheme() == "file" {
@@ -2163,7 +2299,7 @@
     /// # }
     /// ```
     #[cfg(any(unix, windows, target_os = "redox"))]
-    #[allow(clippy::clippy::result_unit_err)]
+    #[allow(clippy::result_unit_err)]
     pub fn from_file_path<P: AsRef<Path>>(path: P) -> Result<Url, ()> {
         let mut serialization = "file://".to_owned();
         let host_start = serialization.len() as u32;
@@ -2200,7 +2336,7 @@
     /// Note that `std::path` does not consider trailing slashes significant
     /// and usually does not include them (e.g. in `Path::parent()`).
     #[cfg(any(unix, windows, target_os = "redox"))]
-    #[allow(clippy::clippy::result_unit_err)]
+    #[allow(clippy::result_unit_err)]
     pub fn from_directory_path<P: AsRef<Path>>(path: P) -> Result<Url, ()> {
         let mut url = Url::from_file_path(path)?;
         if !url.serialization.ends_with('/') {
@@ -2317,7 +2453,7 @@
     /// for a Windows path, is not UTF-8.)
     #[inline]
     #[cfg(any(unix, windows, target_os = "redox"))]
-    #[allow(clippy::clippy::result_unit_err)]
+    #[allow(clippy::result_unit_err)]
     pub fn to_file_path(&self) -> Result<PathBuf, ()> {
         if let Some(segments) = self.path_segments() {
             let host = match self.host() {
@@ -2375,6 +2511,13 @@
     }
 }
 
+/// String converstion.
+impl From<Url> for String {
+    fn from(value: Url) -> String {
+        value.serialization
+    }
+}
+
 /// Debug the serialization of this URL.
 impl fmt::Debug for Url {
     #[inline]
@@ -2382,6 +2525,7 @@
         formatter
             .debug_struct("Url")
             .field("scheme", &self.scheme())
+            .field("cannot_be_a_base", &self.cannot_be_a_base())
             .field("username", &self.username())
             .field("password", &self.password())
             .field("host", &self.host())
diff --git a/src/quirks.rs b/src/quirks.rs
index 72dadaa..0dbc6eb 100644
--- a/src/quirks.rs
+++ b/src/quirks.rs
@@ -56,7 +56,7 @@
 }
 
 /// Setter for https://url.spec.whatwg.org/#dom-url-protocol
-#[allow(clippy::clippy::result_unit_err)]
+#[allow(clippy::result_unit_err)]
 pub fn set_protocol(url: &mut Url, mut new_protocol: &str) -> Result<(), ()> {
     // The scheme state in the spec ignores everything after the first `:`,
     // but `set_scheme` errors if there is more.
@@ -73,7 +73,7 @@
 }
 
 /// Setter for https://url.spec.whatwg.org/#dom-url-username
-#[allow(clippy::clippy::result_unit_err)]
+#[allow(clippy::result_unit_err)]
 pub fn set_username(url: &mut Url, new_username: &str) -> Result<(), ()> {
     url.set_username(new_username)
 }
@@ -85,7 +85,7 @@
 }
 
 /// Setter for https://url.spec.whatwg.org/#dom-url-password
-#[allow(clippy::clippy::result_unit_err)]
+#[allow(clippy::result_unit_err)]
 pub fn set_password(url: &mut Url, new_password: &str) -> Result<(), ()> {
     url.set_password(if new_password.is_empty() {
         None
@@ -101,7 +101,7 @@
 }
 
 /// Setter for https://url.spec.whatwg.org/#dom-url-host
-#[allow(clippy::clippy::result_unit_err)]
+#[allow(clippy::result_unit_err)]
 pub fn set_host(url: &mut Url, new_host: &str) -> Result<(), ()> {
     // If context object’s url’s cannot-be-a-base-URL flag is set, then return.
     if url.cannot_be_a_base() {
@@ -158,7 +158,7 @@
 }
 
 /// Setter for https://url.spec.whatwg.org/#dom-url-hostname
-#[allow(clippy::clippy::result_unit_err)]
+#[allow(clippy::result_unit_err)]
 pub fn set_hostname(url: &mut Url, new_hostname: &str) -> Result<(), ()> {
     if url.cannot_be_a_base() {
         return Err(());
@@ -200,7 +200,7 @@
 }
 
 /// Setter for https://url.spec.whatwg.org/#dom-url-port
-#[allow(clippy::clippy::result_unit_err)]
+#[allow(clippy::result_unit_err)]
 pub fn set_port(url: &mut Url, new_port: &str) -> Result<(), ()> {
     let result;
     {
diff --git a/tests/unit.rs b/tests/unit.rs
index 4c25198..13055a4 100644
--- a/tests/unit.rs
+++ b/tests/unit.rs
@@ -12,7 +12,7 @@
 use std::cell::{Cell, RefCell};
 use std::net::{Ipv4Addr, Ipv6Addr};
 use std::path::{Path, PathBuf};
-use url::{form_urlencoded, Host, Url};
+use url::{form_urlencoded, Host, Origin, Url};
 
 #[test]
 fn size() {
@@ -518,6 +518,209 @@
 }
 
 #[test]
+fn test_origin_blob_equality() {
+    let origin = &Url::parse("http://example.net/").unwrap().origin();
+    let blob_origin = &Url::parse("blob:http://example.net/").unwrap().origin();
+
+    assert_eq!(origin, blob_origin);
+}
+
+#[test]
+fn test_origin_opaque() {
+    assert!(!Origin::new_opaque().is_tuple());
+    assert!(!&Url::parse("blob:malformed//").unwrap().origin().is_tuple())
+}
+
+#[test]
+fn test_origin_unicode_serialization() {
+    let data = [
+        ("http://😅.com", "http://😅.com"),
+        ("ftp://😅:🙂@🙂.com", "ftp://🙂.com"),
+        ("https://user@😅.com", "https://😅.com"),
+        ("http://😅.🙂:40", "http://😅.🙂:40"),
+    ];
+    for &(unicode_url, expected_serialization) in &data {
+        let origin = Url::parse(unicode_url).unwrap().origin();
+        assert_eq!(origin.unicode_serialization(), *expected_serialization);
+    }
+
+    let ascii_origins = [
+        Url::parse("http://example.net/").unwrap().origin(),
+        Url::parse("http://example.net:80/").unwrap().origin(),
+        Url::parse("http://example.net:81/").unwrap().origin(),
+        Url::parse("http://example.net").unwrap().origin(),
+        Url::parse("http://example.net/hello").unwrap().origin(),
+        Url::parse("https://example.net").unwrap().origin(),
+        Url::parse("ftp://example.net").unwrap().origin(),
+        Url::parse("file://example.net").unwrap().origin(),
+        Url::parse("http://user@example.net/").unwrap().origin(),
+        Url::parse("http://user:pass@example.net/")
+            .unwrap()
+            .origin(),
+        Url::parse("http://127.0.0.1").unwrap().origin(),
+    ];
+    for ascii_origin in &ascii_origins {
+        assert_eq!(
+            ascii_origin.ascii_serialization(),
+            ascii_origin.unicode_serialization()
+        );
+    }
+}
+
+#[test]
+fn test_socket_addrs() {
+    use std::net::ToSocketAddrs;
+
+    let data = [
+        ("https://127.0.0.1/", "127.0.0.1", 443),
+        ("https://127.0.0.1:9742/", "127.0.0.1", 9742),
+        ("custom-protocol://127.0.0.1:9742/", "127.0.0.1", 9742),
+        ("custom-protocol://127.0.0.1/", "127.0.0.1", 9743),
+        ("https://[::1]/", "::1", 443),
+        ("https://[::1]:9742/", "::1", 9742),
+        ("custom-protocol://[::1]:9742/", "::1", 9742),
+        ("custom-protocol://[::1]/", "::1", 9743),
+        ("https://localhost/", "localhost", 443),
+        ("https://localhost:9742/", "localhost", 9742),
+        ("custom-protocol://localhost:9742/", "localhost", 9742),
+        ("custom-protocol://localhost/", "localhost", 9743),
+    ];
+
+    for (url_string, host, port) in &data {
+        let url = url::Url::parse(url_string).unwrap();
+        let addrs = url
+            .socket_addrs(|| match url.scheme() {
+                "custom-protocol" => Some(9743),
+                _ => None,
+            })
+            .unwrap();
+        assert_eq!(
+            Some(addrs[0]),
+            (*host, *port).to_socket_addrs().unwrap().next()
+        );
+    }
+}
+
+#[test]
+fn test_no_base_url() {
+    let mut no_base_url = Url::parse("mailto:test@example.net").unwrap();
+
+    assert!(no_base_url.cannot_be_a_base());
+    assert!(no_base_url.path_segments().is_none());
+    assert!(no_base_url.path_segments_mut().is_err());
+    assert!(no_base_url.set_host(Some("foo")).is_err());
+    assert!(no_base_url
+        .set_ip_host("127.0.0.1".parse().unwrap())
+        .is_err());
+
+    no_base_url.set_path("/foo");
+    assert_eq!(no_base_url.path(), "%2Ffoo");
+}
+
+#[test]
+fn test_domain() {
+    let url = Url::parse("https://127.0.0.1/").unwrap();
+    assert_eq!(url.domain(), None);
+
+    let url = Url::parse("mailto:test@example.net").unwrap();
+    assert_eq!(url.domain(), None);
+
+    let url = Url::parse("https://example.com/").unwrap();
+    assert_eq!(url.domain(), Some("example.com"));
+}
+
+#[test]
+fn test_query() {
+    let url = Url::parse("https://example.com/products?page=2#fragment").unwrap();
+    assert_eq!(url.query(), Some("page=2"));
+    assert_eq!(
+        url.query_pairs().next(),
+        Some((Cow::Borrowed("page"), Cow::Borrowed("2")))
+    );
+
+    let url = Url::parse("https://example.com/products").unwrap();
+    assert!(url.query().is_none());
+    assert_eq!(url.query_pairs().count(), 0);
+
+    let url = Url::parse("https://example.com/?country=español").unwrap();
+    assert_eq!(url.query(), Some("country=espa%C3%B1ol"));
+    assert_eq!(
+        url.query_pairs().next(),
+        Some((Cow::Borrowed("country"), Cow::Borrowed("español")))
+    );
+
+    let url = Url::parse("https://example.com/products?page=2&sort=desc").unwrap();
+    assert_eq!(url.query(), Some("page=2&sort=desc"));
+    let mut pairs = url.query_pairs();
+    assert_eq!(pairs.count(), 2);
+    assert_eq!(
+        pairs.next(),
+        Some((Cow::Borrowed("page"), Cow::Borrowed("2")))
+    );
+    assert_eq!(
+        pairs.next(),
+        Some((Cow::Borrowed("sort"), Cow::Borrowed("desc")))
+    );
+}
+
+#[test]
+fn test_fragment() {
+    let url = Url::parse("https://example.com/#fragment").unwrap();
+    assert_eq!(url.fragment(), Some("fragment"));
+
+    let url = Url::parse("https://example.com/").unwrap();
+    assert_eq!(url.fragment(), None);
+}
+
+#[test]
+fn test_set_ip_host() {
+    let mut url = Url::parse("http://example.com").unwrap();
+
+    url.set_ip_host("127.0.0.1".parse().unwrap()).unwrap();
+    assert_eq!(url.host_str(), Some("127.0.0.1"));
+
+    url.set_ip_host("::1".parse().unwrap()).unwrap();
+    assert_eq!(url.host_str(), Some("[::1]"));
+}
+
+#[test]
+fn test_set_href() {
+    use url::quirks::set_href;
+
+    let mut url = Url::parse("https://existing.url").unwrap();
+
+    assert!(set_href(&mut url, "mal//formed").is_err());
+
+    assert!(set_href(
+        &mut url,
+        "https://user:pass@domain.com:9742/path/file.ext?key=val&key2=val2#fragment"
+    )
+    .is_ok());
+    assert_eq!(
+        url,
+        Url::parse("https://user:pass@domain.com:9742/path/file.ext?key=val&key2=val2#fragment")
+            .unwrap()
+    );
+}
+
+#[test]
+fn test_domain_encoding_quirks() {
+    use url::quirks::{domain_to_ascii, domain_to_unicode};
+
+    let data = [
+        ("http://example.com", "", ""),
+        ("😅.🙂", "xn--j28h.xn--938h", "😅.🙂"),
+        ("example.com", "example.com", "example.com"),
+        ("mailto:test@example.net", "", ""),
+    ];
+
+    for url in &data {
+        assert_eq!(domain_to_ascii(url.0), url.1);
+        assert_eq!(domain_to_unicode(url.0), url.2);
+    }
+}
+
+#[test]
 fn test_windows_unc_path() {
     if !cfg!(windows) {
         return;
@@ -582,6 +785,38 @@
 }
 
 #[test]
+fn test_syntax_violation_callback_types() {
+    use url::SyntaxViolation::*;
+
+    let data = [
+        ("http://mozilla.org/\\foo", Backslash, "backslash"),
+        (" http://mozilla.org", C0SpaceIgnored, "leading or trailing control or space character are ignored in URLs"),
+        ("http://user:pass@mozilla.org", EmbeddedCredentials, "embedding authentication information (username or password) in an URL is not recommended"),
+        ("http:///mozilla.org", ExpectedDoubleSlash, "expected //"),
+        ("file:/foo.txt", ExpectedFileDoubleSlash, "expected // after file:"),
+        ("file://mozilla.org/c:/file.txt", FileWithHostAndWindowsDrive, "file: with host and Windows drive letter"),
+        ("http://mozilla.org/^", NonUrlCodePoint, "non-URL code point"),
+        ("http://mozilla.org/#\00", NullInFragment, "NULL characters are ignored in URL fragment identifiers"),
+        ("http://mozilla.org/%1", PercentDecode, "expected 2 hex digits after %"),
+        ("http://mozilla.org\t/foo", TabOrNewlineIgnored, "tabs or newlines are ignored in URLs"),
+        ("http://user@:pass@mozilla.org", UnencodedAtSign, "unencoded @ sign in username or password")
+    ];
+
+    for test_case in &data {
+        let violation = Cell::new(None);
+        Url::options()
+            .syntax_violation_callback(Some(&|v| violation.set(Some(v))))
+            .parse(test_case.0)
+            .unwrap();
+
+        let v = violation.take();
+        assert_eq!(v, Some(test_case.1));
+        assert_eq!(v.unwrap().description(), test_case.2);
+        assert_eq!(v.unwrap().to_string(), test_case.2);
+    }
+}
+
+#[test]
 fn test_options_reuse() {
     use url::SyntaxViolation::*;
     let violations = RefCell::new(Vec::new());
@@ -679,3 +914,205 @@
     segments.pop_if_empty();
     segments.pop();
 }
+
+#[test]
+fn test_slicing() {
+    use url::Position::*;
+
+    #[derive(Default)]
+    struct ExpectedSlices<'a> {
+        full: &'a str,
+        scheme: &'a str,
+        username: &'a str,
+        password: &'a str,
+        host: &'a str,
+        port: &'a str,
+        path: &'a str,
+        query: &'a str,
+        fragment: &'a str,
+    }
+
+    let data = [
+        ExpectedSlices {
+            full: "https://user:pass@domain.com:9742/path/file.ext?key=val&key2=val2#fragment",
+            scheme: "https",
+            username: "user",
+            password: "pass",
+            host: "domain.com",
+            port: "9742",
+            path: "/path/file.ext",
+            query: "key=val&key2=val2",
+            fragment: "fragment",
+        },
+        ExpectedSlices {
+            full: "https://domain.com:9742/path/file.ext#fragment",
+            scheme: "https",
+            host: "domain.com",
+            port: "9742",
+            path: "/path/file.ext",
+            fragment: "fragment",
+            ..Default::default()
+        },
+        ExpectedSlices {
+            full: "https://domain.com:9742/path/file.ext",
+            scheme: "https",
+            host: "domain.com",
+            port: "9742",
+            path: "/path/file.ext",
+            ..Default::default()
+        },
+        ExpectedSlices {
+            full: "blob:blob-info",
+            scheme: "blob",
+            path: "blob-info",
+            ..Default::default()
+        },
+    ];
+
+    for expected_slices in &data {
+        let url = Url::parse(expected_slices.full).unwrap();
+        assert_eq!(&url[..], expected_slices.full);
+        assert_eq!(&url[BeforeScheme..AfterScheme], expected_slices.scheme);
+        assert_eq!(
+            &url[BeforeUsername..AfterUsername],
+            expected_slices.username
+        );
+        assert_eq!(
+            &url[BeforePassword..AfterPassword],
+            expected_slices.password
+        );
+        assert_eq!(&url[BeforeHost..AfterHost], expected_slices.host);
+        assert_eq!(&url[BeforePort..AfterPort], expected_slices.port);
+        assert_eq!(&url[BeforePath..AfterPath], expected_slices.path);
+        assert_eq!(&url[BeforeQuery..AfterQuery], expected_slices.query);
+        assert_eq!(
+            &url[BeforeFragment..AfterFragment],
+            expected_slices.fragment
+        );
+        assert_eq!(&url[..AfterFragment], expected_slices.full);
+    }
+}
+
+#[test]
+fn test_make_relative() {
+    let tests = [
+        (
+            "http://127.0.0.1:8080/test",
+            "http://127.0.0.1:8080/test",
+            "",
+        ),
+        (
+            "http://127.0.0.1:8080/test",
+            "http://127.0.0.1:8080/test/",
+            "test/",
+        ),
+        (
+            "http://127.0.0.1:8080/test/",
+            "http://127.0.0.1:8080/test",
+            "../test",
+        ),
+        (
+            "http://127.0.0.1:8080/",
+            "http://127.0.0.1:8080/?foo=bar#123",
+            "?foo=bar#123",
+        ),
+        (
+            "http://127.0.0.1:8080/",
+            "http://127.0.0.1:8080/test/video",
+            "test/video",
+        ),
+        (
+            "http://127.0.0.1:8080/test",
+            "http://127.0.0.1:8080/test/video",
+            "test/video",
+        ),
+        (
+            "http://127.0.0.1:8080/test/",
+            "http://127.0.0.1:8080/test/video",
+            "video",
+        ),
+        (
+            "http://127.0.0.1:8080/test",
+            "http://127.0.0.1:8080/test2/video",
+            "test2/video",
+        ),
+        (
+            "http://127.0.0.1:8080/test/",
+            "http://127.0.0.1:8080/test2/video",
+            "../test2/video",
+        ),
+        (
+            "http://127.0.0.1:8080/test/bla",
+            "http://127.0.0.1:8080/test2/video",
+            "../test2/video",
+        ),
+        (
+            "http://127.0.0.1:8080/test/bla/",
+            "http://127.0.0.1:8080/test2/video",
+            "../../test2/video",
+        ),
+        (
+            "http://127.0.0.1:8080/test/?foo=bar#123",
+            "http://127.0.0.1:8080/test/video",
+            "video",
+        ),
+        (
+            "http://127.0.0.1:8080/test/",
+            "http://127.0.0.1:8080/test/video?baz=meh#456",
+            "video?baz=meh#456",
+        ),
+        (
+            "http://127.0.0.1:8080/test",
+            "http://127.0.0.1:8080/test?baz=meh#456",
+            "?baz=meh#456",
+        ),
+        (
+            "http://127.0.0.1:8080/test/",
+            "http://127.0.0.1:8080/test?baz=meh#456",
+            "../test?baz=meh#456",
+        ),
+        (
+            "http://127.0.0.1:8080/test/",
+            "http://127.0.0.1:8080/test/?baz=meh#456",
+            "?baz=meh#456",
+        ),
+        (
+            "http://127.0.0.1:8080/test/?foo=bar#123",
+            "http://127.0.0.1:8080/test/video?baz=meh#456",
+            "video?baz=meh#456",
+        ),
+    ];
+
+    for (base, uri, relative) in &tests {
+        let base_uri = url::Url::parse(base).unwrap();
+        let relative_uri = url::Url::parse(uri).unwrap();
+        let make_relative = base_uri.make_relative(&relative_uri).unwrap();
+        assert_eq!(
+            make_relative, *relative,
+            "base: {}, uri: {}, relative: {}",
+            base, uri, relative
+        );
+        assert_eq!(
+            base_uri.join(&relative).unwrap().as_str(),
+            *uri,
+            "base: {}, uri: {}, relative: {}",
+            base,
+            uri,
+            relative
+        );
+    }
+
+    let error_tests = [
+        ("http://127.0.0.1:8080/", "https://127.0.0.1:8080/test/"),
+        ("http://127.0.0.1:8080/", "http://127.0.0.1:8081/test/"),
+        ("http://127.0.0.1:8080/", "http://127.0.0.2:8080/test/"),
+        ("mailto:a@example.com", "mailto:b@example.com"),
+    ];
+
+    for (base, uri) in &error_tests {
+        let base_uri = url::Url::parse(base).unwrap();
+        let relative_uri = url::Url::parse(uri).unwrap();
+        let make_relative = base_uri.make_relative(&relative_uri);
+        assert_eq!(make_relative, None, "base: {}, uri: {}", base, uri);
+    }
+}