Blob Blame History Raw
diff -Nrup a/netwerk/base/rust-url-capi/Cargo.toml b/netwerk/base/rust-url-capi/Cargo.toml
--- a/netwerk/base/rust-url-capi/Cargo.toml	2020-02-18 02:37:59.000000000 +0300
+++ b/netwerk/base/rust-url-capi/Cargo.toml	2020-02-28 20:28:57.265406022 +0300
@@ -8,6 +8,6 @@ name = "rust_url_capi"
 
 [dependencies]
 libc = "0.2.0"
-url = "1.5.1"
+url = "1.7.2"
 nsstring = { path = "../../../xpcom/rust/nsstring" }
 nserror = { path = "../../../xpcom/rust/nserror" }
diff -Nrup a/third_party/rust/url/.cargo-checksum.json b/third_party/rust/url/.cargo-checksum.json
--- a/third_party/rust/url/.cargo-checksum.json	2020-02-18 02:39:21.000000000 +0300
+++ b/third_party/rust/url/.cargo-checksum.json	2020-02-28 20:28:57.261406050 +0300
@@ -1 +1 @@
-{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".travis.yml":"890af214187ffcba4732acb2d1af30d7adb9aade0679e9fdb06baae363240b8e","Cargo.toml":"ec586106c4d0625919a3591fe3ae915043e82c8bfdd1c9e747171ba5e21047e1","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e","Makefile":"bffd75d34654b2955d4f005f1a5e85c821c90becf1a8a52cbe10121972f43148","README.md":"eb3f4694003f408cbe3c7f3e9fbbc71241defb940cc55a816981f0f0f144c8eb","UPGRADING.md":"fbcc2d39bdf17db0745793db6626fcd5c909dddd4ce13b27566cfabece22c368","appveyor.yml":"c78486dbfbe6ebbf3d808afb9a19f7ec18c4704ce451c6305f0716999b70a1a6","docs/.nojekyll":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","docs/404.html":"f61e6271c1ea1aa113b64b356e994595fa548f0433f89948d747503ad22195cd","docs/index.html":"f61e6271c1ea1aa113b64b356e994595fa548f0433f89948d747503ad22195cd","github.png":"b432fd855efe7c430fe6a57ccf83935c1996f03a7cdc8d6e1b34154b8c43f6ec","rust-url-todo":"1192cee7b6cedf2133d97dc6074b593a1d19b0ee13fff6f28d6329855044e575","src/encoding.rs":"f3e109ca8ec5a9130da50cdfb3003530aedb6dd5a440f0790d76b71f6981119c","src/form_urlencoded.rs":"7ccaef7148e4bc2577154c50f8705db3a055b641269e24c22770f06222321e1e","src/host.rs":"281165d732ea87b6f01a98f7c68ffcb284c41f84b3ab6ed674fb8e57022d1019","src/lib.rs":"bd156e8bcfbd44f0cd52c8b394e03ec63fea012c0bf5ca554521352714838605","src/origin.rs":"7071dcc1070ccfae84cdcd43586b84a9706e35a9a099ff4dde128da0909bd0bc","src/parser.rs":"9d30868f0900586fec6f122a0322598a08116ab0b4c4d8caf5c35a720381a73a","src/path_segments.rs":"7bd3142eaa568863ef44e2255c181239141f9eeee337f889b9ffaaeab4ca669d","src/quirks.rs":"1231f965e22bb3632c22993e2a8d4c7470bcb4a8de25d049f31784303f0def03","src/slicing.rs":"4e539886b23945a92094625f3e531a4bff40daa44240b5d19ee8577478c4f7fe","tests/data.rs":"c333766897f6492fb6583ab5c8a511973b7a55f58ca550799432343da64d5ca7","tests/setters_tests.json":"ebcbdb52e9a4b5a565f8806d52ebc610d46a34df883e10b0be080d026468ff73","tests/unit.rs":"c2f206f433be619414d761d358a2a4a5a46cfe8a4fea5339adec5e9937d78de2","tests/urltestdata.json":"430c74aa3a31afaa57a92805544e00825f4dffe2def98c1e3c212c3db80268af"},"package":"eeb819346883532a271eb626deb43c4a1bb4c4dd47c519bd78137c3e72a4fe27"}
\ No newline at end of file
+{"files":{"Cargo.toml":"80d575ae6adad93cb0910b385b871e2d92d558078f58a3c8eafe95940d459f6b","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e","README.md":"eb3f4694003f408cbe3c7f3e9fbbc71241defb940cc55a816981f0f0f144c8eb","UPGRADING.md":"fbcc2d39bdf17db0745793db6626fcd5c909dddd4ce13b27566cfabece22c368","appveyor.yml":"c78486dbfbe6ebbf3d808afb9a19f7ec18c4704ce451c6305f0716999b70a1a6","benches/parse_url.rs":"821ecb051c3c6c40eb3b268ba7337b2988333627d0af0c8e1afc84734ffbbf2b","docs/404.html":"f61e6271c1ea1aa113b64b356e994595fa548f0433f89948d747503ad22195cd","docs/index.html":"f61e6271c1ea1aa113b64b356e994595fa548f0433f89948d747503ad22195cd","src/encoding.rs":"f3e109ca8ec5a9130da50cdfb3003530aedb6dd5a440f0790d76b71f6981119c","src/form_urlencoded.rs":"d8c35e92375cafcd7e12c4f0d5374bab62aa1f333629d55b007a9c3d5c3cb615","src/host.rs":"66a2c0c77a8add2da16bc690fbc82b130cf1367ac655fc36990a214e193a4d6c","src/lib.rs":"e09dcba401018169ee26764e1c2bccf0855a5d935707c2100fd8d8e77a1bbc91","src/origin.rs":"6e4821eb9600a32ef54d05c8e1a7937f6d9b4dd1e3bda7f36c7988f6a2bef78b","src/parser.rs":"76368cbe93308123c014a3502024cf97d97ca61dcfc7b6ecd710073867d6deca","src/path_segments.rs":"7bd3142eaa568863ef44e2255c181239141f9eeee337f889b9ffaaeab4ca669d","src/quirks.rs":"6cf1697bad363532cbcc60917a9b126560ac3ab3e1a77da0abcf4f2a40c8233a","src/slicing.rs":"4e539886b23945a92094625f3e531a4bff40daa44240b5d19ee8577478c4f7fe","tests/data.rs":"f2c1c6d1823e8d21aeeae31c786d7f4ef0d97352a896f8c5aeb03a41fedb9a48","tests/setters_tests.json":"08ddaa632ad19c81e83b904bfaa94bc971f26e2bdfcef27d2f93fd033ad57340","tests/unit.rs":"ead7185710ce06c8d68ea18700618477867ee355656eabcad26cfcfaaad361a0","tests/urltestdata.json":"1b0c7c727d8d7e79dfb0d0aa347ff05675ddb68bc4ead38f83fd8e89bc59cc32"},"package":"dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"}
\ No newline at end of file
diff -Nrup a/third_party/rust/url/.travis.yml b/third_party/rust/url/.travis.yml
--- a/third_party/rust/url/.travis.yml	2020-02-18 02:39:21.000000000 +0300
+++ b/third_party/rust/url/.travis.yml	1970-01-01 03:00:00.000000000 +0300
@@ -1,9 +0,0 @@
-language: rust
-rust:
-  - nightly
-  - beta
-  - stable
-  - 1.17.0
-script: make test
-notifications:
-  webhooks: http://build.servo.org:54856/travis
diff -Nrup a/third_party/rust/url/Cargo.toml b/third_party/rust/url/Cargo.toml
--- a/third_party/rust/url/Cargo.toml	2020-02-18 02:39:21.000000000 +0300
+++ b/third_party/rust/url/Cargo.toml	2020-02-28 20:28:57.261406050 +0300
@@ -1,24 +1,31 @@
-[package]
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g. crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
 
+[package]
 name = "url"
-# When updating version, also modify html_root_url in the lib.rs
-version = "1.5.1"
+version = "1.7.2"
 authors = ["The rust-url developers"]
-
 description = "URL library for Rust, based on the WHATWG URL Standard"
 documentation = "https://docs.rs/url"
-repository = "https://github.com/servo/rust-url"
 readme = "README.md"
 keywords = ["url", "parser"]
 categories = ["parser-implementations", "web-programming", "encoding"]
 license = "MIT/Apache-2.0"
+repository = "https://github.com/servo/rust-url"
+[package.metadata.docs.rs]
+features = ["query_encoding"]
 
-[badges]
-travis-ci = { repository = "servo/rust-url" }
-appveyor = { repository = "servo/rust-url" }
-
-[workspace]
-members = [".", "idna", "percent_encoding", "url_serde"]
+[lib]
+test = false
 
 [[test]]
 name = "unit"
@@ -27,23 +34,50 @@ name = "unit"
 name = "data"
 harness = false
 
-[lib]
-test = false
+[[bench]]
+name = "parse_url"
+harness = false
+[dependencies.encoding]
+version = "0.2"
+optional = true
 
-[dev-dependencies]
-rustc-test = "0.1"
-rustc-serialize = "0.3"
-serde_json = ">=0.6.1, <0.9"
+[dependencies.heapsize]
+version = ">=0.4.1, <0.5"
+optional = true
+
+[dependencies.idna]
+version = "0.1.0"
+
+[dependencies.matches]
+version = "0.1"
+
+[dependencies.percent-encoding]
+version = "1.0.0"
+
+[dependencies.rustc-serialize]
+version = "0.3"
+optional = true
+
+[dependencies.serde]
+version = ">=0.6.1, <0.9"
+optional = true
+[dev-dependencies.bencher]
+version = "0.1"
+
+[dev-dependencies.rustc-serialize]
+version = "0.3"
+
+[dev-dependencies.rustc-test]
+version = "0.3"
+
+[dev-dependencies.serde_json]
+version = ">=0.6.1, <0.9"
 
 [features]
-query_encoding = ["encoding"]
 heap_size = ["heapsize"]
+query_encoding = ["encoding"]
+[badges.appveyor]
+repository = "Manishearth/rust-url"
 
-[dependencies]
-encoding = {version = "0.2", optional = true}
-heapsize = {version = ">=0.1.1, <0.5", optional = true}
-idna = { version = "0.1.0", path = "./idna" }
-matches = "0.1"
-percent-encoding = { version = "1.0.0", path = "./percent_encoding" }
-rustc-serialize = {version = "0.3", optional = true}
-serde = {version = ">=0.6.1, <0.9", optional = true}
+[badges.travis-ci]
+repository = "servo/rust-url"
diff -Nrup a/third_party/rust/url/Makefile b/third_party/rust/url/Makefile
--- a/third_party/rust/url/Makefile	2020-02-18 02:39:21.000000000 +0300
+++ b/third_party/rust/url/Makefile	1970-01-01 03:00:00.000000000 +0300
@@ -1,6 +0,0 @@
-test:
-	cargo test --features "query_encoding serde rustc-serialize heapsize"
-	(cd idna && cargo test)
-	(cd url_serde && cargo test)
-
-.PHONY: test
diff -Nrup a/third_party/rust/url/benches/parse_url.rs b/third_party/rust/url/benches/parse_url.rs
--- a/third_party/rust/url/benches/parse_url.rs	1970-01-01 03:00:00.000000000 +0300
+++ b/third_party/rust/url/benches/parse_url.rs	2020-02-28 20:28:57.261406050 +0300
@@ -0,0 +1,18 @@
+#[macro_use]
+extern crate bencher;
+
+extern crate url;
+
+use bencher::{black_box, Bencher};
+
+use url::Url;
+
+fn short(bench: &mut Bencher) {
+    let url = "https://example.com/bench";
+
+    bench.bytes = url.len() as u64;
+    bench.iter(|| black_box(url).parse::<Url>().unwrap());
+}
+
+benchmark_group!(benches, short);
+benchmark_main!(benches);
diff -Nrup a/third_party/rust/url/rust-url-todo b/third_party/rust/url/rust-url-todo
--- a/third_party/rust/url/rust-url-todo	2020-02-18 02:39:21.000000000 +0300
+++ b/third_party/rust/url/rust-url-todo	1970-01-01 03:00:00.000000000 +0300
@@ -1,14 +0,0 @@
-* standalone path parsing?
-* Test setters
-  * Test trim C0/space
-  * Test remove tab & newline
-
-
-
-#[test]
-fn test_path_segments() {
-    let mut url = Url::parse("http://example.net").unwrap();
-    url.push_path_segment("foo").unwrap();
-    url.extend_path_segments(&["bar", "b/az"]).unwrap();
-    assert_eq!(url.as_str(), "http://example.net/foo");
-}
diff -Nrup a/third_party/rust/url/src/form_urlencoded.rs b/third_party/rust/url/src/form_urlencoded.rs
--- a/third_party/rust/url/src/form_urlencoded.rs	2020-02-18 02:39:21.000000000 +0300
+++ b/third_party/rust/url/src/form_urlencoded.rs	2020-02-28 20:28:57.262406043 +0300
@@ -16,6 +16,7 @@
 use encoding::EncodingOverride;
 use percent_encoding::{percent_encode_byte, percent_decode};
 use std::borrow::{Borrow, Cow};
+use std::fmt;
 use std::str;
 
 
@@ -216,6 +217,15 @@ pub struct Serializer<T: Target> {
     target: Option<T>,
     start_position: usize,
     encoding: EncodingOverride,
+    custom_encoding: Option<SilentDebug<Box<FnMut(&str) -> Cow<[u8]>>>>,
+}
+
+struct SilentDebug<T>(T);
+
+impl<T> fmt::Debug for SilentDebug<T> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.write_str("…")
+    }
 }
 
 pub trait Target {
@@ -247,8 +257,16 @@ impl<'a> Target for &'a mut String {
 // * `Serializer` keeps its target in a private field
 // * Unlike in other `Target` impls, `UrlQuery::finished` does not return `Self`.
 impl<'a> Target for ::UrlQuery<'a> {
-    fn as_mut_string(&mut self) -> &mut String { &mut self.url.serialization }
-    fn finish(self) -> &'a mut ::Url { self.url }
+    fn as_mut_string(&mut self) -> &mut String {
+        &mut self.url.as_mut().unwrap().serialization
+    }
+
+    fn finish(mut self) -> &'a mut ::Url {
+        let url = self.url.take().unwrap();
+        url.restore_already_parsed_fragment(self.fragment.take());
+        url
+    }
+
     type Finished = &'a mut ::Url;
 }
 
@@ -272,6 +290,7 @@ impl<T: Target> Serializer<T> {
             target: Some(target),
             start_position: start_position,
             encoding: EncodingOverride::utf8(),
+            custom_encoding: None,
         }
     }
 
@@ -290,11 +309,20 @@ impl<T: Target> Serializer<T> {
         self
     }
 
