Bug 1444151 - Part 5: Update rust-url to 1.7.0, r=valentin
authorNika Layzell <nika@thelayzells.com>
Fri, 09 Mar 2018 15:42:17 -0500
changeset 412658 c217f857942fe082fd3738d645b082ad1af5d7ba
parent 412657 239b9760cb6754828e4055fb1605590ba9ab3a81
child 412659 bb10a801252acf83c581f860242636a241ad0a62
push id101979
push usernika@thelayzells.com
push dateTue, 10 Apr 2018 21:51:07 +0000
treeherdermozilla-inbound@bb10a801252a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvalentin
bugs1444151
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1444151 - Part 5: Update rust-url to 1.7.0, r=valentin
Cargo.lock
third_party/rust/url/.cargo-checksum.json
third_party/rust/url/.travis.yml
third_party/rust/url/Cargo.toml
third_party/rust/url/Makefile
third_party/rust/url/github.png
third_party/rust/url/rust-url-todo
third_party/rust/url/src/host.rs
third_party/rust/url/src/lib.rs
third_party/rust/url/src/parser.rs
third_party/rust/url/src/quirks.rs
third_party/rust/url/tests/data.rs
third_party/rust/url/tests/setters_tests.json
third_party/rust/url/tests/unit.rs
third_party/rust/url/tests/urltestdata.json
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -896,17 +896,17 @@ dependencies = [
  "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
  "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "ident_case"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -1264,17 +1264,17 @@ dependencies = [
 ]
 
 [[package]]
 name = "mozurl"
 version = "0.0.1"
 dependencies = [
  "nserror 0.1.0",
  "nsstring 0.1.0",
- "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "xpcom 0.1.0",
 ]
 
 [[package]]
 name = "mozversion"
 version = "0.1.3"
 dependencies = [
  "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2189,17 +2189,17 @@ name = "unreachable"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "url"
-version = "1.6.0"
+version = "1.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2251,17 +2251,17 @@ version = "0.35.0"
 dependencies = [
  "cookie 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webidl"
 version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "lalrpop 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2658,17 +2658,17 @@ dependencies = [
 "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
 "checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f"
 "checksum unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18127285758f0e2c6cf325bb3f3d138a12fee27de4f23e146cd6a179f26c2cf3"
 "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 unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
 "checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91"
 "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
-"checksum url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa35e768d4daf1d85733418a49fb42e10d7f633e394fccab4ab7aba897053fe2"
+"checksum url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f808aadd8cfec6ef90e4a14eb46f24511824d1ac596b9682703c87056c8678b7"
 "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
 "checksum uuid 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "78c590b5bd79ed10aad8fb75f078a59d8db445af6c743e55c4a53227fc01c13f"
 "checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b"
 "checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
 "checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
 "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
 "checksum walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "63636bd0eb3d00ccb8b9036381b526efac53caf112b7783b730ab3f8e44da369"
 "checksum webidl 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dc14e4b71f94b5bb4c6d696e3b3be4d2e9ee6750a60870ecae09ff7138a131a7"
--- a/third_party/rust/url/.cargo-checksum.json
+++ b/third_party/rust/url/.cargo-checksum.json
@@ -1,1 +1,1 @@
-{"files":{".travis.yml":"890af214187ffcba4732acb2d1af30d7adb9aade0679e9fdb06baae363240b8e","Cargo.toml":"52e8e0a7014d3b0c654491184e9fc82f8c61ba7f51332e2b6a787330be42a301","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":"320418526c4564a4469581d426e7467bcefe504eecd098e1eb90a2663a75fd80","src/host.rs":"4c74946777d76e9b307604f8e24a3485fd0856b664450194e8b429e262cf410d","src/lib.rs":"894cc76c31357fb588292e990a87f4e951043e32ea3d9f38fddc145302d0b318","src/origin.rs":"6e4821eb9600a32ef54d05c8e1a7937f6d9b4dd1e3bda7f36c7988f6a2bef78b","src/parser.rs":"e379c9eb51cff977b9cef368ab64bcff7626e885b859772ab1bc76c9193e9fde","src/path_segments.rs":"7bd3142eaa568863ef44e2255c181239141f9eeee337f889b9ffaaeab4ca669d","src/quirks.rs":"fe6095104cf583053ab35d8a2a093a8581da083641e32d1c5acfe322a26c4bde","src/slicing.rs":"4e539886b23945a92094625f3e531a4bff40daa44240b5d19ee8577478c4f7fe","tests/data.rs":"50a110e475b1717fdaff6524be7d8916cb41a45461e0715f632ff54d0ce28886","tests/setters_tests.json":"ebcbdb52e9a4b5a565f8806d52ebc610d46a34df883e10b0be080d026468ff73","tests/unit.rs":"c0305ca991b2c03815aac2e69ef1c1792b087df92272a6196eb698b27625321f","tests/urltestdata.json":"df87028e1eaea4ef70cf1c1faaed2584e81a46b8b6cd90f50d47b77726ece41c"},"package":"fa35e768d4daf1d85733418a49fb42e10d7f633e394fccab4ab7aba897053fe2"}
\ No newline at end of file
+{"files":{".travis.yml":"f1183facdda0bd8d7ed7c4fed656b074f3c1dbfc53653dba99293edea0888e7a","Cargo.toml":"0c9e7c23f4216471a535938d0babb4c30595c0ed747ef208da3f042027a3b55b","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"20c7855c364d57ea4c97889a5e8d98470a9952dade37bd9248b9a54431670e5e","README.md":"eb3f4694003f408cbe3c7f3e9fbbc71241defb940cc55a816981f0f0f144c8eb","UPGRADING.md":"fbcc2d39bdf17db0745793db6626fcd5c909dddd4ce13b27566cfabece22c368","appveyor.yml":"c78486dbfbe6ebbf3d808afb9a19f7ec18c4704ce451c6305f0716999b70a1a6","docs/.nojekyll":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","docs/404.html":"f61e6271c1ea1aa113b64b356e994595fa548f0433f89948d747503ad22195cd","docs/index.html":"f61e6271c1ea1aa113b64b356e994595fa548f0433f89948d747503ad22195cd","src/encoding.rs":"f3e109ca8ec5a9130da50cdfb3003530aedb6dd5a440f0790d76b71f6981119c","src/form_urlencoded.rs":"320418526c4564a4469581d426e7467bcefe504eecd098e1eb90a2663a75fd80","src/host.rs":"66a2c0c77a8add2da16bc690fbc82b130cf1367ac655fc36990a214e193a4d6c","src/lib.rs":"899d5741dc0da32cea327f11e10bd2f83722c854f946b7201aae4f6c12edc477","src/origin.rs":"6e4821eb9600a32ef54d05c8e1a7937f6d9b4dd1e3bda7f36c7988f6a2bef78b","src/parser.rs":"91882bcf1dc87c98b2849fe2cecfcbcfa9e478dd39e07b7c029269c98e613163","src/path_segments.rs":"7bd3142eaa568863ef44e2255c181239141f9eeee337f889b9ffaaeab4ca669d","src/quirks.rs":"6cf1697bad363532cbcc60917a9b126560ac3ab3e1a77da0abcf4f2a40c8233a","src/slicing.rs":"4e539886b23945a92094625f3e531a4bff40daa44240b5d19ee8577478c4f7fe","tests/data.rs":"e95a78cadbe156597938057b7048d0d0ac4d3568ca548c0658fbea88d71f2de1","tests/setters_tests.json":"08ddaa632ad19c81e83b904bfaa94bc971f26e2bdfcef27d2f93fd033ad57340","tests/unit.rs":"fb17881a57aab4d369cdbcbb4d062083fc2b80319187fe0040891d2830de22fe","tests/urltestdata.json":"1b0c7c727d8d7e79dfb0d0aa347ff05675ddb68bc4ead38f83fd8e89bc59cc32"},"package":"f808aadd8cfec6ef90e4a14eb46f24511824d1ac596b9682703c87056c8678b7"}
\ No newline at end of file
--- a/third_party/rust/url/.travis.yml
+++ b/third_party/rust/url/.travis.yml
@@ -1,9 +1,29 @@
 language: rust
-rust:
-  - nightly
-  - beta
-  - stable
-  - 1.17.0
-script: make test
+
+jobs:
+  include:
+  - rust: 1.17.0
+    install:
+    # --precise requires Cargo.lock to already exist
+    - cargo update
+    # getopts is only used in tests. Its versions 0.2.16+ don’t build on 1.17.0
+    - cargo update -p getopts --precise 0.2.15
+    # data-url uses pub(crate) which is unstable in 1.17
+    script: cargo test --all-features -p url -p idna -p percent-encoding -p url_serde
+
+  - rust: stable
+    script: cargo test --all-features --all
+
+  - rust: beta
+    script: cargo test --all-features --all
+
+  - rust: nightly
+    script: cargo test --all-features --all
+
+  - rust: nightly
+    env: TARGET=WASM32  # For job list UI
+    install: rustup target add wasm32-unknown-unknown
+    script: cargo build --all --target=wasm32-unknown-unknown
+
 notifications:
   webhooks: http://build.servo.org:54856/travis
--- a/third_party/rust/url/Cargo.toml
+++ b/third_party/rust/url/Cargo.toml
@@ -7,68 +7,70 @@
 #
 # 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"
-version = "1.6.0"
+version = "1.7.0"
 authors = ["The rust-url developers"]
 description = "URL library for Rust, based on the WHATWG URL Standard"
 documentation = "https://docs.rs/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"]
 
 [lib]
 test = false
 
 [[test]]
 name = "unit"
 
 [[test]]
 name = "data"
 harness = false
-[dependencies.rustc-serialize]
-version = "0.3"
+[dependencies.encoding]
+version = "0.2"
 optional = true
 
-[dependencies.matches]
-version = "0.1"
-
-[dependencies.serde]
-version = ">=0.6.1, <0.9"
-optional = true
-
-[dependencies.percent-encoding]
-version = "1.0.0"
-
-[dependencies.encoding]
-version = "0.2"
+[dependencies.heapsize]
+version = ">=0.4.1, <0.5"
 optional = true
 
 [dependencies.idna]
 version = "0.1.0"
 
-[dependencies.heapsize]
-version = ">=0.4.1, <0.5"
+[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.rustc-serialize]
+version = "0.3"
+
 [dev-dependencies.rustc-test]
 version = "0.2"
 
-[dev-dependencies.rustc-serialize]
-version = "0.3"
-
 [dev-dependencies.serde_json]
 version = ">=0.6.1, <0.9"
 
 [features]
 heap_size = ["heapsize"]
 query_encoding = ["encoding"]
+[badges.appveyor]
+repository = "Manishearth/rust-url"
+
 [badges.travis-ci]
 repository = "servo/rust-url"
-
-[badges.appveyor]
-repository = "Manishearth/rust-url"
deleted file mode 100644
--- a/third_party/rust/url/Makefile
+++ /dev/null
@@ -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
deleted file mode 100644
index b2c327097e33667628759b5b8194cbe364f471fc..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/third_party/rust/url/rust-url-todo
+++ /dev/null
@@ -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");
-}
--- a/third_party/rust/url/src/host.rs
+++ b/third_party/rust/url/src/host.rs
@@ -8,17 +8,17 @@
 
 #[cfg(feature = "heapsize")] use heapsize::HeapSizeOf;
 use std::cmp;
 use std::fmt::{self, Formatter};
 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)]
 pub enum HostInternal {
     None,
     Domain,
     Ipv4(Ipv4Addr),
     Ipv6(Ipv6Addr),
@@ -68,17 +68,19 @@ impl<S> From<Host<S>> for HostInternal {
         }
     }
 }
 
 /// The host name of an URL.
 #[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.
     /// `Url::host_str` returns the serialization of this address,
     /// as four decimal integers separated by `.` dots.
     Ipv4(Ipv4Addr),
 
     /// An IPv6 address.