+    /// Set the character encoding to be used for names and values before percent-encoding.
+    pub fn custom_encoding_override<F>(&mut self, encode: F) -> &mut Self
+        where F: FnMut(&str) -> Cow<[u8]> + 'static
+    {
+        self.custom_encoding = Some(SilentDebug(Box::new(encode)));
+        self
+    }
+
     /// Serialize and append a name/value pair.
     ///
     /// Panics if called after `.finish()`.
     pub fn append_pair(&mut self, name: &str, value: &str) -> &mut Self {
-        append_pair(string(&mut self.target), self.start_position, self.encoding, name, value);
+        append_pair(string(&mut self.target), self.start_position, self.encoding,
+                    &mut self.custom_encoding, name, value);
         self
     }
 
@@ -311,7 +339,8 @@ impl<T: Target> Serializer<T> {
             let string = string(&mut self.target);
             for pair in iter {
                 let &(ref k, ref v) = pair.borrow();
-                append_pair(string, self.start_position, self.encoding, k.as_ref(), v.as_ref());
+                append_pair(string, self.start_position, self.encoding,
+                            &mut self.custom_encoding, k.as_ref(), v.as_ref());
             }
         }
         self
@@ -324,6 +353,8 @@ impl<T: Target> Serializer<T> {
     /// Panics if called after `.finish()`.
     #[cfg(feature = "query_encoding")]
     pub fn append_charset(&mut self) -> &mut Self {
+        assert!(self.custom_encoding.is_none(),
+                "Cannot use both custom_encoding_override() and append_charset()");
         {
             let string = string(&mut self.target);
             append_separator_if_needed(string, self.start_position);
@@ -361,9 +392,20 @@ fn string<T: Target>(target: &mut Option
 }
 
 fn append_pair(string: &mut String, start_position: usize, encoding: EncodingOverride,
+               custom_encoding: &mut Option<SilentDebug<Box<FnMut(&str) -> Cow<[u8]>>>>,
                name: &str, value: &str) {
     append_separator_if_needed(string, start_position);
-    string.extend(byte_serialize(&encoding.encode(name.into())));
+    append_encoded(name, string, encoding, custom_encoding);
     string.push('=');
-    string.extend(byte_serialize(&encoding.encode(value.into())));
+    append_encoded(value, string, encoding, custom_encoding);
+}
+
+fn append_encoded(s: &str, string: &mut String, encoding: EncodingOverride,
+               custom_encoding: &mut Option<SilentDebug<Box<FnMut(&str) -> Cow<[u8]>>>>) {
+    let bytes = if let Some(SilentDebug(ref mut custom)) = *custom_encoding {
+        custom(s)
+    } else {
+        encoding.encode(s.into())
+    };
+    string.extend(byte_serialize(&bytes));
 }
diff -Nrup a/third_party/rust/url/src/host.rs b/third_party/rust/url/src/host.rs
--- a/third_party/rust/url/src/host.rs	2020-02-18 02:39:21.000000000 +0300
+++ b/third_party/rust/url/src/host.rs	2020-02-28 20:28:57.247406151 +0300
@@ -13,7 +13,7 @@ use std::io;
 use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
 use std::vec;
 use parser::{ParseResult, ParseError};
-use percent_encoding::percent_decode;
+use percent_encoding::{percent_decode, utf8_percent_encode, SIMPLE_ENCODE_SET};
 use idna;
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -73,7 +73,9 @@ impl<S> From<Host<S>> for HostInternal {
 #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
 pub enum Host<S=String> {
     /// A DNS domain name, as '.' dot-separated labels.
-    /// Non-ASCII labels are encoded in punycode per IDNA.
+    /// Non-ASCII labels are encoded in punycode per IDNA if this is the host of
+    /// a special URL, or percent encoded for non-special URLs. Hosts for
+    /// non-special URLs are also called opaque hosts.
     Domain(S),
 
     /// An IPv4 address.
@@ -137,7 +139,7 @@ impl<'a> Host<&'a str> {
 impl Host<String> {
     /// Parse a host: either an IPv6 address in [] square brackets, or a domain.
     ///
-    /// https://url.spec.whatwg.org/#host-parsing
+    /// <https://url.spec.whatwg.org/#host-parsing>
     pub fn parse(input: &str) -> Result<Self, ParseError> {
         if input.starts_with('[') {
             if !input.ends_with(']') {
@@ -158,6 +160,23 @@ impl Host<String> {
             Ok(Host::Domain(domain.into()))
         }
     }
+
+    // <https://url.spec.whatwg.org/#concept-opaque-host-parser>
+    pub fn parse_opaque(input: &str) -> Result<Self, ParseError> {
+        if input.starts_with('[') {
+            if !input.ends_with(']') {
+                return Err(ParseError::InvalidIpv6Address)
+            }
+            return parse_ipv6addr(&input[1..input.len() - 1]).map(Host::Ipv6)
+        }
+        if input.find(|c| matches!(c,
+            '\0' | '\t' | '\n' | '\r' | ' ' | '#' | '/' | ':' | '?' | '@' | '[' | '\\' | ']'
+        )).is_some() {
+            return Err(ParseError::InvalidDomainCharacter)
+        }
+        let s = utf8_percent_encode(input, SIMPLE_ENCODE_SET).to_string();
+        Ok(Host::Domain(s))
+    }
 }
 
 impl<S: AsRef<str>> fmt::Display for Host<S> {
@@ -309,8 +328,8 @@ fn longest_zero_sequence(pieces: &[u16;
     }
 }
 
-/// https://url.spec.whatwg.org/#ipv4-number-parser
-fn parse_ipv4number(mut input: &str) -> Result<u32, ()> {
+/// <https://url.spec.whatwg.org/#ipv4-number-parser>
+fn parse_ipv4number(mut input: &str) -> Result<Option<u32>, ()> {
     let mut r = 10;
     if input.starts_with("0x") || input.starts_with("0X") {
         input = &input[2..];
@@ -319,19 +338,35 @@ fn parse_ipv4number(mut input: &str) ->
         input = &input[1..];
         r = 8;
     }
+
+    // At the moment we can't know the reason why from_str_radix fails
+    // https://github.com/rust-lang/rust/issues/22639
+    // So instead we check if the input looks like a real number and only return
+    // an error when it's an overflow.
+    let valid_number = match r {
+        8 => input.chars().all(|c| c >= '0' && c <='7'),
+        10 => input.chars().all(|c| c >= '0' && c <='9'),
+        16 => input.chars().all(|c| (c >= '0' && c <='9') || (c >='a' && c <= 'f') || (c >= 'A' && c <= 'F')),
+        _ => false
+    };
+
+    if !valid_number {
+        return Ok(None);
+    }
+
     if input.is_empty() {
-        return Ok(0);
+        return Ok(Some(0));
     }
     if input.starts_with('+') {
-        return Err(())
+        return Ok(None);
     }
     match u32::from_str_radix(input, r) {
-        Ok(number) => Ok(number),
+        Ok(number) => Ok(Some(number)),
         Err(_) => Err(()),
     }
 }
 
-/// https://url.spec.whatwg.org/#concept-ipv4-parser
+/// <https://url.spec.whatwg.org/#concept-ipv4-parser>
 fn parse_ipv4addr(input: &str) -> ParseResult<Option<Ipv4Addr>> {
     if input.is_empty() {
         return Ok(None)
@@ -344,15 +379,19 @@ fn parse_ipv4addr(input: &str) -> ParseR
         return Ok(None);
     }
     let mut numbers: Vec<u32> = Vec::new();
+    let mut overflow = false;
     for part in parts {
         if part == "" {
             return Ok(None);
         }
-        if let Ok(n) = parse_ipv4number(part) {
-            numbers.push(n);
-        } else {
-            return Ok(None);
-        }
+        match parse_ipv4number(part) {
+            Ok(Some(n)) => numbers.push(n),
+            Ok(None) => return Ok(None),
+            Err(()) => overflow = true
+        };
+    }
+    if overflow {
+        return Err(ParseError::InvalidIpv4Address);
     }
     let mut ipv4 = numbers.pop().expect("a non-empty list of numbers");
     // Equivalent to: ipv4 >= 256 ** (4 − numbers.len())
@@ -368,7 +407,7 @@ fn parse_ipv4addr(input: &str) -> ParseR
     Ok(Some(Ipv4Addr::from(ipv4)))
 }
 
-/// https://url.spec.whatwg.org/#concept-ipv6-parser
+/// <https://url.spec.whatwg.org/#concept-ipv6-parser>
 fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
     let input = input.as_bytes();
     let len = input.len();
@@ -423,6 +462,9 @@ fn parse_ipv6addr(input: &str) -> ParseR
                         return Err(ParseError::InvalidIpv6Address)
                     }
                     i = start;
+                    if piece_pointer > 6 {
+                        return Err(ParseError::InvalidIpv6Address)
+                    }
                     is_ip_v4 = true;
                 },
                 b':' => {
@@ -445,16 +487,24 @@ fn parse_ipv6addr(input: &str) -> ParseR
         if piece_pointer > 6 {
             return Err(ParseError::InvalidIpv6Address)
         }
-        let mut dots_seen = 0;
+        let mut numbers_seen = 0;
         while i < len {
-            let mut value = None;
+            if numbers_seen > 0 {
+                if numbers_seen < 4 && (i < len && input[i] == b'.') {
+                    i += 1
+                } else {
+                    return Err(ParseError::InvalidIpv6Address)
+                }
+            }
+
+            let mut ipv4_piece = None;
             while i < len {
                 let digit = match input[i] {
                     c @ b'0' ... b'9' => c - b'0',
                     _ => break
                 };
-                match value {
-                    None => value = Some(digit as u16),
+                match ipv4_piece {
+                    None => ipv4_piece = Some(digit as u16),
                     Some(0) => return Err(ParseError::InvalidIpv6Address),  // No leading zero
                     Some(ref mut v) => {
                         *v = *v * 10 + digit as u16;
@@ -465,24 +515,28 @@ fn parse_ipv6addr(input: &str) -> ParseR
                 }
                 i += 1;
             }
-            if dots_seen < 3 && !(i < len && input[i] == b'.') {
-                return Err(ParseError::InvalidIpv6Address)
-            }
-            pieces[piece_pointer] = if let Some(v) = value {
+
+            pieces[piece_pointer] = if let Some(v) = ipv4_piece {
                 pieces[piece_pointer] * 0x100 + v
             } else {
                 return Err(ParseError::InvalidIpv6Address)
             };
-            if dots_seen == 1 || dots_seen == 3 {
+            numbers_seen += 1;
+
+            if numbers_seen == 2 || numbers_seen == 4 {
                 piece_pointer += 1;
             }
-            i += 1;
-            if dots_seen == 3 && i < len {
-                return Err(ParseError::InvalidIpv6Address)
-            }
-            dots_seen += 1;
+        }
+
+        if numbers_seen != 4 {
+            return Err(ParseError::InvalidIpv6Address)
         }
     }
+
+    if i < len {
+        return Err(ParseError::InvalidIpv6Address)
+    }
+
     match compress_pointer {
         Some(compress_pointer) => {
             let mut swaps = piece_pointer - compress_pointer;
diff -Nrup a/third_party/rust/url/src/lib.rs b/third_party/rust/url/src/lib.rs
--- a/third_party/rust/url/src/lib.rs	2020-02-18 02:39:21.000000000 +0300
+++ b/third_party/rust/url/src/lib.rs	2020-02-28 20:28:57.262406043 +0300
@@ -104,7 +104,7 @@ assert_eq!(css_url.as_str(), "http://ser
 # run().unwrap();
 */
 
-#![doc(html_root_url = "https://docs.rs/url/1.5.1")]
+#![doc(html_root_url = "https://docs.rs/url/1.7.0")]
 
 #[cfg(feature="rustc-serialize")] extern crate rustc_serialize;
 #[macro_use] extern crate matches;
@@ -112,12 +112,13 @@ assert_eq!(css_url.as_str(), "http://ser
 #[cfg(feature="heapsize")] #[macro_use] extern crate heapsize;
 
 pub extern crate idna;
+#[macro_use]
 pub extern crate percent_encoding;
 
 use encoding::EncodingOverride;
 #[cfg(feature = "heapsize")] use heapsize::HeapSizeOf;
 use host::HostInternal;
-use parser::{Parser, Context, SchemeType, to_u32};
+use parser::{Parser, Context, SchemeType, to_u32, ViolationFn};
 use percent_encoding::{PATH_SEGMENT_ENCODE_SET, USERINFO_ENCODE_SET,
                        percent_encode, percent_decode, utf8_percent_encode};
 use std::borrow::Borrow;
@@ -135,7 +136,7 @@ use std::str;
 pub use origin::{Origin, OpaqueOrigin};
 pub use host::{Host, HostAndPort, SocketAddrs};
 pub use path_segments::PathSegmentsMut;
-pub use parser::ParseError;
+pub use parser::{ParseError, SyntaxViolation};
 pub use slicing::Position;
 
 mod encoding;
@@ -186,7 +187,7 @@ impl HeapSizeOf for Url {
 pub struct ParseOptions<'a> {
     base_url: Option<&'a Url>,
     encoding_override: encoding::EncodingOverride,
-    log_syntax_violation: Option<&'a Fn(&'static str)>,
+    violation_fn: ViolationFn<'a>,
 }
 
 impl<'a> ParseOptions<'a> {
@@ -209,9 +210,47 @@ impl<'a> ParseOptions<'a> {
         self
     }
 
-    /// Call the provided function or closure on non-fatal parse errors.
+    /// Call the provided function or closure on non-fatal parse errors, passing
+    /// a static string description.  This method is deprecated in favor of
+    /// `syntax_violation_callback` and is implemented as an adaptor for the
+    /// latter, passing the `SyntaxViolation` description. Only the last value
+    /// passed to either method will be used by a parser.
+    #[deprecated]
     pub fn log_syntax_violation(mut self, new: Option<&'a Fn(&'static str)>) -> Self {
-        self.log_syntax_violation = new;
+        self.violation_fn = match new {
+            Some(f) => ViolationFn::OldFn(f),
+            None => ViolationFn::NoOp
+        };
+        self
+    }
+
+    /// Call the provided function or closure for a non-fatal `SyntaxViolation`
+    /// when it occurs during parsing. Note that since the provided function is
+    /// `Fn`, the caller might need to utilize _interior mutability_, such as with
+    /// a `RefCell`, to collect the violations.
+    ///
+    /// ## Example
+    /// ```
+    /// use std::cell::RefCell;
+    /// use url::{Url, SyntaxViolation};
+    /// # use url::ParseError;
+    /// # fn run() -> Result<(), url::ParseError> {
+    /// let violations = RefCell::new(Vec::new());
+    /// let url = Url::options()
+    ///     .syntax_violation_callback(Some(&|v| violations.borrow_mut().push(v)))
+    ///     .parse("https:////example.com")?;
+    /// assert_eq!(url.as_str(), "https://example.com/");
+    /// assert_eq!(violations.into_inner(),
+    ///            vec!(SyntaxViolation::ExpectedDoubleSlash));
+    /// # Ok(())
+    /// # }
+    /// # run().unwrap();
+    /// ```
+    pub fn syntax_violation_callback(mut self, new: Option<&'a Fn(SyntaxViolation)>) -> Self {
+        self.violation_fn = match new {
+            Some(f) => ViolationFn::NewFn(f),
+            None => ViolationFn::NoOp
+        };
         self
     }
 
@@ -221,7 +260,7 @@ impl<'a> ParseOptions<'a> {
             serialization: String::with_capacity(input.len()),
             base_url: self.base_url,
             query_encoding_override: self.encoding_override,
-            log_syntax_violation: self.log_syntax_violation,
+            violation_fn: self.violation_fn,
             context: Context::UrlParser,
         }.parse_url(input)
     }
@@ -229,11 +268,12 @@ impl<'a> ParseOptions<'a> {
 
 impl<'a> Debug for ParseOptions<'a> {
     fn fmt(&self, f: &mut Formatter) -> fmt::Result {
-        write!(f, "ParseOptions {{ base_url: {:?}, encoding_override: {:?}, log_syntax_violation: ", self.base_url, self.encoding_override)?;
-        match self.log_syntax_violation {
-            Some(_) => write!(f, "Some(Fn(&'static str)) }}"),
-            None => write!(f, "None }}")
-        }
+        write!(f,
+               "ParseOptions {{ base_url: {:?}, encoding_override: {:?}, \
+                violation_fn: {:?} }}",
+               self.base_url,
+               self.encoding_override,
+               self.violation_fn)
     }
 }
 
@@ -252,6 +292,13 @@ impl Url {
     /// # }
     /// # run().unwrap();
     /// ```
+    ///
+    /// # Errors
+    ///
+    /// If the function can not parse an absolute URL from the given string,
+    /// a [`ParseError`] variant will be returned.
+    ///
+    /// [`ParseError`]: enum.ParseError.html
     #[inline]
     pub fn parse(input: &str) -> Result<Url, ::ParseError> {
         Url::options().parse(input)
@@ -274,6 +321,13 @@ impl Url {
     /// # }
     /// # run().unwrap();
     /// ```
+    ///
+    /// # Errors
+    ///
+    /// If the function can not parse an absolute URL from the given string,
+    /// a [`ParseError`] variant will be returned.
+    ///
+    /// [`ParseError`]: enum.ParseError.html
     #[inline]
     pub fn parse_with_params<I, K, V>(input: &str, iter: I) -> Result<Url, ::ParseError>
         where I: IntoIterator,
@@ -301,7 +355,7 @@ impl Url {
     /// ```rust
     /// use url::Url;
     /// # use url::ParseError;
-    /// 
+    ///
     /// # fn run() -> Result<(), ParseError> {
     /// let base = Url::parse("https://example.net/a/b.html")?;
     /// let url = base.join("c.png")?;
@@ -314,6 +368,13 @@ impl Url {
     /// # }
     /// # run().unwrap();
     /// ```
+    ///
+    /// # Errors
+    ///
+    /// If the function can not parse an URL from the given string
+    /// with this URL as the base URL, a [`ParseError`] variant will be returned.
+    ///
+    /// [`ParseError`]: enum.ParseError.html
     #[inline]
     pub fn join(&self, input: &str) -> Result<Url, ::ParseError> {
         Url::options().base_url(Some(self)).parse(input)
@@ -342,7 +403,7 @@ impl Url {
         ParseOptions {
             base_url: None,
             encoding_override: EncodingOverride::utf8(),
-            log_syntax_violation: None,
+            violation_fn: ViolationFn::NoOp,
         }
     }
 
@@ -500,7 +561,7 @@ impl Url {
         Ok(())
     }
 
-    /// Return the origin of this URL (https://url.spec.whatwg.org/#origin)
+    /// Return the origin of this URL (<https://url.spec.whatwg.org/#origin>)
     ///
     /// Note: this returns an opaque origin for `file:` URLs, which causes
     /// `url.origin() != url.origin()`.
@@ -1156,11 +1217,11 @@ impl Url {
     /// assert_eq!(url.as_str(), "https://example.com/data.csv");
 
     /// url.set_fragment(Some("cell=4,1-6,2"));
-    /// assert_eq!(url.as_str(), "https://example.com/data.csv#cell=4,1-6,2");  
+    /// assert_eq!(url.as_str(), "https://example.com/data.csv#cell=4,1-6,2");
     /// assert_eq!(url.fragment(), Some("cell=4,1-6,2"));
     ///
     /// url.set_fragment(None);
-    /// assert_eq!(url.as_str(), "https://example.com/data.csv");    
+    /// assert_eq!(url.as_str(), "https://example.com/data.csv");
     /// assert!(url.fragment().is_none());
     /// # Ok(())
     /// # }
@@ -1213,7 +1274,7 @@ impl Url {
     /// assert_eq!(url.as_str(), "https://example.com/products");
     ///
     /// url.set_query(Some("page=2"));
-    /// assert_eq!(url.as_str(), "https://example.com/products?page=2");    
+    /// assert_eq!(url.as_str(), "https://example.com/products?page=2");
     /// assert_eq!(url.query(), Some("page=2"));
     /// # Ok(())
     /// # }
@@ -1283,7 +1344,7 @@ impl Url {
             self.serialization.push('?');
         }
 
-        let query = UrlQuery { url: self, fragment: fragment };
+        let query = UrlQuery { url: Some(self), fragment: fragment };
         form_urlencoded::Serializer::for_suffix(query, query_start + "?".len())
     }
 
@@ -1309,12 +1370,12 @@ impl Url {
     /// # fn run() -> Result<(), ParseError> {
     /// let mut url = Url::parse("https://example.com")?;
     /// url.set_path("api/comments");
-    /// assert_eq!(url.as_str(), "https://example.com/api/comments");    
+    /// assert_eq!(url.as_str(), "https://example.com/api/comments");
     /// assert_eq!(url.path(), "/api/comments");
     ///
     /// let mut url = Url::parse("https://example.com/api")?;
     /// url.set_path("data/report.csv");
-    /// assert_eq!(url.as_str(), "https://example.com/data/report.csv");    
+    /// assert_eq!(url.as_str(), "https://example.com/data/report.csv");
     /// assert_eq!(url.path(), "/data/report.csv");
     /// # Ok(())
     /// # }
@@ -1406,7 +1467,8 @@ impl Url {
     /// # run().unwrap();
     /// ```
     pub fn set_port(&mut self, mut port: Option<u16>) -> Result<(), ()> {
-        if !self.has_host() || self.scheme() == "file" {
+        // has_host implies !cannot_be_a_base
+        if !self.has_host() || self.host() == Some(Host::Domain("")) || self.scheme() == "file" {
             return Err(())
         }
         if port.is_some() && port == parser::default_port(self.scheme()) {
@@ -1448,9 +1510,6 @@ impl Url {
 
     /// Change this URL’s host.
     ///
-    /// If this URL is cannot-be-a-base or there is an error parsing the given `host`,
-    /// do nothing and return `Err`.
-    ///
     /// Removing the host (calling this with `None`)
     /// will also remove any username, password, and port number.
     ///
@@ -1477,7 +1536,7 @@ impl Url {
     /// ```
     /// use url::Url;
     /// # use url::ParseError;
-    /// 
+    ///
     /// # fn run() -> Result<(), ParseError> {
     /// let mut url = Url::parse("foo://example.net")?;
     /// let result = url.set_host(None);
@@ -1493,7 +1552,7 @@ impl Url {
     /// ```
     /// use url::Url;
     /// # use url::ParseError;
-    /// 
+    ///
     /// # fn run() -> Result<(), ParseError> {
     /// let mut url = Url::parse("https://example.net")?;
     /// let result = url.set_host(None);
@@ -1509,7 +1568,7 @@ impl Url {
     /// ```
     /// use url::Url;
     /// # use url::ParseError;
-    /// 
+    ///
     /// # fn run() -> Result<(), ParseError> {
     /// let mut url = Url::parse("mailto:rms@example.net")?;
     ///
@@ -1524,6 +1583,13 @@ impl Url {
     /// # }
     /// # run().unwrap();
     /// ```
+    ///
+    /// # Errors
+    ///
+    /// If this URL is cannot-be-a-base or there is an error parsing the given `host`,
+    /// a [`ParseError`] variant will be returned.
+    ///
+    /// [`ParseError`]: enum.ParseError.html
     pub fn set_host(&mut self, host: Option<&str>) -> Result<(), ParseError> {
         if self.cannot_be_a_base() {
             return Err(ParseError::SetHostOnCannotBeABaseUrl)
@@ -1533,7 +1599,11 @@ impl Url {
             if host == "" && SchemeType::from(self.scheme()).is_special() {
                 return Err(ParseError::EmptyHost);
             }
-            self.set_host_internal(Host::parse(host)?, None)
+            if SchemeType::from(self.scheme()).is_special() {
+                self.set_host_internal(Host::parse(host)?, None)
+            } else {
+                self.set_host_internal(Host::parse_opaque(host)?, None)
+            }
         } else if self.has_host() {
             if SchemeType::from(self.scheme()).is_special() {
                 return Err(ParseError::EmptyHost)
@@ -1666,7 +1736,8 @@ impl Url {
     /// # run().unwrap();
     /// ```
     pub fn set_password(&mut self, password: Option<&str>) -> Result<(), ()> {
-        if !self.has_host() {
+        // has_host implies !cannot_be_a_base
+        if !self.has_host() || self.host() == Some(Host::Domain("")) || self.scheme() == "file" {
             return Err(())
         }
         if let Some(password) = password {
@@ -1732,21 +1803,23 @@ impl Url {
     /// ```
     ///
     /// Setup username to user1
+    ///
     /// ```rust
     /// use url::{Url, ParseError};
     ///
     /// # fn run() -> Result<(), ParseError> {
-    /// let mut url = Url::parse("ftp://:secre1@example.com")?;
+    /// let mut url = Url::parse("ftp://:secre1@example.com/")?;
     /// let result = url.set_username("user1");
     /// assert!(result.is_ok());
     /// assert_eq!(url.username(), "user1");
-    /// assert_eq!(url.as_str(), "ftp://user1:secre1@example.com");
+    /// assert_eq!(url.as_str(), "ftp://user1:secre1@example.com/");
     /// # Ok(())
     /// # }
     /// # run().unwrap();
     /// ```
     pub fn set_username(&mut self, username: &str) -> Result<(), ()> {
-        if !self.has_host() {
+        // has_host implies !cannot_be_a_base
+        if !self.has_host() || self.host() == Some(Host::Domain("")) || self.scheme() == "file" {
             return Err(())
         }
         let username_start = self.scheme_end + 3;
@@ -1805,7 +1878,7 @@ impl Url {
     /// ```
     /// use url::Url;
     /// # use url::ParseError;
-    /// 
+    ///
     /// # fn run() -> Result<(), ParseError> {
     /// let mut url = Url::parse("https://example.net")?;
     /// let result = url.set_scheme("foo");
@@ -1822,7 +1895,7 @@ impl Url {
     /// ```
     /// use url::Url;
     /// # use url::ParseError;
-    /// 
+    ///
     /// # fn run() -> Result<(), ParseError> {
     /// let mut url = Url::parse("https://example.net")?;
     /// let result = url.set_scheme("foõ");
@@ -1838,7 +1911,7 @@ impl Url {
     /// ```
     /// use url::Url;
     /// # use url::ParseError;
-    /// 
+    ///
     /// # fn run() -> Result<(), ParseError> {
     /// let mut url = Url::parse("mailto:rms@example.net")?;
     /// let result = url.set_scheme("https");
@@ -1887,7 +1960,7 @@ impl Url {
     /// ```
     /// # if cfg!(unix) {
     /// use url::Url;
-    /// 
+    ///
     /// # fn run() -> Result<(), ()> {
     /// let url = Url::from_file_path("/tmp/foo.txt")?;
     /// assert_eq!(url.as_str(), "file:///tmp/foo.txt");
@@ -1902,6 +1975,7 @@ impl Url {
     /// # run().unwrap();
     /// # }
     /// ```
+    #[cfg(any(unix, windows, target_os="redox"))]
     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;
@@ -1937,6 +2011,7 @@ impl Url {
     ///
     /// 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"))]
     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('/') {
@@ -2018,6 +2093,7 @@ impl Url {
     /// (That is, if the percent-decoded path contains a NUL byte or,
     /// for a Windows path, is not UTF-8.)
     #[inline]
+    #[cfg(any(unix, windows, target_os="redox"))]
     pub fn to_file_path(&self) -> Result<PathBuf, ()> {
         if let Some(segments) = self.path_segments() {
             let host = match self.host() {
@@ -2264,6 +2340,7 @@ fn path_to_file_url_segments_windows(pat
     Ok((host_end, host_internal))
 }
 
+
 #[cfg(any(unix, target_os = "redox"))]
 fn file_url_segments_to_pathbuf(host: Option<&str>, segments: str::Split<char>) -> Result<PathBuf, ()> {
     use std::ffi::OsStr;
@@ -2347,13 +2424,15 @@ fn io_error<T>(reason: &str) -> io::Resu
 /// Implementation detail of `Url::query_pairs_mut`. Typically not used directly.
 #[derive(Debug)]
 pub struct UrlQuery<'a> {
-    url: &'a mut Url,
+    url: Option<&'a mut Url>,
     fragment: Option<String>,
 }
 
 impl<'a> Drop for UrlQuery<'a> {
     fn drop(&mut self) {
-        self.url.restore_already_parsed_fragment(self.fragment.take())
+        if let Some(url) = self.url.take() {
+            url.restore_already_parsed_fragment(self.fragment.take())
+        }
     }
 }
 
diff -Nrup a/third_party/rust/url/src/origin.rs b/third_party/rust/url/src/origin.rs
--- a/third_party/rust/url/src/origin.rs	2020-02-18 02:39:21.000000000 +0300
+++ b/third_party/rust/url/src/origin.rs	2020-02-28 20:28:57.238406216 +0300
@@ -49,7 +49,7 @@ pub fn url_origin(url: &Url) -> Origin {
 /// - If the scheme is anything else, the origin is opaque, meaning
 ///   the URL does not have the same origin as any other URL.
 ///
-/// For more information see https://url.spec.whatwg.org/#origin
+/// For more information see <https://url.spec.whatwg.org/#origin>
 #[derive(PartialEq, Eq, Hash, Clone, Debug)]
 pub enum Origin {
     /// A globally unique identifier
@@ -86,7 +86,7 @@ impl Origin {
         matches!(*self, Origin::Tuple(..))
     }
 
-    /// https://html.spec.whatwg.org/multipage/#ascii-serialisation-of-an-origin
+    /// <https://html.spec.whatwg.org/multipage/#ascii-serialisation-of-an-origin>
     pub fn ascii_serialization(&self) -> String {
         match *self {
             Origin::Opaque(_) => "null".to_owned(),
@@ -100,7 +100,7 @@ impl Origin {
         }
     }
 
-    /// https://html.spec.whatwg.org/multipage/#unicode-serialisation-of-an-origin
+    /// <https://html.spec.whatwg.org/multipage/#unicode-serialisation-of-an-origin>
     pub fn unicode_serialization(&self) -> String {
         match *self {
             Origin::Opaque(_) => "null".to_owned(),
diff -Nrup a/third_party/rust/url/src/parser.rs b/third_party/rust/url/src/parser.rs
--- a/third_party/rust/url/src/parser.rs	2020-02-18 02:39:21.000000000 +0300
+++ b/third_party/rust/url/src/parser.rs	2020-02-28 20:28:57.263406036 +0300
@@ -6,7 +6,9 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
+#[allow(unused_imports, deprecated)]
 use std::ascii::AsciiExt;
+
 use std::error::Error;
 use std::fmt::{self, Formatter, Write};
 use std::str;
@@ -20,6 +22,12 @@ use percent_encoding::{
     PATH_SEGMENT_ENCODE_SET
 };
 
+define_encode_set! {
+    // The backslash (\) character is treated as a path separator in special URLs
+    // so it needs to be additionally escaped in that case.
+    pub SPECIAL_PATH_SEGMENT_ENCODE_SET = [PATH_SEGMENT_ENCODE_SET] | {'\\'}
+}
+
 pub type ParseResult<T> = Result<T, ParseError>;
 
 macro_rules! simple_enum_error {
@@ -70,6 +78,54 @@ impl From<::idna::uts46::Errors> for Par
     fn from(_: ::idna::uts46::Errors) -> ParseError { ParseError::IdnaError }
 }
 
+macro_rules! syntax_violation_enum {
+    ($($name: ident => $description: expr,)+) => {
+        /// Non-fatal syntax violations that can occur during parsing.
+        #[derive(PartialEq, Eq, Clone, Copy, Debug)]
+        pub enum SyntaxViolation {
+            $(
+                $name,
+            )+
+        }
+
+        impl SyntaxViolation {
+            pub fn description(&self) -> &'static str {
+                match *self {
+                    $(
+                        SyntaxViolation::$name => $description,
+                    )+
+                }
+            }
+        }
+    }
+}
+
+syntax_violation_enum! {
+    Backslash => "backslash",
+    C0SpaceIgnored =>
+        "leading or trailing control or space character are ignored in URLs",
+    EmbeddedCredentials =>
+        "embedding authentication information (username or password) \
+         in an URL is not recommended",
+    ExpectedDoubleSlash => "expected //",
+    ExpectedFileDoubleSlash => "expected // after file:",
+    FileWithHostAndWindowsDrive => "file: with host and Windows drive letter",
+    NonUrlCodePoint => "non-URL code point",
+    NullInFragment => "NULL characters are ignored in URL fragment identifiers",
+    PercentDecode => "expected 2 hex digits after %",
+    TabOrNewlineIgnored => "tabs or newlines are ignored in URLs",
+    UnencodedAtSign => "unencoded @ sign in username or password",
+}
+
+#[cfg(feature = "heapsize")]
+known_heap_size!(0, SyntaxViolation);
+
+impl fmt::Display for SyntaxViolation {
+    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+        self.description().fmt(fmt)
+    }
+}
+
 #[derive(Copy, Clone)]
 pub enum SchemeType {
     File,
@@ -112,18 +168,17 @@ pub struct Input<'i> {
 
 impl<'i> Input<'i> {
     pub fn new(input: &'i str) -> Self {
-        Input::with_log(input, None)
+        Input::with_log(input, ViolationFn::NoOp)
     }
 
-    pub fn with_log(original_input: &'i str, log_syntax_violation: Option<&Fn(&'static str)>)
-                    -> Self {
+    pub fn with_log(original_input: &'i str, vfn: ViolationFn) -> Self {
         let input = original_input.trim_matches(c0_control_or_space);
-        if let Some(log) = log_syntax_violation {
+        if vfn.is_set() {
             if input.len() < original_input.len() {
-                log("leading or trailing control or space character are ignored in URLs")
+                vfn.call(SyntaxViolation::C0SpaceIgnored)
             }
             if input.chars().any(|c| matches!(c, '\t' | '\n' | '\r')) {
-                log("tabs or newlines are ignored in URLs")
+                vfn.call(SyntaxViolation::TabOrNewlineIgnored)
             }
         }
         Input { chars: input.chars() }
@@ -216,11 +271,60 @@ impl<'i> Iterator for Input<'i> {
     }
 }
 
+/// Wrapper for syntax violation callback functions.
+#[derive(Copy, Clone)]
+pub enum ViolationFn<'a> {
+    NewFn(&'a (Fn(SyntaxViolation) + 'a)),
+    OldFn(&'a (Fn(&'static str) + 'a)),
+    NoOp
+}
+
+impl<'a> ViolationFn<'a> {
+    /// Call with a violation.
+    pub fn call(self, v: SyntaxViolation) {
+        match self {
+            ViolationFn::NewFn(f) => f(v),
+            ViolationFn::OldFn(f) => f(v.description()),
+            ViolationFn::NoOp => {}
+        }
+    }
+
+    /// Call with a violation, if provided test returns true. Avoids
+    /// the test entirely if `NoOp`.
+    pub fn call_if<F>(self, v: SyntaxViolation, test: F)
+        where F: Fn() -> bool
+    {
+        match self {
+            ViolationFn::NewFn(f) => if test() { f(v) },
+            ViolationFn::OldFn(f) => if test() { f(v.description()) },
+            ViolationFn::NoOp => {} // avoid test
+        }
+    }
+
+    /// True if not `NoOp`
+    pub fn is_set(self) -> bool {
+        match self {
+            ViolationFn::NoOp => false,
+            _ => true
+        }
+    }
+}
+
+impl<'a> fmt::Debug for ViolationFn<'a> {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        match *self {
+            ViolationFn::NewFn(_) => write!(f, "NewFn(Fn(SyntaxViolation))"),
+            ViolationFn::OldFn(_) => write!(f, "OldFn(Fn(&'static str))"),
+            ViolationFn::NoOp     => write!(f, "NoOp")
+        }
+    }
+}
+
 pub struct Parser<'a> {
     pub serialization: String,
     pub base_url: Option<&'a Url>,
     pub query_encoding_override: EncodingOverride,
-    pub log_syntax_violation: Option<&'a Fn(&'static str)>,
+    pub violation_fn: ViolationFn<'a>,
     pub context: Context,
 }
 
@@ -237,29 +341,14 @@ impl<'a> Parser<'a> {
             serialization: serialization,
             base_url: None,
             query_encoding_override: EncodingOverride::utf8(),
-            log_syntax_violation: None,
+            violation_fn: ViolationFn::NoOp,
             context: Context::Setter,
         }
     }
 
-    fn syntax_violation(&self, reason: &'static str) {
-        if let Some(log) = self.log_syntax_violation {
-            log(reason)
-        }
-    }
-
-    fn syntax_violation_if<F: Fn() -> bool>(&self, reason: &'static str, test: F) {
-        // Skip test if not logging.
-        if let Some(log) = self.log_syntax_violation {
-            if test() {
-                log(reason)
-            }
-        }
-    }
-
     /// https://url.spec.whatwg.org/#concept-basic-url-parser
     pub fn parse_url(mut self, input: &str) -> ParseResult<Url> {
-        let input = Input::with_log(input, self.log_syntax_violation);
+        let input = Input::with_log(input, self.violation_fn);
         if let Ok(remaining) = self.parse_scheme(input.clone()) {
             return self.parse_with_scheme(remaining)
         }
@@ -310,12 +399,13 @@ impl<'a> Parser<'a> {
     }
 
     fn parse_with_scheme(mut self, input: Input) -> ParseResult<Url> {
+        use SyntaxViolation::{ExpectedFileDoubleSlash, ExpectedDoubleSlash};
         let scheme_end = to_u32(self.serialization.len())?;
         let scheme_type = SchemeType::from(&self.serialization);
         self.serialization.push(':');
         match scheme_type {
             SchemeType::File => {
-                self.syntax_violation_if("expected // after file:", || !input.starts_with("//"));
+                self.violation_fn.call_if(ExpectedFileDoubleSlash, || !input.starts_with("//"));
                 let base_file_url = self.base_url.and_then(|base| {
                     if base.scheme() == "file" { Some(base) } else { None }
                 });
@@ -335,7 +425,7 @@ impl<'a> Parser<'a> {
                     }
                 }
                 // special authority slashes state
-                self.syntax_violation_if("expected //", || {
+                self.violation_fn.call_if(ExpectedDoubleSlash, || {
                     input.clone().take_while(|&c| matches!(c, '/' | '\\'))
                     .collect::<String>() != "//"
                 });
@@ -371,6 +461,7 @@ impl<'a> Parser<'a> {
     }
 
     fn parse_file(mut self, input: Input, mut base_file_url: Option<&Url>) -> ParseResult<Url> {
+        use SyntaxViolation::Backslash;
         // file state
         debug_assert!(self.serialization.is_empty());
         let (first_char, input_after_first_char) = input.split_first();
@@ -451,6 +542,7 @@ impl<'a> Parser<'a> {
                     let scheme_end = "file".len() as u32;
                     let path_start = "file://".len() as u32;
                     let fragment_start = "file:///".len() as u32;
+                    self.serialization.push('#');
                     self.parse_fragment(input_after_first_char);
                     Ok(Url {
                         serialization: self.serialization,
@@ -467,18 +559,18 @@ impl<'a> Parser<'a> {
                 }
             }
             Some('/') | Some('\\') => {
-                self.syntax_violation_if("backslash", || first_char == Some('\\'));
+                self.violation_fn.call_if(Backslash, || first_char == Some('\\'));
                 // file slash state
                 let (next_char, input_after_next_char) = input_after_first_char.split_first();
-                self.syntax_violation_if("backslash", || next_char == Some('\\'));
+                self.violation_fn.call_if(Backslash, || next_char == Some('\\'));
                 if matches!(next_char, Some('/') | Some('\\')) {
                     // file host state
                     self.serialization.push_str("file://");
                     let scheme_end = "file".len() as u32;
                     let host_start = "file://".len() as u32;
-                    let (path_start, host, remaining) =
+                    let (path_start, mut host, remaining) =
                         self.parse_file_host(input_after_next_char)?;
-                    let host_end = to_u32(self.serialization.len())?;
+                    let mut host_end = to_u32(self.serialization.len())?;
                     let mut has_host = !matches!(host, HostInternal::None);
                     let remaining = if path_start {
                         self.parse_path_start(SchemeType::File, &mut has_host, remaining)
@@ -487,7 +579,13 @@ impl<'a> Parser<'a> {
                         self.serialization.push('/');
                         self.parse_path(SchemeType::File, &mut has_host, path_start, remaining)
                     };
-                    // FIXME: deal with has_host
+                    // For file URLs that have a host and whose path starts
+                    // with the windows drive letter we just remove the host.
+                    if !has_host {
+                        self.serialization.drain(host_start as usize..host_end as usize);
+                        host_end = host_start;
+                        host = HostInternal::None;
+                    }
                     let (query_start, fragment_start) =
                         self.parse_query_and_fragment(scheme_end, remaining)?;
                     Ok(Url {
@@ -616,7 +714,7 @@ impl<'a> Parser<'a> {
             Some('/') | Some('\\') => {
                 let (slashes_count, remaining) = input.count_matching(|c| matches!(c, '/' | '\\'));
                 if slashes_count >= 2 {
-                    self.syntax_violation_if("expected //", || {
+                    self.violation_fn.call_if(SyntaxViolation::ExpectedDoubleSlash, || {
                         input.clone().take_while(|&c| matches!(c, '/' | '\\'))
                         .collect::<String>() != "//"
                     });
@@ -680,11 +778,9 @@ impl<'a> Parser<'a> {
             match c {
                 '@' => {
                     if last_at.is_some() {
-                        self.syntax_violation("unencoded @ sign in username or password")
+                        self.violation_fn.call(SyntaxViolation::UnencodedAtSign)
                     } else {
-                        self.syntax_violation(
-                            "embedding authentification information (username or password) \
-                            in an URL is not recommended")
+                        self.violation_fn.call(SyntaxViolation::EmbeddedCredentials)
                     }
                     last_at = Some((char_count, remaining.clone()))
                 },
@@ -701,14 +797,23 @@ impl<'a> Parser<'a> {
         };
 
         let mut username_end = None;
+        let mut has_password = false;
+        let mut has_username = false;
         while userinfo_char_count > 0 {
             let (c, utf8_c) = input.next_utf8().unwrap();
             userinfo_char_count -= 1;
             if c == ':' && username_end.is_none() {
                 // Start parsing password
                 username_end = Some(to_u32(self.serialization.len())?);
-                self.serialization.push(':');
+                // We don't add a colon if the password is empty
+                if userinfo_char_count > 0 {
+                    self.serialization.push(':');
+                    has_password = true;
+                }
             } else {
+                if !has_password {
+                    has_username = true;
+                }
                 self.check_url_code_point(c, &input);
                 self.serialization.extend(utf8_percent_encode(utf8_c, USERINFO_ENCODE_SET));
             }
@@ -717,7 +822,9 @@ impl<'a> Parser<'a> {
             Some(i) => i,
             None => to_u32(self.serialization.len())?,
         };
-        self.serialization.push('@');
+        if has_username || has_password {
+            self.serialization.push('@');
+        }
         Ok((username_end, remaining))
     }
 
@@ -783,6 +890,10 @@ impl<'a> Parser<'a> {
         if scheme_type.is_special() && host_str.is_empty() {
             return Err(ParseError::EmptyHost)
         }
+        if !scheme_type.is_special() {
+            let host = Host::parse_opaque(host_str)?;
+            return Ok((host, input));
+        }
         let host = Host::parse(host_str)?;
         Ok((host, input))
     }
@@ -867,7 +978,7 @@ impl<'a> Parser<'a> {
         match input.split_first() {
             (Some('/'), remaining) => input = remaining,
             (Some('\\'), remaining) => if scheme_type.is_special() {
-                self.syntax_violation("backslash");
+                self.violation_fn.call(SyntaxViolation::Backslash);
                 input = remaining
             },
             _ => {}
@@ -895,7 +1006,7 @@ impl<'a> Parser<'a> {
                     },
                     '\\' if self.context != Context::PathSegmentSetter &&
                             scheme_type.is_special() => {
-                        self.syntax_violation("backslash");
+                        self.violation_fn.call(SyntaxViolation::Backslash);
                         ends_with_slash = true;
                         break
                     },
@@ -905,18 +1016,14 @@ impl<'a> Parser<'a> {
                     },
                     _ => {
                         self.check_url_code_point(c, &input);
-                        if c == '%' {
-                            let after_percent_sign = input.clone();
-                            if matches!(input.next(), Some('2')) &&
-                                    matches!(input.next(), Some('E') | Some('e')) {
-                                self.serialization.push('.');
-                                continue
-                            }
-                            input = after_percent_sign
-                        }
                         if self.context == Context::PathSegmentSetter {
-                            self.serialization.extend(utf8_percent_encode(
-                                utf8_c, PATH_SEGMENT_ENCODE_SET));
+                            if scheme_type.is_special() {
+                                self.serialization.extend(utf8_percent_encode(
+                                    utf8_c, SPECIAL_PATH_SEGMENT_ENCODE_SET));
+                            } else {
+                                self.serialization.extend(utf8_percent_encode(
+                                    utf8_c, PATH_SEGMENT_ENCODE_SET));
+                            }
                         } else {
                             self.serialization.extend(utf8_percent_encode(
                                 utf8_c, DEFAULT_ENCODE_SET));
@@ -925,7 +1032,7 @@ impl<'a> Parser<'a> {
                 }
             }
             match &self.serialization[segment_start..] {
-                ".." => {
+                ".." | "%2e%2e" | "%2e%2E" | "%2E%2e" | "%2E%2E" | "%2e." | "%2E." | ".%2e" | ".%2E"  => {
                     debug_assert!(self.serialization.as_bytes()[segment_start - 1] == b'/');
                     self.serialization.truncate(segment_start - 1);  // Truncate "/.."
                     self.pop_path(scheme_type, path_start);
@@ -933,7 +1040,7 @@ impl<'a> Parser<'a> {
                         self.serialization.push('/')
                     }
                 },
-                "." => {
+                "." | "%2e" | "%2E" => {
                     self.serialization.truncate(segment_start);
                 },
                 _ => {
@@ -945,7 +1052,7 @@ impl<'a> Parser<'a> {
                             self.serialization.push(':');
                         }
                         if *has_host {
-                            self.syntax_violation("file: with host and Windows drive letter");
+                            self.violation_fn.call(SyntaxViolation::FileWithHostAndWindowsDrive);
                             *has_host = false;  // FIXME account for this in callers
                         }
                     }
@@ -1087,7 +1194,7 @@ impl<'a> Parser<'a> {
     pub fn parse_fragment(&mut self, mut input: Input) {
         while let Some((c, utf8_c)) = input.next_utf8() {
             if c ==  '\0' {
-                self.syntax_violation("NULL characters are ignored in URL fragment identifiers")
+                self.violation_fn.call(SyntaxViolation::NullInFragment)
             } else {
                 self.check_url_code_point(c, &input);
                 self.serialization.extend(utf8_percent_encode(utf8_c,
@@ -1097,15 +1204,16 @@ impl<'a> Parser<'a> {
     }
 
     fn check_url_code_point(&self, c: char, input: &Input) {
-        if let Some(log) = self.log_syntax_violation {
+        let vfn = self.violation_fn;
+        if vfn.is_set() {
             if c == '%' {
                 let mut input = input.clone();
                 if !matches!((input.next(), input.next()), (Some(a), Some(b))
                              if is_ascii_hex_digit(a) && is_ascii_hex_digit(b)) {
-                    log("expected 2 hex digits after %")
+                    vfn.call(SyntaxViolation::PercentDecode)
                 }
             } else if !is_url_code_point(c) {
-                log("non-URL code point")
+                vfn.call(SyntaxViolation::NonUrlCodePoint)
             }
         }
     }
diff -Nrup a/third_party/rust/url/src/quirks.rs b/third_party/rust/url/src/quirks.rs
--- a/third_party/rust/url/src/quirks.rs	2020-02-18 02:39:21.000000000 +0300
+++ b/third_party/rust/url/src/quirks.rs	2020-02-28 20:28:57.249406137 +0300
@@ -46,7 +46,7 @@ pub fn set_href(url: &mut Url, value: &s
 
 /// Getter for https://url.spec.whatwg.org/#dom-url-origin
 pub fn origin(url: &Url) -> String {
-    url.origin().unicode_serialization()
+    url.origin().ascii_serialization()
 }
 
 /// Getter for https://url.spec.whatwg.org/#dom-url-protocol
@@ -152,7 +152,7 @@ pub fn set_port(url: &mut Url, new_port:
     {
         // has_host implies !cannot_be_a_base
         let scheme = url.scheme();
-        if !url.has_host() || scheme == "file" {
+        if !url.has_host() || url.host() == Some(Host::Domain("")) || scheme == "file" {
             return Err(())
         }
         result = Parser::parse_port(Input::new(new_port), || default_port(scheme), Context::Setter)
diff -Nrup a/third_party/rust/url/tests/data.rs b/third_party/rust/url/tests/data.rs
--- a/third_party/rust/url/tests/data.rs	2020-02-18 02:39:21.000000000 +0300
+++ b/third_party/rust/url/tests/data.rs	2020-02-28 20:28:57.263406036 +0300
@@ -9,7 +9,7 @@
 //! Data-driven tests
 
 extern crate rustc_serialize;
-extern crate test;
+extern crate rustc_test as test;
 extern crate url;
 
 use rustc_serialize::json::{self, Json};
@@ -29,6 +29,7 @@ fn check_invariants(url: &Url) {
 fn run_parsing(input: &str, base: &str, expected: Result<ExpectedAttributes, ()>) {
     let base = match Url::parse(&base) {
         Ok(base) => base,
+        Err(_) if expected.is_err() => return,
         Err(message) => panic!("Error parsing base {:?}: {}", base, message)
     };
     let (url, expected) = match (base.join(&input), expected) {
@@ -188,11 +189,7 @@ fn main() {
     {
         let mut add_one = |name: String, run: test::TestFn| {
             tests.push(test::TestDescAndFn {
-                desc: test::TestDesc {
-                    name: test::DynTestName(name),
-                    ignore: false,
-                    should_panic: test::ShouldPanic::No,
-                },
+                desc: test::TestDesc::new(test::DynTestName(name)),
                 testfn: run,
             })
         };
diff -Nrup a/third_party/rust/url/tests/setters_tests.json b/third_party/rust/url/tests/setters_tests.json
--- a/third_party/rust/url/tests/setters_tests.json	2020-02-18 02:39:21.000000000 +0300
+++ b/third_party/rust/url/tests/setters_tests.json	2020-02-28 20:28:57.250406130 +0300
@@ -102,6 +102,31 @@
             }
         },
         {
+            "comment": "Can’t switch from file URL with no host",
+            "href": "file://localhost/",
+            "new_value": "http",
+            "expected": {
+                "href": "file:///",
+                "protocol": "file:"
+            }
+        },
+        {
+            "href": "file:///test",
+            "new_value": "gopher",
+            "expected": {
+                "href": "file:///test",
+                "protocol": "file:"
+            }
+        },
+        {
+            "href": "file:",
+            "new_value": "wss",
+            "expected": {
+                "href": "file:///",
+                "protocol": "file:"
+            }
+        },
+        {
             "comment": "Spec deviation: from special scheme to not is not problematic. https://github.com/whatwg/url/issues/104",
             "href": "http://example.net",
             "new_value": "b",
@@ -176,6 +201,14 @@
             }
         },
         {
+            "href": "javascript:alert(1)",
+            "new_value": "wario",
+            "expected": {
+                "href": "javascript:alert(1)",
+                "username": ""
+            }
+        },
+        {
             "href": "http://example.net",
             "new_value": "me",
             "expected": {
@@ -224,6 +257,30 @@
                 "href": "http://%c3%89t%C3%A9@example.net/",
                 "username": "%c3%89t%C3%A9"
             }
+        },
+        {
+            "href": "sc:///",
+            "new_value": "x",
+            "expected": {
+                "href": "sc:///",
+                "username": ""
+            }
+        },
+        {
+            "href": "file://test/",
+            "new_value": "test",
+            "expected": {
+                "href": "file://test/",
+                "username": ""
+            }
+        },
+        {
+            "href": "javascript://x/",
+            "new_value": "wario",
+            "expected": {
+                "href": "javascript://wario@x/",
+                "username": "wario"
+            }
         }
     ],
     "password": [
@@ -303,10 +360,106 @@
                 "href": "http://:%c3%89t%C3%A9@example.net/",
                 "password": "%c3%89t%C3%A9"
             }
+        },
+        {
+            "href": "sc:///",
+            "new_value": "x",
+            "expected": {
+                "href": "sc:///",
+                "password": ""
+            }
+        },
+        {
+            "href": "file://test/",
+            "new_value": "test",
+            "expected": {
+                "href": "file://test/",
+                "password": ""
+            }
+        },
+        {
+            "href": "javascript://x/",
+            "new_value": "bowser",
+            "expected": {
+                "href": "javascript://:bowser@x/",
+                "password": "bowser"
+            }
         }
     ],
     "host": [
         {
+            "href": "sc://x/",
+            "new_value": "\u0009",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "\u000A",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "\u000D",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "#",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "/",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "?",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "@",
+            "expected": {
+                "href": "sc://x/",
+                "host": "x",
+                "hostname": "x"
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "ß",
+            "expected": {
+                "href": "sc://%C3%9F/",
+                "host": "%C3%9F",
+                "hostname": "%C3%9F"
+            }
+        },
+        {
             "comment": "Cannot-be-a-base means no host",
             "href": "mailto:me@example.net",
             "new_value": "example.com",
@@ -384,15 +537,6 @@
             }
         },
         {
-            "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",
@@ -536,7 +680,7 @@
             }
         },
         {
-            "comment": "\\ is not a delimiter for non-special schemes, and it’s invalid in a domain",
+            "comment": "\\ is not a delimiter for non-special schemes, but still forbidden in hosts",
             "href": "view-source+http://example.net/path",
             "new_value": "example.com\\stuff",
             "expected": {
@@ -600,10 +744,119 @@
                 "hostname": "example.com",
                 "port": ""
             }
+        },
+        {
+            "comment": "Broken IPv6",
+            "href": "http://example.net/",
+            "new_value": "[google.com]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "http://example.net/",
+            "new_value": "[::1.2.3.4x]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "http://example.net/",
+            "new_value": "[::1.2.3.]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "http://example.net/",
+            "new_value": "[::1.2.]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "http://example.net/",
+            "new_value": "[::1.]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
         }
     ],
     "hostname": [
         {
+            "href": "sc://x/",
+            "new_value": "\u0009",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "\u000A",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "\u000D",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "#",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "/",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "?",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "@",
+            "expected": {
+                "href": "sc://x/",
+                "host": "x",
+                "hostname": "x"
+            }
+        },
+        {
             "comment": "Cannot-be-a-base means no host",
             "href": "mailto:me@example.net",
             "new_value": "example.com",
@@ -659,15 +912,6 @@
             }
         },
         {
-            "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:8080",
             "new_value": "0x7F000001",
@@ -756,7 +1000,7 @@
             }
         },
         {
-            "comment": "\\ is not a delimiter for non-special schemes, and it’s invalid in a domain",
+            "comment": "\\ is not a delimiter for non-special schemes, but still forbidden in hosts",
             "href": "view-source+http://example.net/path",
             "new_value": "example.com\\stuff",
             "expected": {
@@ -765,6 +1009,52 @@
                 "hostname": "example.net",
                 "port": ""
             }
+        },
+        {
+            "comment": "Broken IPv6",
+            "href": "http://example.net/",
+            "new_value": "[google.com]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "http://example.net/",
+            "new_value": "[::1.2.3.4x]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "http://example.net/",
+            "new_value": "[::1.2.3.]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "http://example.net/",
+            "new_value": "[::1.2.]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "http://example.net/",
+            "new_value": "[::1.]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
         }
     ],
     "port": [
@@ -779,7 +1069,7 @@
             }
         },
         {
-            "comment": "Port number is removed if empty in the new value: https://github.com/whatwg/url/pull/113",
+            "comment": "Port number is removed if empty is the new value",
             "href": "http://example.net:8080",
             "new_value": "",
             "expected": {
@@ -920,6 +1210,65 @@
                 "hostname": "example.net",
                 "port": "8080"
             }
+        },
+        {
+            "comment": "Port numbers are 16 bit integers, overflowing is an error",
+            "href": "non-special://example.net:8080/path",
+            "new_value": "65536",
+            "expected": {
+                "href": "non-special://example.net:8080/path",
+                "host": "example.net:8080",
+                "hostname": "example.net",
+                "port": "8080"
+            }
+        },
+        {
+            "href": "file://test/",
+            "new_value": "12",
+            "expected": {
+                "href": "file://test/",
+                "port": ""
+            }
+        },
+        {
+            "href": "file://localhost/",
+            "new_value": "12",
+            "expected": {
+                "href": "file:///",
+                "port": ""
+            }
+        },
+        {
+            "href": "non-base:value",
+            "new_value": "12",
+            "expected": {
+                "href": "non-base:value",
+                "port": ""
+            }
+        },
+        {
+            "href": "sc:///",
+            "new_value": "12",
+            "expected": {
+                "href": "sc:///",
+                "port": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "12",
+            "expected": {
+                "href": "sc://x:12/",
+                "port": "12"
+            }
+        },
+        {
+            "href": "javascript://x/",
+            "new_value": "12",
+            "expected": {
+                "href": "javascript://x:12/",
+                "port": "12"
+            }
         }
     ],
     "pathname": [
@@ -970,8 +1319,8 @@
             "href": "view-source+http://example.net/home?lang=fr#nav",
             "new_value": "\\a\\%2E\\b\\%2e.\\c",
             "expected": {
-                "href": "view-source+http://example.net/\\a\\.\\b\\..\\c?lang=fr#nav",
-                "pathname": "/\\a\\.\\b\\..\\c"
+                "href": "view-source+http://example.net/\\a\\%2E\\b\\%2e.\\c?lang=fr#nav",
+                "pathname": "/\\a\\%2E\\b\\%2e.\\c"
             }
         },
         {
@@ -984,12 +1333,48 @@
             }
         },
         {
-            "comment": "Bytes already percent-encoded are left as-is, except %2E.",
+            "comment": "Bytes already percent-encoded are left as-is, including %2E outside dotted segments.",
             "href": "http://example.net",
             "new_value": "%2e%2E%c3%89té",
             "expected": {
-                "href": "http://example.net/..%c3%89t%C3%A9",
-                "pathname": "/..%c3%89t%C3%A9"
+                "href": "http://example.net/%2e%2E%c3%89t%C3%A9",
+                "pathname": "/%2e%2E%c3%89t%C3%A9"
+            }
+        },
+        {
+            "comment": "? needs to be encoded",
+            "href": "http://example.net",
+            "new_value": "?",
+            "expected": {
+                "href": "http://example.net/%3F",
+                "pathname": "/%3F"
+            }
+        },
+        {
+            "comment": "# needs to be encoded",
+            "href": "http://example.net",
+            "new_value": "#",
+            "expected": {
+                "href": "http://example.net/%23",
+                "pathname": "/%23"
+            }
+        },
+        {
+            "comment": "? needs to be encoded, non-special scheme",
+            "href": "sc://example.net",
+            "new_value": "?",
+            "expected": {
+                "href": "sc://example.net/%3F",
+                "pathname": "/%3F"
+            }
+        },
+        {
+            "comment": "# needs to be encoded, non-special scheme",
+            "href": "sc://example.net",
+            "new_value": "#",
+            "expected": {
+                "href": "sc://example.net/%23",
+                "pathname": "/%23"
             }
         }
     ],
diff -Nrup a/third_party/rust/url/tests/unit.rs b/third_party/rust/url/tests/unit.rs
--- a/third_party/rust/url/tests/unit.rs	2020-02-18 02:39:21.000000000 +0300
+++ b/third_party/rust/url/tests/unit.rs	2020-02-28 20:28:57.263406036 +0300
@@ -11,7 +11,9 @@
 #[macro_use]
 extern crate url;
 
+use std::ascii::AsciiExt;
 use std::borrow::Cow;
+use std::cell::{Cell, RefCell};
 use std::net::{Ipv4Addr, Ipv6Addr};
 use std::path::{Path, PathBuf};
 use url::{Host, HostAndPort, Url, form_urlencoded};
@@ -108,6 +110,17 @@ fn new_directory_paths() {
 }
 
 #[test]
+fn path_backslash_fun() {
+    let mut special_url = "http://foobar.com".parse::<Url>().unwrap();
+    special_url.path_segments_mut().unwrap().push("foo\\bar");
+    assert_eq!(special_url.as_str(), "http://foobar.com/foo%5Cbar");
+
+    let mut nonspecial_url = "thing://foobar.com".parse::<Url>().unwrap();
+    nonspecial_url.path_segments_mut().unwrap().push("foo\\bar");
+    assert_eq!(nonspecial_url.as_str(), "thing://foobar.com/foo\\bar");
+}
+
+#[test]
 fn from_str() {
     assert!("http://testing.com/this".parse::<Url>().is_ok());
 }
@@ -221,7 +234,7 @@ fn test_serialization() {
         ("http://example.com/", "http://example.com/"),
         ("http://addslash.com", "http://addslash.com/"),
         ("http://@emptyuser.com/", "http://emptyuser.com/"),
-        ("http://:@emptypass.com/", "http://:@emptypass.com/"),
+        ("http://:@emptypass.com/", "http://emptypass.com/"),
         ("http://user@user.com/", "http://user@user.com/"),
         ("http://user:pass@userpass.com/", "http://user:pass@userpass.com/"),
         ("http://slashquery.com/path/?q=something", "http://slashquery.com/path/?q=something"),
@@ -256,6 +269,15 @@ fn test_form_serialize() {
 }
 
 #[test]
+fn form_urlencoded_custom_encoding_override() {
+    let encoded = form_urlencoded::Serializer::new(String::new())
+        .custom_encoding_override(|s| s.as_bytes().to_ascii_uppercase().into())
+        .append_pair("foo", "bar")
+        .finish();
+    assert_eq!(encoded, "FOO=BAR");
+}
+
+#[test]
 fn host_and_port_display() {
     assert_eq!(
         format!(
@@ -286,22 +308,6 @@ fn host_and_port_display() {
 }
 
 #[test]
-/// https://github.com/servo/rust-url/issues/25
-fn issue_25() {
-    let filename = if cfg!(windows) { r"C:\run\pg.sock" } else { "/run/pg.sock" };
-    let mut url = Url::from_file_path(filename).unwrap();
-    url.check_invariants().unwrap();
-    url.set_scheme("postgres").unwrap();
-    url.check_invariants().unwrap();
-    url.set_host(Some("")).unwrap();
-    url.check_invariants().unwrap();
-    url.set_username("me").unwrap();
-    url.check_invariants().unwrap();
-    let expected = format!("postgres://me@/{}run/pg.sock", if cfg!(windows) { "C:/" } else { "" });
-    assert_eq!(url.as_str(), expected);
-}
-
-#[test]
 /// https://github.com/servo/rust-url/issues/61
 fn issue_61() {
     let mut url = Url::parse("http://mozilla.org").unwrap();
@@ -372,6 +378,11 @@ fn test_set_host() {
     let mut url = Url::parse("foobar://example.net/hello").unwrap();
     url.set_host(None).unwrap();
     assert_eq!(url.as_str(), "foobar:/hello");
+
+    let mut url = Url::parse("foo://ș").unwrap();
+    assert_eq!(url.as_str(), "foo://%C8%99/");
+    url.set_host(Some("goșu.ro")).unwrap();
+    assert_eq!(url.as_str(), "foo://go%C8%99u.ro/");
 }
 
 #[test]
@@ -478,3 +489,68 @@ fn test_windows_unc_path() {
     let url = Url::from_file_path(Path::new(r"\\.\some\path\file.txt"));
     assert!(url.is_err());
 }
+
+// Test the now deprecated log_syntax_violation method for backward
+// compatibility
+#[test]
+#[allow(deprecated)]
+fn test_old_log_violation_option() {
+    let violation = Cell::new(None);
+    let url = Url::options()
+        .log_syntax_violation(Some(&|s| violation.set(Some(s.to_owned()))))
+        .parse("http:////mozilla.org:42").unwrap();
+    assert_eq!(url.port(), Some(42));
+
+    let violation = violation.take();
+    assert_eq!(violation, Some("expected //".to_string()));
+}
+
+#[test]
+fn test_syntax_violation_callback() {
+    use url::SyntaxViolation::*;
+    let violation = Cell::new(None);
+    let url = Url::options()
+        .syntax_violation_callback(Some(&|v| violation.set(Some(v))))
+        .parse("http:////mozilla.org:42").unwrap();
+    assert_eq!(url.port(), Some(42));
+
+    let v = violation.take().unwrap();
+    assert_eq!(v, ExpectedDoubleSlash);
+    assert_eq!(v.description(), "expected //");
+}
+
+#[test]
+fn test_syntax_violation_callback_lifetimes() {
+    use url::SyntaxViolation::*;
+    let violation = Cell::new(None);
+    let vfn = |s| violation.set(Some(s));
+
+    let url = Url::options()
+        .syntax_violation_callback(Some(&vfn))
+        .parse("http:////mozilla.org:42").unwrap();
+    assert_eq!(url.port(), Some(42));
+    assert_eq!(violation.take(), Some(ExpectedDoubleSlash));
+
+    let url = Url::options()
+        .syntax_violation_callback(Some(&vfn))
+        .parse("http://mozilla.org\\path").unwrap();
+    assert_eq!(url.path(), "/path");
+    assert_eq!(violation.take(), Some(Backslash));
+}
+
+#[test]
+fn test_options_reuse() {
+    use url::SyntaxViolation::*;
+    let violations = RefCell::new(Vec::new());
+    let vfn = |v| violations.borrow_mut().push(v);
+
+    let options = Url::options()
+        .syntax_violation_callback(Some(&vfn));
+    let url = options.parse("http:////mozilla.org").unwrap();
+
+    let options = options.base_url(Some(&url));
+    let url = options.parse("/sub\\path").unwrap();
+    assert_eq!(url.as_str(), "http://mozilla.org/sub/path");
+    assert_eq!(*violations.borrow(),
+               vec!(ExpectedDoubleSlash, Backslash));
+}
diff -Nrup a/third_party/rust/url/tests/urltestdata.json b/third_party/rust/url/tests/urltestdata.json
--- a/third_party/rust/url/tests/urltestdata.json	2020-02-18 02:39:21.000000000 +0300
+++ b/third_party/rust/url/tests/urltestdata.json	2020-02-28 20:28:57.252406115 +0300
@@ -31,6 +31,66 @@
     "hash": "#c"
   },
   {
+    "input": "https://test:@test",
+    "base": "about:blank",
+    "href": "https://test@test/",
+    "origin": "https://test",
+    "protocol": "https:",
+    "username": "test",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "https://:@test",
+    "base": "about:blank",
+    "href": "https://test/",
+    "origin": "https://test",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "non-special://test:@test/x",
+    "base": "about:blank",
+    "href": "non-special://test@test/x",
+    "origin": "null",
+    "protocol": "non-special:",
+    "username": "test",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/x",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "non-special://:@test/x",
+    "base": "about:blank",
+    "href": "non-special://test/x",
+    "origin": "null",
+    "protocol": "non-special:",
+    "username": "",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/x",
+    "search": "",
+    "hash": ""
+  },
+  {
     "input": "http:foo.com",
     "base": "http://example.org/foo/bar",
     "href": "http://example.org/foo/foo.com",
@@ -106,6 +166,20 @@
     "hash": "# e"
   },
   {
+    "input": "lolscheme:x x#x x",
+    "base": "about:blank",
+    "href": "lolscheme:x x#x x",
+    "protocol": "lolscheme:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "x x",
+    "search": "",
+    "hash": "#x x"
+  },
+  {
     "input": "http://f:/c",
     "base": "http://example.org/foo/bar",
     "href": "http://f/c",
@@ -201,6 +275,11 @@
     "failure": true
   },
   {
+    "input": "non-special://f:999999/c",
+    "base": "http://example.org/foo/bar",
+    "failure": true
+  },
+  {
     "input": "http://f: 21 / b ? d # e ",
     "base": "http://example.org/foo/bar",
     "failure": true
@@ -960,6 +1039,26 @@
     "hash": ""
   },
   {
+    "input": "file://example:1/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "file://example:test/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "file://example%/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "file://[example]/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
     "input": "ftps:/example.com/",
     "base": "http://example.org/foo/bar",
     "href": "ftps:/example.com/",
@@ -1785,7 +1884,7 @@
   {
     "input": "http://example.com/foo/%2e%2",
     "base": "about:blank",
-    "href": "http://example.com/foo/.%2",
+    "href": "http://example.com/foo/%2e%2",
     "origin": "http://example.com",
     "protocol": "http:",
     "username": "",
@@ -1793,14 +1892,14 @@
     "host": "example.com",
     "hostname": "example.com",
     "port": "",
-    "pathname": "/foo/.%2",
+    "pathname": "/foo/%2e%2",
     "search": "",
     "hash": ""
   },
   {
     "input": "http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar",
     "base": "about:blank",
-    "href": "http://example.com/..bar",
+    "href": "http://example.com/%2e.bar",
     "origin": "http://example.com",
     "protocol": "http:",
     "username": "",
@@ -1808,7 +1907,7 @@
     "host": "example.com",
     "hostname": "example.com",
     "port": "",
-    "pathname": "/..bar",
+    "pathname": "/%2e.bar",
     "search": "",
     "hash": ""
   },
@@ -2189,11 +2288,6 @@
     "hash": "# %C2%BB"
   },
   {
-    "input": "http://[www.google.com]/",
-    "base": "about:blank",
-    "failure": true
-  },
-  {
     "input": "http://www.google.com",
     "base": "about:blank",
     "href": "http://www.google.com/",
@@ -2226,7 +2320,7 @@
   {
     "input": "http://www/foo%2Ehtml",
     "base": "about:blank",
-    "href": "http://www/foo.html",
+    "href": "http://www/foo%2Ehtml",
     "origin": "http://www",
     "protocol": "http:",
     "username": "",
@@ -2234,7 +2328,7 @@
     "host": "www",
     "hostname": "www",
     "port": "",
-    "pathname": "/foo.html",
+    "pathname": "/foo%2Ehtml",
     "search": "",
     "hash": ""
   },
@@ -3096,7 +3190,7 @@
   {
     "input": "http:a:@www.example.com",
     "base": "about:blank",
-    "href": "http://a:@www.example.com/",
+    "href": "http://a@www.example.com/",
     "origin": "http://www.example.com",
     "protocol": "http:",
     "username": "a",
@@ -3111,7 +3205,7 @@
   {
     "input": "http:/a:@www.example.com",
     "base": "about:blank",
-    "href": "http://a:@www.example.com/",
+    "href": "http://a@www.example.com/",
     "origin": "http://www.example.com",
     "protocol": "http:",
     "username": "a",
@@ -3126,7 +3220,7 @@
   {
     "input": "http://a:@www.example.com",
     "base": "about:blank",
-    "href": "http://a:@www.example.com/",
+    "href": "http://a@www.example.com/",
     "origin": "http://www.example.com",
     "protocol": "http:",
     "username": "a",
@@ -3171,7 +3265,7 @@
   {
     "input": "http://:@www.example.com",
     "base": "about:blank",
-    "href": "http://:@www.example.com/",
+    "href": "http://www.example.com/",
     "origin": "http://www.example.com",
     "protocol": "http:",
     "username": "",
@@ -3465,6 +3559,22 @@
     "search": "",
     "hash": ""
   },
+  "Leading and trailing C0 control or space",
+  {
+    "input": "\u0000\u001b\u0004\u0012 http://example.com/\u001f \u000d ",
+    "base": "about:blank",
+    "href": "http://example.com/",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
   "Ideographic full stop (full-width period for Chinese, etc.) should be treated as a dot. U+3002 is mapped to U+002E (dot)",
   {
     "input": "http://www.foo。bar.com",
@@ -3493,6 +3603,32 @@
     "base": "http://other.com/",
     "failure": true
   },
+  "U+FFFD",
+  {
+    "input": "https://\ufffd",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "https://%EF%BF%BD",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "https://x/\ufffd?\ufffd#\ufffd",
+    "base": "about:blank",
+    "href": "https://x/%EF%BF%BD?%EF%BF%BD#%EF%BF%BD",
+    "origin": "https://x",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "x",
+    "hostname": "x",
+    "port": "",
+    "pathname": "/%EF%BF%BD",
+    "search": "?%EF%BF%BD",
+    "hash": "#%EF%BF%BD"
+  },
   "Test name prepping, fullwidth input should be converted to ASCII and NOT IDN-ized. This is 'Go' in fullwidth UTF-8/UTF-16.",
   {
     "input": "http://Go.com",
@@ -3536,7 +3672,7 @@
     "input": "http://你好你好",
     "base": "http://other.com/",
     "href": "http://xn--6qqa088eba/",
-    "origin": "http://你好你好",
+    "origin": "http://xn--6qqa088eba",
     "protocol": "http:",
     "username": "",
     "password": "",
@@ -3547,6 +3683,36 @@
     "search": "",
     "hash": ""
   },
+  {
+    "input": "https://faß.ExAmPlE/",
+    "base": "about:blank",
+    "href": "https://xn--fa-hia.example/",
+    "origin": "https://xn--fa-hia.example",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "xn--fa-hia.example",
+    "hostname": "xn--fa-hia.example",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "sc://faß.ExAmPlE/",
+    "base": "about:blank",
+    "href": "sc://fa%C3%9F.ExAmPlE/",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "fa%C3%9F.ExAmPlE",
+    "hostname": "fa%C3%9F.ExAmPlE",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
   "Invalid escaped characters should fail and the percents should be escaped. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24191",
   {
     "input": "http://%zz%66%a.com",
@@ -3600,18 +3766,23 @@
     "base": "http://other.com/",
     "failure": true
   },
-  "Invalid escaping should trigger the regular host error handling",
+  "Invalid escaping in hosts causes failure",
   {
     "input": "http://%3g%78%63%30%2e%30%32%35%30%2E.01",
     "base": "http://other.com/",
     "failure": true
   },
-  "Something that isn't exactly an IP should get treated as a host and spaces escaped",
+  "A space in a host causes failure",
   {
     "input": "http://192.168.0.1 hello",
     "base": "http://other.com/",
     "failure": true
   },
+  {
+    "input": "https://x x:12",
+    "base": "about:blank",
+    "failure": true
+  },
   "Fullwidth and escaped UTF-8 fullwidth should still be treated as IP",
   {
     "input": "http://0Xc0.0250.01",
@@ -3628,12 +3799,83 @@
     "search": "",
     "hash": ""
   },
+  "Domains with empty labels",
+  {
+    "input": "http://./",
+    "base": "about:blank",
+    "href": "http://./",
+    "origin": "http://.",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": ".",
+    "hostname": ".",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://../",
+    "base": "about:blank",
+    "href": "http://../",
+    "origin": "http://..",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "..",
+    "hostname": "..",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://0..0x300/",
+    "base": "about:blank",
+    "href": "http://0..0x300/",
+    "origin": "http://0..0x300",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "0..0x300",
+    "hostname": "0..0x300",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
   "Broken IPv6",
   {
+    "input": "http://[www.google.com]/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
     "input": "http://[google.com]",
     "base": "http://other.com/",
     "failure": true
   },
+  {
+    "input": "http://[::1.2.3.4x]",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://[::1.2.3.]",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://[::1.2.]",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://[::1.]",
+    "base": "http://other.com/",
+    "failure": true
+  },
   "Misc Unicode",
   {
     "input": "http://foo:💩@example.com/bar",
@@ -4176,22 +4418,91 @@
     "search": "",
     "hash": ""
   },
-  "# unknown schemes and non-ASCII domains",
+  "# unknown schemes and their hosts",
   {
     "input": "sc://ñ.test/",
     "base": "about:blank",
-    "href": "sc://xn--ida.test/",
+    "href": "sc://%C3%B1.test/",
     "origin": "null",
     "protocol": "sc:",
     "username": "",
     "password": "",
-    "host": "xn--ida.test",
-    "hostname": "xn--ida.test",
+    "host": "%C3%B1.test",
+    "hostname": "%C3%B1.test",
     "port": "",
     "pathname": "/",
     "search": "",
     "hash": ""
   },
+  {
+    "input": "sc://\u001F!\"$&'()*+,-.;<=>^_`{|}~/",
+    "base": "about:blank",
+    "href": "sc://%1F!\"$&'()*+,-.;<=>^_`{|}~/",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "%1F!\"$&'()*+,-.;<=>^_`{|}~",
+    "hostname": "%1F!\"$&'()*+,-.;<=>^_`{|}~",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "sc://\u0000/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "sc:// /",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "sc://%/",
+    "base": "about:blank",
+    "href": "sc://%/",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "%",
+    "hostname": "%",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "sc://[/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "sc://\\/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "sc://]/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "x",
+    "base": "sc://ñ",
+    "href": "sc://%C3%B1/x",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "%C3%B1",
+    "hostname": "%C3%B1",
+    "port": "",
+    "pathname": "/x",
+    "search": "",
+    "hash": ""
+  },
   "# unknown schemes and backslashes",
   {
     "input": "sc:\\../",
@@ -4224,6 +4535,88 @@
     "search": "",
     "hash": ""
   },
+  "# unknown scheme with bogus percent-encoding",
+  {
+    "input": "wow:%NBD",
+    "base": "about:blank",
+    "href": "wow:%NBD",
+    "origin": "null",
+    "protocol": "wow:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "%NBD",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "wow:%1G",
+    "base": "about:blank",
+    "href": "wow:%1G",
+    "origin": "null",
+    "protocol": "wow:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "%1G",
+    "search": "",
+    "hash": ""
+  },
+  "# Hosts and percent-encoding",
+  {
+    "input": "ftp://example.com%80/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "ftp://example.com%A0/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "https://example.com%80/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "https://example.com%A0/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "ftp://%e2%98%83",
+    "base": "about:blank",
+    "href": "ftp://xn--n3h/",
+    "origin": "ftp://xn--n3h",
+    "protocol": "ftp:",
+    "username": "",
+    "password": "",
+    "host": "xn--n3h",
+    "hostname": "xn--n3h",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "https://%e2%98%83",
+    "base": "about:blank",
+    "href": "https://xn--n3h/",
+    "origin": "https://xn--n3h",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "xn--n3h",
+    "hostname": "xn--n3h",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
   "# tests from jsdom/whatwg-url designed for code coverage",
   {
     "input": "http://127.0.0.1:10100/relative_import.html",
@@ -4371,75 +4764,1385 @@
     "port": "",
     "pathname": "/baz",
     "search": "?qux",
-    "searchParams": "",
+    "searchParams": "qux=",
     "hash": "#foo%08bar"
   },
-  "# IPv6 compression and serialization",
+  "# IPv4 parsing (via https://github.com/nodejs/node/pull/10317)",
+  {
+    "input": "http://192.168.257",
+    "base": "http://other.com/",
+    "href": "http://192.168.1.1/",
+    "origin": "http://192.168.1.1",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "192.168.1.1",
+    "hostname": "192.168.1.1",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://192.168.257.com",
+    "base": "http://other.com/",
+    "href": "http://192.168.257.com/",
+    "origin": "http://192.168.257.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "192.168.257.com",
+    "hostname": "192.168.257.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://256",
+    "base": "http://other.com/",
+    "href": "http://0.0.1.0/",
+    "origin": "http://0.0.1.0",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "0.0.1.0",
+    "hostname": "0.0.1.0",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://256.com",
+    "base": "http://other.com/",
+    "href": "http://256.com/",
+    "origin": "http://256.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "256.com",
+    "hostname": "256.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://999999999",
+    "base": "http://other.com/",
+    "href": "http://59.154.201.255/",
+    "origin": "http://59.154.201.255",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "59.154.201.255",
+    "hostname": "59.154.201.255",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://999999999.com",
+    "base": "http://other.com/",
+    "href": "http://999999999.com/",
+    "origin": "http://999999999.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "999999999.com",
+    "hostname": "999999999.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://10000000000",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://10000000000.com",
+    "base": "http://other.com/",
+    "href": "http://10000000000.com/",
+    "origin": "http://10000000000.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "10000000000.com",
+    "hostname": "10000000000.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://4294967295",
+    "base": "http://other.com/",
+    "href": "http://255.255.255.255/",
+    "origin": "http://255.255.255.255",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "255.255.255.255",
+    "hostname": "255.255.255.255",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://4294967296",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://0xffffffff",
+    "base": "http://other.com/",
+    "href": "http://255.255.255.255/",
+    "origin": "http://255.255.255.255",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "255.255.255.255",
+    "hostname": "255.255.255.255",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://0xffffffff1",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://256.256.256.256",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://256.256.256.256.256",
+    "base": "http://other.com/",
+    "href": "http://256.256.256.256.256/",
+    "origin": "http://256.256.256.256.256",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "256.256.256.256.256",
+    "hostname": "256.256.256.256.256",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "https://0x.0x.0",
+    "base": "about:blank",
+    "href": "https://0.0.0.0/",
+    "origin": "https://0.0.0.0",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "0.0.0.0",
+    "hostname": "0.0.0.0",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "More IPv4 parsing (via https://github.com/jsdom/whatwg-url/issues/92)",
+  {
+    "input": "https://256.0.0.1/test",
+    "base": "about:blank",
+    "failure": true
+  },
+  "# file URLs containing percent-encoded Windows drive letters (shouldn't work)",
+  {
+    "input": "file:///C%3A/",
+    "base": "about:blank",
+    "href": "file:///C%3A/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C%3A/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:///C%7C/",
+    "base": "about:blank",
+    "href": "file:///C%7C/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C%7C/",
+    "search": "",
+    "hash": ""
+  },
+  "# file URLs relative to other file URLs (via https://github.com/jsdom/whatwg-url/pull/60)",
+  {
+    "input": "pix/submit.gif",
+    "base": "file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/anchor.html",
+    "href": "file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "..",
+    "base": "file:///C:/",
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "..",
+    "base": "file:///",
+    "href": "file:///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "# More file URL tests by zcorpan and annevk",
+  {
+    "input": "/",
+    "base": "file:///C:/a/b",
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "//d:",
+    "base": "file:///C:/a/b",
+    "href": "file:///d:",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/d:",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "//d:/..",
+    "base": "file:///C:/a/b",
+    "href": "file:///d:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/d:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "..",
+    "base": "file:///ab:/",
+    "href": "file:///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "..",
+    "base": "file:///1:/",
+    "href": "file:///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "",
+    "base": "file:///test?test#test",
+    "href": "file:///test?test",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "?test",
+    "hash": ""
+  },
+  {
+    "input": "file:",
+    "base": "file:///test?test#test",
+    "href": "file:///test?test",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "?test",
+    "hash": ""
+  },
+  {
+    "input": "?x",
+    "base": "file:///test?test#test",
+    "href": "file:///test?x",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "?x",
+    "hash": ""
+  },
+  {
+    "input": "file:?x",
+    "base": "file:///test?test#test",
+    "href": "file:///test?x",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "?x",
+    "hash": ""
+  },
+  {
+    "input": "#x",
+    "base": "file:///test?test#test",
+    "href": "file:///test?test#x",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "?test",
+    "hash": "#x"
+  },
+  {
+    "input": "file:#x",
+    "base": "file:///test?test#test",
+    "href": "file:///test?test#x",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "?test",
+    "hash": "#x"
+  },
+  "# File URLs and many (back)slashes",
+  {
+    "input": "file:///localhost//cat",
+    "base": "about:blank",
+    "href": "file:///localhost//cat",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/localhost//cat",
+    "search": "",
+    "hash": ""
+  },
   {
-    "input": "http://[fe80:cd00::1257:0:211e:729c]/",
+    "input": "\\//pig",
+    "base": "file://lion/",
+    "href": "file:///pig",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/pig",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://",
+    "base": "file://ape/",
+    "href": "file:///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "# Windows drive letter handling with the 'file:' base URL",
+  {
+    "input": "C|#",
+    "base": "file://host/dir/file",
+    "href": "file:///C:#",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "C|?",
+    "base": "file://host/dir/file",
+    "href": "file:///C:?",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "C|/",
+    "base": "file://host/dir/file",
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "C|\n/",
+    "base": "file://host/dir/file",
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "C|\\",
+    "base": "file://host/dir/file",
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "C",
+    "base": "file://host/dir/file",
+    "href": "file://host/dir/C",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/dir/C",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "C|a",
+    "base": "file://host/dir/file",
+    "href": "file://host/dir/C|a",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/dir/C|a",
+    "search": "",
+    "hash": ""
+  },
+  "# Windows drive letter quirk in the file slash state",
+  {
+    "input": "/c:/foo/bar",
+    "base": "file://host/path",
+    "href": "file:///c:/foo/bar",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/c:/foo/bar",
+    "search": "",
+    "hash": ""
+  },
+  "# Windows drive letter quirk (no host)",
+  {
+    "input": "file:/C|/",
     "base": "about:blank",
-    "href": "http://[fe80:cd00::1257:0:211e:729c]/",
-    "origin": "http://[fe80:cd00::1257:0:211e:729c]",
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://C|/",
+    "base": "about:blank",
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  "# Windows drive letter quirk with not empty host",
+  {
+    "input": "file://example.net/C:/",
+    "base": "about:blank",
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://1.2.3.4/C:/",
+    "base": "about:blank",
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://[1::8]/C:/",
+    "base": "about:blank",
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  "# file URLs without base URL by Rimas Misevičius",
+  {
+    "input": "file:",
+    "base": "about:blank",
+    "href": "file:///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:?q=v",
+    "base": "about:blank",
+    "href": "file:///?q=v",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "?q=v",
+    "hash": ""
+  },
+  {
+    "input": "file:#frag",
+    "base": "about:blank",
+    "href": "file:///#frag",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": "#frag"
+  },
+  "# IPv6 tests",
+  {
+    "input": "http://[1:0::]",
+    "base": "http://example.net/",
+    "href": "http://[1::]/",
+    "origin": "http://[1::]",
     "protocol": "http:",
     "username": "",
     "password": "",
-    "host": "[fe80:cd00::1257:0:211e:729c]",
-    "hostname": "[fe80:cd00::1257:0:211e:729c]",
+    "host": "[1::]",
+    "hostname": "[1::]",
     "port": "",
     "pathname": "/",
     "search": "",
-    "searchParams": "",
     "hash": ""
   },
-  "# IPv6 compression and serialization: Compress sequences of two or more zeroes",
   {
-    "input": "http://[fe80:cd00:0:0:1257:0:211e:729c]/",
+    "input": "http://[0:1:2:3:4:5:6:7:8]",
+    "base": "http://example.net/",
+    "failure": true
+  },
+  {
+    "input": "https://[0::0::0]",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "https://[0:.0]",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "https://[0:0:]",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "https://[0:1:2:3:4:5:6:7.0.0.0.1]",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "https://[0:1.00.0.0.0]",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "https://[0:1.290.0.0.0]",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "https://[0:1.23.23]",
+    "base": "about:blank",
+    "failure": true
+  },
+  "# Empty host",
+  {
+    "input": "http://?",
     "base": "about:blank",
-    "href": "http://[fe80:cd00::1257:0:211e:729c]/",
-    "origin": "http://[fe80:cd00::1257:0:211e:729c]",
+    "failure": true
+  },
+  {
+    "input": "http://#",
+    "base": "about:blank",
+    "failure": true
+  },
+  "Port overflow (2^32 + 81)",
+  {
+    "input": "http://f:4294967377/c",
+    "base": "http://example.org/",
+    "failure": true
+  },
+  "Port overflow (2^64 + 81)",
+  {
+    "input": "http://f:18446744073709551697/c",
+    "base": "http://example.org/",
+    "failure": true
+  },
+  "Port overflow (2^128 + 81)",
+  {
+    "input": "http://f:340282366920938463463374607431768211537/c",
+    "base": "http://example.org/",
+    "failure": true
+  },
+  "# Non-special-URL path tests",
+  {
+    "input": "///",
+    "base": "sc://x/",
+    "href": "sc:///",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "tftp://foobar.com/someconfig;mode=netascii",
+    "base": "about:blank",
+    "href": "tftp://foobar.com/someconfig;mode=netascii",
+    "origin": "null",
+    "protocol": "tftp:",
+    "username": "",
+    "password": "",
+    "host": "foobar.com",
+    "hostname": "foobar.com",
+    "port": "",
+    "pathname": "/someconfig;mode=netascii",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "telnet://user:pass@foobar.com:23/",
+    "base": "about:blank",
+    "href": "telnet://user:pass@foobar.com:23/",
+    "origin": "null",
+    "protocol": "telnet:",
+    "username": "user",
+    "password": "pass",
+    "host": "foobar.com:23",
+    "hostname": "foobar.com",
+    "port": "23",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ut2004://10.10.10.10:7777/Index.ut2",
+    "base": "about:blank",
+    "href": "ut2004://10.10.10.10:7777/Index.ut2",
+    "origin": "null",
+    "protocol": "ut2004:",
+    "username": "",
+    "password": "",
+    "host": "10.10.10.10:7777",
+    "hostname": "10.10.10.10",
+    "port": "7777",
+    "pathname": "/Index.ut2",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "redis://foo:bar@somehost:6379/0?baz=bam&qux=baz",
+    "base": "about:blank",
+    "href": "redis://foo:bar@somehost:6379/0?baz=bam&qux=baz",
+    "origin": "null",
+    "protocol": "redis:",
+    "username": "foo",
+    "password": "bar",
+    "host": "somehost:6379",
+    "hostname": "somehost",
+    "port": "6379",
+    "pathname": "/0",
+    "search": "?baz=bam&qux=baz",
+    "hash": ""
+  },
+  {
+    "input": "rsync://foo@host:911/sup",
+    "base": "about:blank",
+    "href": "rsync://foo@host:911/sup",
+    "origin": "null",
+    "protocol": "rsync:",
+    "username": "foo",
+    "password": "",
+    "host": "host:911",
+    "hostname": "host",
+    "port": "911",
+    "pathname": "/sup",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "git://github.com/foo/bar.git",
+    "base": "about:blank",
+    "href": "git://github.com/foo/bar.git",
+    "origin": "null",
+    "protocol": "git:",
+    "username": "",
+    "password": "",
+    "host": "github.com",
+    "hostname": "github.com",
+    "port": "",
+    "pathname": "/foo/bar.git",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "irc://myserver.com:6999/channel?passwd",
+    "base": "about:blank",
+    "href": "irc://myserver.com:6999/channel?passwd",
+    "origin": "null",
+    "protocol": "irc:",
+    "username": "",
+    "password": "",
+    "host": "myserver.com:6999",
+    "hostname": "myserver.com",
+    "port": "6999",
+    "pathname": "/channel",
+    "search": "?passwd",
+    "hash": ""
+  },
+  {
+    "input": "dns://fw.example.org:9999/foo.bar.org?type=TXT",
+    "base": "about:blank",
+    "href": "dns://fw.example.org:9999/foo.bar.org?type=TXT",
+    "origin": "null",
+    "protocol": "dns:",
+    "username": "",
+    "password": "",
+    "host": "fw.example.org:9999",
+    "hostname": "fw.example.org",
+    "port": "9999",
+    "pathname": "/foo.bar.org",
+    "search": "?type=TXT",
+    "hash": ""
+  },
+  {
+    "input": "ldap://localhost:389/ou=People,o=JNDITutorial",
+    "base": "about:blank",
+    "href": "ldap://localhost:389/ou=People,o=JNDITutorial",
+    "origin": "null",
+    "protocol": "ldap:",
+    "username": "",
+    "password": "",
+    "host": "localhost:389",
+    "hostname": "localhost",
+    "port": "389",
+    "pathname": "/ou=People,o=JNDITutorial",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "git+https://github.com/foo/bar",
+    "base": "about:blank",
+    "href": "git+https://github.com/foo/bar",
+    "origin": "null",
+    "protocol": "git+https:",
+    "username": "",
+    "password": "",
+    "host": "github.com",
+    "hostname": "github.com",
+    "port": "",
+    "pathname": "/foo/bar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "urn:ietf:rfc:2648",
+    "base": "about:blank",
+    "href": "urn:ietf:rfc:2648",
+    "origin": "null",
+    "protocol": "urn:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "ietf:rfc:2648",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "tag:joe@example.org,2001:foo/bar",
+    "base": "about:blank",
+    "href": "tag:joe@example.org,2001:foo/bar",
+    "origin": "null",
+    "protocol": "tag:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "joe@example.org,2001:foo/bar",
+    "search": "",
+    "hash": ""
+  },
+  "# percent encoded hosts in non-special-URLs",
+  {
+    "input": "non-special://%E2%80%A0/",
+    "base": "about:blank",
+    "href": "non-special://%E2%80%A0/",
+    "protocol": "non-special:",
+    "username": "",
+    "password": "",
+    "host": "%E2%80%A0",
+    "hostname": "%E2%80%A0",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "non-special://H%4fSt/path",
+    "base": "about:blank",
+    "href": "non-special://H%4fSt/path",
+    "protocol": "non-special:",
+    "username": "",
+    "password": "",
+    "host": "H%4fSt",
+    "hostname": "H%4fSt",
+    "port": "",
+    "pathname": "/path",
+    "search": "",
+    "hash": ""
+  },
+  "# IPv6 in non-special-URLs",
+  {
+    "input": "non-special://[1:2:0:0:5:0:0:0]/",
+    "base": "about:blank",
+    "href": "non-special://[1:2:0:0:5::]/",
+    "protocol": "non-special:",
+    "username": "",
+    "password": "",
+    "host": "[1:2:0:0:5::]",
+    "hostname": "[1:2:0:0:5::]",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "non-special://[1:2:0:0:0:0:0:3]/",
+    "base": "about:blank",
+    "href": "non-special://[1:2::3]/",
+    "protocol": "non-special:",
+    "username": "",
+    "password": "",
+    "host": "[1:2::3]",
+    "hostname": "[1:2::3]",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "non-special://[1:2::3]:80/",
+    "base": "about:blank",
+    "href": "non-special://[1:2::3]:80/",
+    "protocol": "non-special:",
+    "username": "",
+    "password": "",
+    "host": "[1:2::3]:80",
+    "hostname": "[1:2::3]",
+    "port": "80",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "non-special://[:80/",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "blob:https://example.com:443/",
+    "base": "about:blank",
+    "href": "blob:https://example.com:443/",
+    "protocol": "blob:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "https://example.com:443/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf",
+    "base": "about:blank",
+    "href": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf",
+    "protocol": "blob:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "d3958f5c-0777-0845-9dcf-2cb28783acaf",
+    "search": "",
+    "hash": ""
+  },
+  "Invalid IPv4 radix digits",
+  {
+    "input": "http://0177.0.0.0189",
+    "base": "about:blank",
+    "href": "http://0177.0.0.0189/",
     "protocol": "http:",
     "username": "",
     "password": "",
-    "host": "[fe80:cd00::1257:0:211e:729c]",
-    "hostname": "[fe80:cd00::1257:0:211e:729c]",
+    "host": "0177.0.0.0189",
+    "hostname": "0177.0.0.0189",
     "port": "",
     "pathname": "/",
     "search": "",
-    "searchParams": "",
     "hash": ""
   },
-  "# IPv6 compression and serialization: Compress longest sequence of zeroes",
   {
-    "input": "http://[fe80:0:0:1257:0:0:0:cd00]/",
+    "input": "http://0x7f.0.0.0x7g",
     "base": "about:blank",
-    "href": "http://[fe80:0:0:1257::cd00]/",
-    "origin": "http://[fe80:0:0:1257::cd00]",
+    "href": "http://0x7f.0.0.0x7g/",
     "protocol": "http:",
     "username": "",
     "password": "",
-    "host": "[fe80:0:0:1257::cd00]",
-    "hostname": "[fe80:0:0:1257::cd00]",
+    "host": "0x7f.0.0.0x7g",
+    "hostname": "0x7f.0.0.0x7g",
     "port": "",
     "pathname": "/",
     "search": "",
-    "searchParams": "",
     "hash": ""
   },
-  "# IPv6 compression and serialization: Do not compress lone zeroes",
   {
-    "input": "http://[fe80:cd00:0:cde:1257:0:211e:729c]/",
+    "input": "http://0X7F.0.0.0X7G",
     "base": "about:blank",
-    "href": "http://[fe80:cd00:0:cde:1257:0:211e:729c]/",
-    "origin": "http://[fe80:cd00:0:cde:1257:0:211e:729c]",
+    "href": "http://0x7f.0.0.0x7g/",
     "protocol": "http:",
     "username": "",
     "password": "",
-    "host": "[fe80:cd00:0:cde:1257:0:211e:729c]",
-    "hostname": "[fe80:cd00:0:cde:1257:0:211e:729c]",
+    "host": "0x7f.0.0.0x7g",
+    "hostname": "0x7f.0.0.0x7g",
     "port": "",
     "pathname": "/",
     "search": "",
-    "searchParams": "",
     "hash": ""
+  },
+  "Invalid IPv4 portion of IPv6 address",
+  {
+    "input": "http://[::127.0.0.0.1]",
+    "base": "about:blank",
+    "failure": true
+  },
+  "Uncompressed IPv6 addresses with 0",
+  {
+    "input": "http://[0:1:0:1:0:1:0:1]",
+    "base": "about:blank",
+    "href": "http://[0:1:0:1:0:1:0:1]/",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "[0:1:0:1:0:1:0:1]",
+    "hostname": "[0:1:0:1:0:1:0:1]",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://[1:0:1:0:1:0:1:0]",
+    "base": "about:blank",
+    "href": "http://[1:0:1:0:1:0:1:0]/",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "[1:0:1:0:1:0:1:0]",
+    "hostname": "[1:0:1:0:1:0:1:0]",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "Percent-encoded query and fragment",
+  {
+    "input": "http://example.org/test?\u0022",
+    "base": "about:blank",
+    "href": "http://example.org/test?%22",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?%22",
+    "hash": ""
+  },
+  {
+    "input": "http://example.org/test?\u0023",
+    "base": "about:blank",
+    "href": "http://example.org/test?#",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.org/test?\u003C",
+    "base": "about:blank",
+    "href": "http://example.org/test?%3C",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?%3C",
+    "hash": ""
+  },
+  {
+    "input": "http://example.org/test?\u003E",
+    "base": "about:blank",
+    "href": "http://example.org/test?%3E",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?%3E",
+    "hash": ""
+  },
+  {
+    "input": "http://example.org/test?\u2323",
+    "base": "about:blank",
+    "href": "http://example.org/test?%E2%8C%A3",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?%E2%8C%A3",
+    "hash": ""
+  },
+  {
+    "input": "http://example.org/test?%23%23",
+    "base": "about:blank",
+    "href": "http://example.org/test?%23%23",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?%23%23",
+    "hash": ""
+  },
+  {
+    "input": "http://example.org/test?%GH",
+    "base": "about:blank",
+    "href": "http://example.org/test?%GH",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?%GH",
+    "hash": ""
+  },
+  {
+    "input": "http://example.org/test?a#%EF",
+    "base": "about:blank",
+    "href": "http://example.org/test?a#%EF",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?a",
+    "hash": "#%EF"
+  },
+  {
+    "input": "http://example.org/test?a#%GH",
+    "base": "about:blank",
+    "href": "http://example.org/test?a#%GH",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?a",
+    "hash": "#%GH"
+  },
+  "Bad bases",
+  {
+    "input": "test-a.html",
+    "base": "a",
+    "failure": true
+  },
+  {
+    "input": "test-a-slash.html",
+    "base": "a/",
+    "failure": true
+  },
+  {
+    "input": "test-a-slash-slash.html",
+    "base": "a//",
+    "failure": true
+  },
+  {
+    "input": "test-a-colon.html",
+    "base": "a:",
+    "failure": true
+  },
+  {
+    "input": "test-a-colon-slash.html",
+    "base": "a:/",
+    "href": "a:/test-a-colon-slash.html",
+    "protocol": "a:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test-a-colon-slash.html",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "test-a-colon-slash-slash.html",
+    "base": "a://",
+    "href": "a:///test-a-colon-slash-slash.html",
+    "protocol": "a:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test-a-colon-slash-slash.html",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "test-a-colon-b.html",
+    "base": "a:b",
+    "failure": true
+  },
+  {
+    "input": "test-a-colon-slash-b.html",
+    "base": "a:/b",
+    "href": "a:/test-a-colon-slash-b.html",
+    "protocol": "a:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test-a-colon-slash-b.html",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "test-a-colon-slash-slash-b.html",
+    "base": "a://b",
+    "href": "a://b/test-a-colon-slash-slash-b.html",
+    "protocol": "a:",
+    "username": "",
+    "password": "",
+    "host": "b",
+    "hostname": "b",
+    "port": "",
+    "pathname": "/test-a-colon-slash-slash-b.html",
+    "search": "",
+    "hash": ""
+  },
+  "Null code point in fragment",
+  {
+    "input": "http://example.org/test?a#b\u0000c",
+    "base": "about:blank",
+    "href": "http://example.org/test?a#bc",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?a",
+    "hash": "#bc"
   }
 ]
diff -Nrup a/toolkit/library/rust/Cargo.lock b/toolkit/library/rust/Cargo.lock
--- a/toolkit/library/rust/Cargo.lock	2020-02-18 02:39:43.000000000 +0300
+++ b/toolkit/library/rust/Cargo.lock	2020-02-28 20:28:57.266406014 +0300
@@ -808,7 +808,7 @@ dependencies = [
  "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "nserror 0.1.0",
  "nsstring 0.1.0",
- "url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1154,7 +1154,7 @@ dependencies = [
 
 [[package]]
 name = "url"
-version = "1.5.1"
+version = "1.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1383,7 +1383,7 @@ dependencies = [
 "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
 "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
 "checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91"
-"checksum url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb819346883532a271eb626deb43c4a1bb4c4dd47c519bd78137c3e72a4fe27"
+"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
 "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
 "checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
 "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"