@@ -153,16 +155,33 @@ impl Host<String> {
             return Err(ParseError::InvalidDomainCharacter)
         }
         if let Some(address) = parse_ipv4addr(&domain)? {
             Ok(Host::Ipv4(address))
         } else {
             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> {
     fn fmt(&self, f: &mut Formatter) -> fmt::Result {
         match *self {
             Host::Domain(ref domain) => domain.as_ref().fmt(f),
             Host::Ipv4(ref addr) => addr.fmt(f),
             Host::Ipv6(ref addr) => {
@@ -305,33 +324,49 @@ fn longest_zero_sequence(pieces: &[u16; 
     if longest_length < 2 {
         (-1, -2)
     } else {
         (longest, longest + longest_length)
     }
 }
 
 /// <https://url.spec.whatwg.org/#ipv4-number-parser>
-fn parse_ipv4number(mut input: &str) -> Result<u32, ()> {
+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..];
         r = 16;
     } else if input.len() >= 2 && input.starts_with('0') {
         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>
 fn parse_ipv4addr(input: &str) -> ParseResult<Option<Ipv4Addr>> {
     if input.is_empty() {
         return Ok(None)
@@ -339,25 +374,29 @@ fn parse_ipv4addr(input: &str) -> ParseR
     let mut parts: Vec<&str> = input.split('.').collect();
     if parts.last() == Some(&"") {
         parts.pop();
     }
     if parts.len() > 4 {
         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())
     if ipv4 > u32::max_value() >> (8 * numbers.len() as u32)  {
         return Err(ParseError::InvalidIpv4Address);
     }
     if numbers.iter().any(|x| *x > 255) {
         return Err(ParseError::InvalidIpv4Address);
--- a/third_party/rust/url/src/lib.rs
+++ b/third_party/rust/url/src/lib.rs
@@ -99,30 +99,30 @@ use url::Url;
 let this_document = Url::parse("http://servo.github.io/rust-url/url/index.html")?;
 let css_url = this_document.join("../main.css")?;
 assert_eq!(css_url.as_str(), "http://servo.github.io/rust-url/main.css");
 # Ok(())
 # }
 # run().unwrap();
 */
 
-#![doc(html_root_url = "https://docs.rs/url/1.6.0")]
+#![doc(html_root_url = "https://docs.rs/url/1.7.0")]
 
 #[cfg(feature="rustc-serialize")] extern crate rustc_serialize;
 #[macro_use] extern crate matches;
 #[cfg(feature="serde")] extern crate serde;
 #[cfg(feature="heapsize")] #[macro_use] extern crate heapsize;
 
 pub extern crate idna;
 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;
 use std::cmp;
 #[cfg(feature = "serde")] use std::error::Error;
 use std::fmt::{self, Write, Debug, Formatter};
 use std::hash;
 use std::io;
@@ -130,17 +130,17 @@ use std::mem;
 use std::net::{ToSocketAddrs, IpAddr};
 use std::ops::{Range, RangeFrom, RangeTo};
 use std::path::{Path, PathBuf};
 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;
 mod host;
 mod origin;
 mod path_segments;
 mod parser;
 mod slicing;
@@ -181,17 +181,17 @@ impl HeapSizeOf for Url {
     }
 }
 
 /// Full configuration for the URL parser.
 #[derive(Copy, Clone)]
 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> {
     /// Change the base URL
     pub fn base_url(mut self, new: Option<&'a Url>) -> Self {
         self.base_url = new;
         self
     }
@@ -204,41 +204,80 @@ impl<'a> ParseOptions<'a> {
     /// This method is only available if the `query_encoding`
     /// [feature](http://doc.crates.io/manifest.html#the-features-section]) is enabled.
     #[cfg(feature = "query_encoding")]
     pub fn encoding_override(mut self, new: Option<encoding::EncodingRef>) -> Self {
         self.encoding_override = EncodingOverride::from_opt_encoding(new).to_output_encoding();
         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
     }
 
     /// Parse an URL string with the configuration so far.
     pub fn parse(self, input: &str) -> Result<Url, ::ParseError> {
         Parser {
             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)
     }
 }
 
 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)
     }
 }
 
 impl Url {
     /// Parse an absolute URL from a string.
     ///
     /// # Examples
     ///
@@ -247,17 +286,17 @@ impl Url {
     /// # use url::ParseError;
     ///
     /// # fn run() -> Result<(), ParseError> {
     /// let url = Url::parse("https://example.net")?;
     /// # Ok(())
     /// # }
     /// # 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> {
@@ -310,33 +349,33 @@ impl Url {
     /// Without it, the last path component is considered to be a “file” name
     /// to be removed to get at the “directory” that is used as the base:
     ///
     /// # Examples
     ///
     /// ```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")?;
     /// assert_eq!(url.as_str(), "https://example.net/a/c.png");  // Not /a/b.html/c.png
     ///
     /// let base = Url::parse("https://example.net/a/b/")?;
     /// let url = base.join("c.png")?;
     /// assert_eq!(url.as_str(), "https://example.net/a/b/c.png");
     /// # Ok(())
     /// # }
     /// # run().unwrap();
     /// ```
     ///
     /// # Errors
     ///
-    /// If the function can not parse an URL from the given string 
+    /// 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)
     }
 
@@ -358,17 +397,17 @@ impl Url {
     /// # Ok(())
     /// # }
     /// # run().unwrap();
     /// ```
     pub fn options<'a>() -> ParseOptions<'a> {
         ParseOptions {
             base_url: None,
             encoding_override: EncodingOverride::utf8(),
-            log_syntax_violation: None,
+            violation_fn: ViolationFn::NoOp,
         }
     }
 
     /// Return the serialization of this URL.
     ///
     /// This is fast since that serialization is already stored in the `Url` struct.
     ///
     /// # Examples
@@ -1172,21 +1211,21 @@ impl Url {
     /// use url::Url;
     /// # use url::ParseError;
     ///
     /// # fn run() -> Result<(), ParseError> {
     /// let mut url = Url::parse("https://example.com/data.csv")?;
     /// 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(())
     /// # }
     /// # run().unwrap();
     /// ```
     pub fn set_fragment(&mut self, fragment: Option<&str>) {
         // Remove any previous fragment
         if let Some(start) = self.fragment_start {
@@ -1229,17 +1268,17 @@ impl Url {
     /// use url::Url;
     /// # use url::ParseError;
     ///
     /// # fn run() -> Result<(), ParseError> {
     /// let mut url = Url::parse("https://example.com/products")?;
     /// 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(())
     /// # }
     /// # run().unwrap();
     /// ```
     pub fn set_query(&mut self, query: Option<&str>) {
         let fragment = self.take_fragment();
 
@@ -1325,22 +1364,22 @@ impl Url {
     ///
     /// ```rust
     /// use url::Url;
     /// # use url::ParseError;
     ///
     /// # 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(())
     /// # }
     /// # run().unwrap();
     /// ```
     pub fn set_path(&mut self, mut path: &str) {
         let after_path = self.take_after_path();
         let old_after_path_pos = to_u32(self.serialization.len()).unwrap();
@@ -1422,17 +1461,18 @@ impl Url {
     ///
     /// let result = url.set_port(None);
     /// assert!(result.is_err());
     /// # Ok(())
     /// # }
     /// # 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()) {
             port = None
         }
         self.set_port_internal(port);
         Ok(())
     }
@@ -1490,49 +1530,49 @@ impl Url {
     /// # run().unwrap();
     /// ```
     ///
     /// Remove host:
     ///
     /// ```
     /// use url::Url;
     /// # use url::ParseError;
-    /// 
+    ///
     /// # fn run() -> Result<(), ParseError> {
     /// let mut url = Url::parse("foo://example.net")?;
     /// let result = url.set_host(None);
     /// assert!(result.is_ok());
     /// assert_eq!(url.as_str(), "foo:/");
     /// # Ok(())
     /// # }
     /// # run().unwrap();
     /// ```
     ///
     /// Cannot remove host for 'special' schemes (e.g. `http`):
     ///
     /// ```
     /// use url::Url;
     /// # use url::ParseError;
-    /// 
+    ///
     /// # fn run() -> Result<(), ParseError> {
     /// let mut url = Url::parse("https://example.net")?;
     /// let result = url.set_host(None);
     /// assert!(result.is_err());
     /// assert_eq!(url.as_str(), "https://example.net/");
     /// # Ok(())
     /// # }
     /// # run().unwrap();
     /// ```
     ///
     /// Cannot change or remove host for cannot-be-a-base URLs:
     ///
     /// ```
     /// use url::Url;
     /// # use url::ParseError;
-    /// 
+    ///
     /// # fn run() -> Result<(), ParseError> {
     /// let mut url = Url::parse("mailto:rms@example.net")?;
     ///
     /// let result = url.set_host(Some("rust-lang.org"));
     /// assert!(result.is_err());
     /// assert_eq!(url.as_str(), "mailto:rms@example.net");
     ///
     /// let result = url.set_host(None);
@@ -1553,17 +1593,21 @@ impl Url {
         if self.cannot_be_a_base() {
             return Err(ParseError::SetHostOnCannotBeABaseUrl)
         }
 
         if let Some(host) = host {
             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)
             }
             debug_assert!(self.byte_at(self.scheme_end) == b':');
             debug_assert!(self.byte_at(self.path_start) == b'/');
             let new_path_start = self.scheme_end + 1;
             self.serialization.drain(new_path_start as usize..self.path_start as usize);
@@ -1686,17 +1730,18 @@ impl Url {
     /// let result = url.set_password(Some("secret2"));
     /// assert!(result.is_ok());
     /// assert_eq!(url.password(), Some("secret2"));
     /// # Ok(())
     /// # }
     /// # 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 {
             let host_and_after = self.slice(self.host_start..).to_owned();
             self.serialization.truncate(self.username_end as usize);
             self.serialization.push(':');
             self.serialization.extend(utf8_percent_encode(password, USERINFO_ENCODE_SET));
             self.serialization.push('@');
@@ -1767,17 +1812,18 @@ impl Url {
     /// assert!(result.is_ok());
     /// assert_eq!(url.username(), "user1");
     /// 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;
         debug_assert!(self.slice(self.scheme_end..username_start) == "://");
         if self.slice(username_start..self.username_end) == username {
             return Ok(())
         }
         let after_username = self.slice(self.username_end..).to_owned();
@@ -1826,50 +1872,50 @@ impl Url {
     ///
     /// # Examples
     ///
     /// Change the URL’s scheme from `https` to `foo`:
     ///
     /// ```
     /// use url::Url;
     /// # use url::ParseError;
-    /// 
+    ///
     /// # fn run() -> Result<(), ParseError> {
     /// let mut url = Url::parse("https://example.net")?;
     /// let result = url.set_scheme("foo");
     /// assert_eq!(url.as_str(), "foo://example.net/");
     /// assert!(result.is_ok());
     /// # Ok(())
     /// # }
     /// # run().unwrap();
     /// ```
     ///
     ///
     /// Cannot change URL’s scheme from `https` to `foõ`:
     ///
     /// ```
     /// use url::Url;
     /// # use url::ParseError;
-    /// 
+    ///
     /// # fn run() -> Result<(), ParseError> {
     /// let mut url = Url::parse("https://example.net")?;
     /// let result = url.set_scheme("foõ");
     /// assert_eq!(url.as_str(), "https://example.net/");
     /// assert!(result.is_err());
     /// # Ok(())
     /// # }
     /// # run().unwrap();
     /// ```
     ///
     /// Cannot change URL’s scheme from `mailto` (cannot-be-a-base) to `https`:
     ///
     /// ```
     /// 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");
     /// assert_eq!(url.as_str(), "mailto:rms@example.net");
     /// assert!(result.is_err());
     /// # Ok(())
     /// # }
     /// # run().unwrap();
@@ -1908,31 +1954,32 @@ impl Url {
     ///
     /// # Examples
     ///
     /// On Unix-like platforms:
     ///
     /// ```
     /// # 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");
     ///
     /// let url = Url::from_file_path("../foo.txt");
     /// assert!(url.is_err());
     ///
     /// let url = Url::from_file_path("https://google.com/");
     /// assert!(url.is_err());
     /// # Ok(())
     /// # }
     /// # 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;
         let (host_end, host) = path_to_file_url_segments(path.as_ref(), &mut serialization)?;
         Ok(Url {
             serialization: serialization,
             scheme_end: "file".len() as u32,
             username_end: host_start,
@@ -1958,16 +2005,17 @@ impl Url {
     ///
     /// * `"index.html"` parsed with `Url::from_directory_path(Path::new("/var/www"))`
     ///   as the base URL is `file:///var/www/index.html`
     /// * `"index.html"` parsed with `Url::from_file_path(Path::new("/var/www"))`
     ///   as the base URL is `file:///var/index.html`, which might not be what was intended.
     ///
     /// 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('/') {
             url.serialization.push('/')
         }
         Ok(url)
     }
 
@@ -2039,16 +2087,17 @@ impl Url {
     /// ```
     ///
     /// Returns `Err` if the host is neither empty nor `"localhost"` (except on Windows, where
     /// `file:` URLs may have a non-local host),
     /// or if `Path::new_opt()` returns `None`.
     /// (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() {
                 None | Some(Host::Domain("localhost")) => None,
                 Some(_) if cfg!(windows) && self.scheme() == "file" => {
                     Some(&self.serialization[self.host_start as usize .. self.host_end as usize])
                 },
                 _ => return Err(())
@@ -2285,16 +2334,17 @@ fn path_to_file_url_segments_windows(pat
         // FIXME: somehow work with non-unicode?
         let component = component.as_os_str().to_str().ok_or(())?;
         serialization.push('/');
         serialization.extend(percent_encode(component.as_bytes(), PATH_SEGMENT_ENCODE_SET));
     }
     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;
     use std::os::unix::prelude::OsStrExt;
     use std::path::PathBuf;
 
     if host.is_some() {
         return Err(());
--- a/third_party/rust/url/src/parser.rs
+++ b/third_party/rust/url/src/parser.rs
@@ -65,16 +65,64 @@ impl fmt::Display for ParseError {
         self.description().fmt(fmt)
     }
 }
 
 impl From<::idna::uts46::Errors> for ParseError {
     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,
     SpecialNotFile,
     NotSpecial,
 }
 
 impl SchemeType {
@@ -107,28 +155,27 @@ pub fn default_port(scheme: &str) -> Opt
 
 #[derive(Clone)]
 pub struct Input<'i> {
     chars: str::Chars<'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() }
     }
 
     #[inline]
     pub fn is_empty(&self) -> bool {
         self.clone().next().is_none()
@@ -211,60 +258,94 @@ impl<F: FnMut(char) -> bool> Pattern for
 
 impl<'i> Iterator for Input<'i> {
     type Item = char;
     fn next(&mut self) -> Option<char> {
         self.chars.by_ref().find(|&c| !matches!(c, '\t' | '\n' | '\r'))
     }
 }
 
+/// 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,
 }
 
 #[derive(PartialEq, Eq, Copy, Clone)]
 pub enum Context {
     UrlParser,
     Setter,
     PathSegmentSetter,
 }
 
 impl<'a> Parser<'a> {
     pub fn for_setter(serialization: String) -> Parser<'a> {
         Parser {
             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)
         }
 
         // No-scheme state
         if let Some(base_url) = self.base_url {
             if input.starts_with('#') {
                 self.fragment_only(base_url, input)
@@ -305,22 +386,23 @@ impl<'a> Parser<'a> {
             Ok(input)
         } else {
             self.serialization.clear();
             Err(())
         }
     }
 
     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 }
                 });
                 self.serialization.clear();
                 self.parse_file(input, base_file_url)
             }
             SchemeType::SpecialNotFile => {
                 // special relative or authority state
@@ -330,17 +412,17 @@ impl<'a> Parser<'a> {
                             base_url.scheme() == &self.serialization[..scheme_end as usize] {
                         // "Cannot-be-a-base" URLs only happen with "not special" schemes.
                         debug_assert!(!base_url.cannot_be_a_base());
                         self.serialization.clear();
                         return self.parse_relative(input, scheme_type, base_url)
                     }
                 }
                 // special authority slashes state
-                self.syntax_violation_if("expected //", || {
+                self.violation_fn.call_if(ExpectedDoubleSlash, || {
                     input.clone().take_while(|&c| matches!(c, '/' | '\\'))
                     .collect::<String>() != "//"
                 });
                 self.after_double_slash(remaining, scheme_type, scheme_end)
             }
             SchemeType::NotSpecial => self.parse_non_special(input, scheme_type, scheme_end)
         }
     }
@@ -366,16 +448,17 @@ impl<'a> Parser<'a> {
         } else {
             self.parse_cannot_be_a_base_path(input)
         };
         self.with_query_and_fragment(scheme_end, username_end, host_start,
                                      host_end, host, port, path_start, remaining)
     }
 
     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();
         match first_char {
             None => {
                 if let Some(base_url) = base_file_url {
                     // Copy everything except the fragment
                     let before_fragment = match base_url.fragment_start {
@@ -446,53 +529,60 @@ impl<'a> Parser<'a> {
             Some('#') => {
                 if let Some(base_url) = base_file_url {
                     self.fragment_only(base_url, input)
                 } else {
                     self.serialization.push_str("file:///");
                     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,
                         scheme_end: scheme_end,
                         username_end: path_start,
                         host_start: path_start,
                         host_end: path_start,
                         host: HostInternal::None,
                         port: None,
                         path_start: path_start,
                         query_start: None,
                         fragment_start: Some(fragment_start),
                     })
                 }
             }
             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)
                     } else {
                         let path_start = self.serialization.len();
                         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 {
                         serialization: self.serialization,
                         scheme_end: scheme_end,
                         username_end: host_start,
                         host_start: host_start,
                         host_end: host_end,
@@ -611,17 +701,17 @@ impl<'a> Parser<'a> {
                     fragment_start: fragment_start,
                     ..*base_url
                 })
             },
             Some('#') => self.fragment_only(base_url, input),
             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>() != "//"
                     });
                     let scheme_end = base_url.scheme_end;
                     debug_assert!(base_url.byte_at(scheme_end) == b':');
                     self.serialization.push_str(base_url.slice(..scheme_end + 1));
                     return self.after_double_slash(remaining, scheme_type, scheme_end)
                 }
@@ -675,54 +765,63 @@ impl<'a> Parser<'a> {
                           -> ParseResult<(u32, Input<'i>)> {
         let mut last_at = None;
         let mut remaining = input.clone();
         let mut char_count = 0;
         while let Some(c) = remaining.next() {
             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 authentication information (username or password) \
-                            in an URL is not recommended")
+                        self.violation_fn.call(SyntaxViolation::EmbeddedCredentials)
                     }
                     last_at = Some((char_count, remaining.clone()))
                 },
                 '/' | '?' | '#' => break,
                 '\\' if scheme_type.is_special() => break,
                 _ => (),
             }
             char_count += 1;
         }
         let (mut userinfo_char_count, remaining) = match last_at {
             None => return Ok((to_u32(self.serialization.len())?, input)),
             Some((0, remaining)) => return Ok((to_u32(self.serialization.len())?, remaining)),
             Some(x) => x
         };
 
         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));
             }
         }
         let username_end = match username_end {
             Some(i) => i,
             None => to_u32(self.serialization.len())?,
         };
-        self.serialization.push('@');
+        if has_username || has_password {
+            self.serialization.push('@');
+        }
         Ok((username_end, remaining))
     }
 
     fn parse_host_and_port<'i>(&mut self, input: Input<'i>,
                                    scheme_end: u32, scheme_type: SchemeType)
                                    -> ParseResult<(u32, HostInternal, Option<u16>, Input<'i>)> {
         let (host, remaining) = Parser::parse_host(input, scheme_type)?;
         write!(&mut self.serialization, "{}", host).unwrap();
@@ -778,16 +877,20 @@ impl<'a> Parser<'a> {
             } else {
                 for _ in host_input {}
                 host_str = &input_str[..bytes]
             }
         }
         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))
     }
 
     pub fn parse_file_host<'i>(&mut self, input: Input<'i>)
                                -> ParseResult<(bool, HostInternal, Input<'i>)> {
         // Undo the Input abstraction here to avoid allocating in the common case
         // where the host part of the input does not contain any tab or newline
@@ -862,17 +965,17 @@ impl<'a> Parser<'a> {
 
     pub fn parse_path_start<'i>(&mut self, scheme_type: SchemeType, has_host: &mut bool,
                             mut input: Input<'i>)
                             -> Input<'i> {
         // Path start state
         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
             },
             _ => {}
         }
         let path_start = self.serialization.len();
         self.serialization.push('/');
         self.parse_path(scheme_type, has_host, path_start, input)
     }
@@ -890,67 +993,58 @@ impl<'a> Parser<'a> {
                 let (c, utf8_c) = if let Some(x) = input.next_utf8() { x } else { break };
                 match c {
                     '/' if self.context != Context::PathSegmentSetter => {
                         ends_with_slash = true;
                         break
                     },
                     '\\' if self.context != Context::PathSegmentSetter &&
                             scheme_type.is_special() => {
-                        self.syntax_violation("backslash");
+                        self.violation_fn.call(SyntaxViolation::Backslash);
                         ends_with_slash = true;
                         break
                     },
                     '?' | '#' if self.context == Context::UrlParser => {
                         input = input_before_c;
                         break
                     },
                     _ => {
                         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));
                         } else {
                             self.serialization.extend(utf8_percent_encode(
                                 utf8_c, DEFAULT_ENCODE_SET));
                         }
                     }
                 }
             }
             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);
                     if !self.serialization[path_start..].ends_with('/') {
                         self.serialization.push('/')
                     }
                 },
-                "." => {
+                "." | "%2e" | "%2E" => {
                     self.serialization.truncate(segment_start);
                 },
                 _ => {
                     if scheme_type.is_file() && is_windows_drive_letter(
                         &self.serialization[path_start + 1..]
                     ) {
                         if self.serialization.ends_with('|') {
                             self.serialization.pop();
                             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
                         }
                     }
                     if ends_with_slash {
                         self.serialization.push('/')
                     }
                 }
             }
@@ -1082,35 +1176,36 @@ impl<'a> Parser<'a> {
             fragment_start: Some(to_u32(before_fragment.len())?),
             ..*base_url
         })
     }
 
     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,
                                                               SIMPLE_ENCODE_SET));
             }
         }
     }
 
     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)
             }
         }
     }
 }
 
 #[inline]
 fn is_ascii_hex_digit(c: char) -> bool {
     matches!(c, 'a'...'f' | 'A'...'F' | '0'...'9')
--- a/third_party/rust/url/src/quirks.rs
+++ b/third_party/rust/url/src/quirks.rs
@@ -147,17 +147,17 @@ pub fn port(url: &Url) -> &str {
 }
 
 /// Setter for https://url.spec.whatwg.org/#dom-url-port
 pub fn set_port(url: &mut Url, new_port: &str) -> Result<(), ()> {
     let result;
     {
         // 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)
     }
     if let Ok((new_port, _remaining)) = result {
         url.set_port_internal(new_port);
         Ok(())
     } else {
--- a/third_party/rust/url/tests/data.rs
+++ b/third_party/rust/url/tests/data.rs
@@ -24,16 +24,17 @@ fn check_invariants(url: &Url) {
         assert_eq!(url, &new_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) {
         (Ok(url), Ok(expected)) => (url, expected),
         (Err(_), Err(())) => return,
         (Err(message), Ok(_)) => panic!("Error parsing URL {:?}: {}", input, message),
         (Ok(_), Err(())) => panic!("Expected a parse error for URL {:?}", input),
     };
--- a/third_party/rust/url/tests/setters_tests.json
+++ b/third_party/rust/url/tests/setters_tests.json
@@ -97,16 +97,41 @@
             "href": "a://example.net",
             "new_value": "bé",
             "expected": {
                 "href": "a://example.net/",
                 "protocol": "a:"
             }
         },
         {
+            "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",
             "expected": {
                 "href": "b://example.net/",
                 "protocol": "b:"
             }
         },
@@ -171,16 +196,24 @@
             "href": "mailto:you@example.net",
             "new_value": "me",
             "expected": {
                 "href": "mailto:you@example.net",
                 "username": ""
             }
         },
         {
+            "href": "javascript:alert(1)",
+            "new_value": "wario",
+            "expected": {
+                "href": "javascript:alert(1)",
+                "username": ""
+            }
+        },
+        {
             "href": "http://example.net",
             "new_value": "me",
             "expected": {
                 "href": "http://me@example.net/",
                 "username": "me"
             }
         },
         {
@@ -219,16 +252,40 @@
         {
             "comment": "Bytes already percent-encoded are left as-is.",
             "href": "http://example.net",
             "new_value": "%c3%89té",
             "expected": {
                 "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": [
         {
             "comment": "No host means no password",
             "href": "file:///home/me/index.html",
             "new_value": "secret",
             "expected": {
@@ -298,20 +355,116 @@
         {
             "comment": "Bytes already percent-encoded are left as-is.",
             "href": "http://example.net",
             "new_value": "%c3%89té",
             "expected": {
                 "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",
             "expected": {
                 "href": "mailto:me@example.net",
                 "host": ""
             }
         },
@@ -379,25 +532,16 @@
             "href": "a:/foo",
             "new_value": "example.net",
             "expected": {
                 "href": "a://example.net/foo",
                 "host": "example.net"
             }
         },
         {
-            "comment": "Path-only URLs can gain a host",
-            "href": "a:/foo",
-            "new_value": "example.net",
-            "expected": {
-                "href": "a://example.net/foo",
-                "host": "example.net"
-            }
-        },
-        {
             "comment": "IPv4 address syntax is normalized",
             "href": "http://example.net",
             "new_value": "0x7F000001:8080",
             "expected": {
                 "href": "http://127.0.0.1:8080/",
                 "host": "127.0.0.1:8080",
                 "hostname": "127.0.0.1",
                 "port": "8080"
@@ -531,17 +675,17 @@
             "expected": {
                 "href": "http://example.com:8080/path",
                 "host": "example.com:8080",
                 "hostname": "example.com",
                 "port": "8080"
             }
         },
         {
-            "comment": "\\ is not a delimiter for non-special schemes, and it’s invalid in a domain",
+            "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": {
                 "href": "view-source+http://example.net/path",
                 "host": "example.net",
                 "hostname": "example.net",
                 "port": ""
             }
@@ -595,20 +739,129 @@
             "href": "http://example.net/path",
             "new_value": "example.com:65536",
             "expected": {
                 "href": "http://example.com/path",
                 "host": "example.com",
                 "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",
             "expected": {
                 "href": "mailto:me@example.net",
                 "host": ""
             }
         },
@@ -654,25 +907,16 @@
             "href": "a:/foo",
             "new_value": "example.net",
             "expected": {
                 "href": "a://example.net/foo",
                 "host": "example.net"
             }
         },
         {
-            "comment": "Path-only URLs can gain a host",
-            "href": "a:/foo",
-            "new_value": "example.net",
-            "expected": {
-                "href": "a://example.net/foo",
-                "host": "example.net"
-            }
-        },
-        {
             "comment": "IPv4 address syntax is normalized",
             "href": "http://example.net:8080",
             "new_value": "0x7F000001",
             "expected": {
                 "href": "http://127.0.0.1:8080/",
                 "host": "127.0.0.1:8080",
                 "hostname": "127.0.0.1",
                 "port": "8080"
@@ -751,40 +995,86 @@
             "expected": {
                 "href": "http://example.com/path",
                 "host": "example.com",
                 "hostname": "example.com",
                 "port": ""
             }
         },
         {
-            "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": {
                 "href": "view-source+http://example.net/path",
                 "host": "example.net",
                 "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": [
         {
             "href": "http://example.net",
             "new_value": "8080",
             "expected": {
                 "href": "http://example.net:8080/",
                 "host": "example.net:8080",
                 "hostname": "example.net",
                 "port": "8080"
             }
         },
         {
-            "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": {
                 "href": "http://example.net/",
                 "host": "example.net",
                 "hostname": "example.net",
                 "port": ""
             }
@@ -915,16 +1205,75 @@
             "href": "http://example.net:8080/path",
             "new_value": "65536",
             "expected": {
                 "href": "http://example.net:8080/path",
                 "host": "example.net:8080",
                 "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": [
         {
             "comment": "Cannot-be-a-base don’t have a path",
             "href": "mailto:me@example.net",
             "new_value": "/foo",
             "expected": {
@@ -965,36 +1314,72 @@
                 "pathname": "/a/c"
             }
         },
         {
             "comment": "\\ is *not* a segment delimiter for non-'special' URLs",
             "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"
             }
         },
         {
             "comment": "UTF-8 percent encoding with the default encode set. Tabs and newlines are removed. Leading or training C0 controls and space are removed.",
             "href": "a:/",
             "new_value": "\u0000\u0001\t\n\r\u001f !\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé",
             "expected": {
                 "href": "a:/!%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9",
                 "pathname": "/!%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9"
             }
         },
         {
-            "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"
             }
         }
     ],
     "search": [
         {
             "href": "https://example.net#nav",
             "new_value": "lang=fr",
             "expected": {
--- a/third_party/rust/url/tests/unit.rs
+++ b/third_party/rust/url/tests/unit.rs
@@ -8,16 +8,17 @@
 
 //! Unit tests
 
 #[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};
 
 #[test]
 fn size() {
     use std::mem::size_of;
     assert_eq!(size_of::<Url>(), size_of::<Option<Url>>());
@@ -217,17 +218,17 @@ fn test_idna() {
 }
 
 #[test]
 fn test_serialization() {
     let data = [
         ("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"),
         ("http://noslashquery.com/path?q=something", "http://noslashquery.com/path?q=something")
     ];
     for &(input, result) in &data {
         let url = Url::parse(input).unwrap();
         assert_eq!(url.as_str(), result);
@@ -291,32 +292,16 @@ fn host_and_port_display() {
                 port: 1337
             })
         ,
         "[2001:db8:85a3:8d3:1319:8a2e:370:7344]:1337"
     )
 }
 
 #[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();
     url.set_scheme("https").unwrap();
     assert_eq!(url.port(), None);
     assert_eq!(url.port_or_known_default(), Some(443));
     url.check_invariants().unwrap();
 }
@@ -377,16 +362,21 @@ fn test_set_host() {
     assert!(url.set_host(None).is_err());
     assert_eq!(url.as_str(), "https://foo.com/hello");
     assert!(url.set_host(Some("")).is_err());
     assert_eq!(url.as_str(), "https://foo.com/hello");
 
     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]
 // https://github.com/servo/rust-url/issues/166
 fn test_leading_dots() {
     assert_eq!(Host::parse(".org").unwrap(), Host::Domain(".org".to_owned()));
     assert_eq!(Url::parse("file://./foo").unwrap().domain(), Some("."));
 }
@@ -483,8 +473,73 @@ fn test_windows_unc_path() {
     // Another way to write these:
     let url = Url::from_file_path(Path::new(r"\\?\UNC\host\share\path\file.txt")).unwrap();
     assert_eq!(url.as_str(), "file://host/share/path/file.txt");
 
     // Paths starting with "\\.\" (Local Device Paths) are intentionally not supported.
     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));
+}
--- a/third_party/rust/url/tests/urltestdata.json
+++ b/third_party/rust/url/tests/urltestdata.json
@@ -26,16 +26,76 @@
     "host": "foo:21",
     "hostname": "foo",
     "port": "21",
     "pathname": "/bar;par",
     "search": "?b",
     "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",
     "origin": "http://example.org",
     "protocol": "http:",
     "username": "",
     "password": "",
     "host": "example.org",
@@ -101,16 +161,30 @@
     "host": "f:21",
     "hostname": "f",
     "port": "21",
     "pathname": "/%20b%20",
     "search": "?%20d%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",
     "origin": "http://f",
     "protocol": "http:",
     "username": "",
     "password": "",
     "host": "f",
@@ -196,16 +270,21 @@
     "failure": true
   },
   {
     "input": "http://f:999999/c",
     "base": "http://example.org/foo/bar",
     "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
   },
   {
     "input": "",
     "base": "http://example.org/foo/bar",
     "href": "http://example.org/foo/bar",
@@ -955,16 +1034,36 @@
     "host": "",
     "hostname": "",
     "port": "",
     "pathname": "/example.com/",
     "search": "",
     "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/",
     "origin": "null",
     "protocol": "ftps:",
     "username": "",
     "password": "",
     "host": "",
@@ -1780,40 +1879,40 @@
     "port": "",
     "pathname": "/foo/",
     "search": "",
     "hash": ""
   },
   {
     "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": "",
     "password": "",
     "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": "",
     "password": "",
     "host": "example.com",
     "hostname": "example.com",
     "port": "",
-    "pathname": "/..bar",
+    "pathname": "/%2e.bar",
     "search": "",
     "hash": ""
   },
   {
     "input": "http://example.com////../..",
     "base": "about:blank",
     "href": "http://example.com//",
     "origin": "http://example.com",
@@ -2184,21 +2283,16 @@
     "host": "",
     "hostname": "",
     "port": "",
     "pathname": "test",
     "search": "",
     "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/",
     "origin": "http://www.google.com",
     "protocol": "http:",
     "username": "",
     "password": "",
     "host": "www.google.com",
@@ -2221,25 +2315,25 @@
     "port": "",
     "pathname": "/",
     "search": "",
     "hash": ""
   },
   {
     "input": "http://www/foo%2Ehtml",
     "base": "about:blank",
-    "href": "http://www/foo.html",
+    "href": "http://www/foo%2Ehtml",
     "origin": "http://www",
     "protocol": "http:",
     "username": "",
     "password": "",
     "host": "www",
     "hostname": "www",
     "port": "",
-    "pathname": "/foo.html",
+    "pathname": "/foo%2Ehtml",
     "search": "",
     "hash": ""
   },
   {
     "input": "http://www/foo/%2E/html",
     "base": "about:blank",
     "href": "http://www/foo/html",
     "origin": "http://www",
@@ -3091,47 +3185,47 @@
   {
     "input": "http::@/www.example.com",
     "base": "about:blank",
     "failure": true
   },
   {
     "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",
     "password": "",
     "host": "www.example.com",
     "hostname": "www.example.com",
     "port": "",
     "pathname": "/",
     "search": "",
     "hash": ""
   },
   {
     "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",
     "password": "",
     "host": "www.example.com",
     "hostname": "www.example.com",
     "port": "",
     "pathname": "/",
     "search": "",
     "hash": ""
   },
   {
     "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",
     "password": "",
     "host": "www.example.com",
     "hostname": "www.example.com",
     "port": "",
     "pathname": "/",
@@ -3166,17 +3260,17 @@
   {
     "input": "http://@:www.example.com",
     "base": "about:blank",
     "failure": true
   },
   {
     "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": "",
     "password": "",
     "host": "www.example.com",
     "hostname": "www.example.com",
     "port": "",
     "pathname": "/",
@@ -3460,16 +3554,32 @@
     "password": "",
     "host": "googoo.com",
     "hostname": "googoo.com",
     "port": "",
     "pathname": "/",
     "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",
     "base": "http://other.com/",
     "href": "http://www.foo.bar.com/",
     "origin": "http://www.foo.bar.com",
     "protocol": "http:",
     "username": "",
@@ -3488,16 +3598,42 @@
     "failure": true
   },
   "This is the same as previous but escaped",
   {
     "input": "http://%ef%b7%90zyx.com",
     "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",
     "base": "http://other.com/",
     "href": "http://go.com/",
     "origin": "http://go.com",
     "protocol": "http:",
     "username": "",
@@ -3542,16 +3678,46 @@
     "password": "",
     "host": "xn--6qqa088eba",
     "hostname": "xn--6qqa088eba",
     "port": "",
     "pathname": "/",
     "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",
     "base": "http://other.com/",
     "failure": true
   },
   "If we get an invalid character that has been escaped.",
   {
@@ -3595,46 +3761,102 @@
     "search": "",
     "hash": ""
   },
   {
     "input": "http://192.168.0.257",
     "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",
     "base": "http://other.com/",
     "href": "http://192.168.0.1/",
     "origin": "http://192.168.0.1",
     "protocol": "http:",
     "username": "",
     "password": "",
     "host": "192.168.0.1",
     "hostname": "192.168.0.1",
     "port": "",
     "pathname": "/",
     "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
@@ -4191,32 +4413,101 @@
     "password": "",
     "host": "",
     "hostname": "",
     "port": "",
     "pathname": "/",
     "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": "%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": "xn--ida.test",
-    "hostname": "xn--ida.test",
+    "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:\\../",
     "base": "about:blank",
     "href": "sc:\\../",
     "origin": "null",
     "protocol": "sc:",
     "username": "",
@@ -4239,16 +4530,98 @@
     "password": "",
     "host": "",
     "hostname": "",
     "port": "",
     "pathname": ":a@example.net",
     "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",
     "base": "about:blank",
     "href": "http://127.0.0.1:10100/relative_import.html",
     "origin": "http://127.0.0.1:10100",
     "protocol": "http:",
     "username": "",
@@ -4386,80 +4759,1390 @@
     "protocol": "http:",
     "username": "",
     "password": "",
     "host": "foo.bar",
     "hostname": "foo.bar",
     "port": "",
     "pathname": "/baz",
     "search": "?qux",
-    "searchParams": "",
+    "searchParams": "qux=",
     "hash": "#foo%08bar"
   },
-  "# IPv6 compression and serialization",
-  {
-    "input": "http://[fe80:cd00::1257:0:211e:729c]/",
-    "base": "about:blank",
-    "href": "http://[fe80:cd00::1257:0:211e:729c]/",
-    "origin": "http://[fe80:cd00::1257:0:211e:729c]",
-    "protocol": "http:",
-    "username": "",
-    "password": "",
-    "host": "[fe80:cd00::1257:0:211e:729c]",
-    "hostname": "[fe80:cd00::1257:0:211e:729c]",
+  "# 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": "",
-    "searchParams": "",
-    "hash": ""
-  },
-  "# IPv6 compression and serialization: Compress sequences of two or more zeroes",
-  {
-    "input": "http://[fe80:cd00:0:0:1257:0:211e:729c]/",
-    "base": "about:blank",
-    "href": "http://[fe80:cd00::1257:0:211e:729c]/",
-    "origin": "http://[fe80:cd00::1257:0:211e:729c]",
-    "protocol": "http:",
-    "username": "",
-    "password": "",
-    "host": "[fe80:cd00::1257:0:211e:729c]",
-    "hostname": "[fe80:cd00::1257:0:211e:729c]",
+    "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": "\\//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": "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": "",
-    "searchParams": "",
-    "hash": ""
-  },
-  "# IPv6 compression and serialization: Compress longest sequence of zeroes",
-  {
-    "input": "http://[fe80:0:0:1257:0:0:0:cd00]/",
-    "base": "about:blank",
-    "href": "http://[fe80:0:0:1257::cd00]/",
-    "origin": "http://[fe80:0:0:1257::cd00]",
-    "protocol": "http:",
-    "username": "",
-    "password": "",
-    "host": "[fe80:0:0:1257::cd00]",
-    "hostname": "[fe80:0:0:1257::cd00]",
+    "hash": "#frag"
+  },
+  "# IPv6 tests",
+  {
+    "input": "http://[1:0::]",
+    "base": "http://example.net/",
+    "href": "http://[1::]/",
+    "origin": "http://[1::]",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "[1::]",
+    "hostname": "[1::]",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "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",
+    "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": "",
-    "searchParams": "",
-    "hash": ""
-  },
-  "# IPv6 compression and serialization: Do not compress lone zeroes",
-  {
-    "input": "http://[fe80:cd00:0:cde:1257:0:211e:729c]/",
-    "base": "about:blank",
-    "href": "http://[fe80:cd00:0:cde:1257:0:211e:729c]/",
-    "origin": "http://[fe80:cd00:0:cde:1257:0:211e:729c]",
-    "protocol": "http:",
-    "username": "",
-    "password": "",
-    "host": "[fe80:cd00:0:cde:1257:0:211e:729c]",
-    "hostname": "[fe80:cd00:0:cde:1257:0:211e:729c]",
+    "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": "0177.0.0.0189",
+    "hostname": "0177.0.0.0189",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://0x7f.0.0.0x7g",
+    "base": "about:blank",
+    "href": "http://0x7f.0.0.0x7g/",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "0x7f.0.0.0x7g",
+    "hostname": "0x7f.0.0.0x7g",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://0X7F.0.0.0X7G",
+    "base": "about:blank",
+    "href": "http://0x7f.0.0.0x7g/",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "0x7f.0.0.0x7g",
+    "hostname": "0x7f.0.0.0x7g",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "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": "",
-    "searchParams": "",
-    "hash": ""
+    "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"
   }
 ]