소스 검색

Auto merge of #517 - servo:2.0, r=nox

2.0

<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/rust-url/517)
<!-- Reviewable:end -->
bors-servo 7 년 전
부모
커밋
6c76f2e71b
34개의 변경된 파일1586개의 추가작업 그리고 2029개의 파일을 삭제
  1. 2 18
      .travis.yml
  2. 5 16
      Cargo.toml
  3. 16 19
      data-url/src/forgiving_base64.rs
  4. 46 30
      data-url/src/lib.rs
  5. 12 9
      data-url/src/mime.rs
  6. 17 18
      data-url/tests/wpt.rs
  7. 0 0
      docs/.nojekyll
  8. 0 3
      docs/404.html
  9. 0 3
      docs/index.html
  10. 2 1
      idna/Cargo.toml
  11. 16 14
      idna/src/lib.rs
  12. 47 38
      idna/src/punycode.rs
  13. 195 133
      idna/src/uts46.rs
  14. 35 25
      idna/tests/punycode.rs
  15. 9 9
      idna/tests/unit.rs
  16. 75 49
      idna/tests/uts46.rs
  17. 37 48
      percent_encoding/lib.rs
  18. 0 146
      src/encoding.rs
  19. 78 142
      src/form_urlencoded.rs
  20. 118 154
      src/host.rs
  21. 280 259
      src/lib.rs
  22. 12 29
      src/origin.rs
  23. 298 225
      src/parser.rs
  24. 21 9
      src/path_segments.rs
  25. 35 0
      src/query_encoding.rs
  26. 19 10
      src/quirks.rs
  27. 37 32
      src/slicing.rs
  28. 47 37
      tests/data.rs
  29. 127 96
      tests/unit.rs
  30. 0 23
      url_serde/Cargo.toml
  31. 0 1
      url_serde/LICENSE-APACHE
  32. 0 1
      url_serde/LICENSE-MIT
  33. 0 11
      url_serde/README.md
  34. 0 421
      url_serde/src/lib.rs

+ 2 - 18
.travis.yml

@@ -1,28 +1,12 @@
 language: rust
+script: cargo test --all-features --all
 
 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
-
-    - cargo update -p unicode-normalization --precise 0.1.5
-
-    # 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: 1.30.0
   - 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

+ 5 - 16
Cargo.toml

@@ -2,7 +2,7 @@
 
 name = "url"
 # When updating version, also modify html_root_url in the lib.rs
-version = "1.7.2"
+version = "2.0.0"
 authors = ["The rust-url developers"]
 
 description = "URL library for Rust, based on the WHATWG URL Standard"
@@ -18,7 +18,7 @@ travis-ci = { repository = "servo/rust-url" }
 appveyor = { repository = "Manishearth/rust-url" }
 
 [workspace]
-members = [".", "idna", "percent_encoding", "url_serde", "data-url"]
+members = [".", "idna", "percent_encoding", "data-url"]
 
 [[test]]
 name = "unit"
@@ -32,27 +32,16 @@ test = false
 
 [dev-dependencies]
 rustc-test = "0.3"
-rustc-serialize = "0.3"
-serde_json = ">=0.6.1, <0.9"
+serde_json = "1.0"
 
 bencher = "0.1"
 
-[features]
-query_encoding = ["encoding"]
-heap_size = ["heapsize"]
-
 [dependencies]
-encoding = {version = "0.2", optional = true}
-heapsize = {version = ">=0.4.1, <0.5", optional = true}
-idna = { version = "0.1.0", path = "./idna" }
+idna = { version = "0.2.0", path = "./idna" }
 matches = "0.1"
 percent-encoding = { version = "1.0.0", path = "./percent_encoding" }
-rustc-serialize = {version = "0.3", optional = true}
-serde = {version = ">=0.6.1, <0.9", optional = true}
+serde = {version = "1.0", optional = true}
 
 [[bench]]
 name = "parse_url"
 harness = false
-
-[package.metadata.docs.rs]
-features = ["query_encoding"]

+ 16 - 19
data-url/src/forgiving_base64.rs

@@ -29,7 +29,7 @@ impl From<DecodeError<Impossible>> for InvalidBase64 {
     fn from(e: DecodeError<Impossible>) -> Self {
         match e {
             DecodeError::InvalidBase64(e) => e,
-            DecodeError::WriteError(e) => match e {}
+            DecodeError::WriteError(e) => match e {},
         }
     }
 }
@@ -46,14 +46,20 @@ pub fn decode_to_vec(input: &[u8]) -> Result<Vec<u8>, InvalidBase64> {
 }
 
 /// <https://infra.spec.whatwg.org/#forgiving-base64-decode>
-pub struct Decoder<F, E> where F: FnMut(&[u8]) -> Result<(), E> {
+pub struct Decoder<F, E>
+where
+    F: FnMut(&[u8]) -> Result<(), E>,
+{
     write_bytes: F,
     bit_buffer: u32,
     buffer_bit_length: u8,
     padding_symbols: u8,
 }
 
-impl<F, E> Decoder<F, E> where F: FnMut(&[u8]) -> Result<(), E> {
+impl<F, E> Decoder<F, E>
+where
+    F: FnMut(&[u8]) -> Result<(), E>,
+{
     pub fn new(write_bytes: F) -> Self {
         Self {
             write_bytes,
@@ -72,12 +78,12 @@ impl<F, E> Decoder<F, E> where F: FnMut(&[u8]) -> Result<(), E> {
 
                 // Remove ASCII whitespace
                 if matches!(byte, b' ' | b'\t' | b'\n' | b'\r' | b'\x0C') {
-                    continue
+                    continue;
                 }
 
                 if byte == b'=' {
                     self.padding_symbols = self.padding_symbols.saturating_add(1);
-                    continue
+                    continue;
                 }
 
                 Err(InvalidBase64Details::UnexpectedSymbol(byte))?
@@ -115,32 +121,22 @@ impl<F, E> Decoder<F, E> where F: FnMut(&[u8]) -> Result<(), E> {
             (12, 2) | (12, 0) => {
                 // A multiple of four of alphabet symbols, followed by two more symbols,
                 // optionally followed by two padding characters (which make a total multiple of four).
-                let byte_buffer = [
-                    (self.bit_buffer >> 4) as u8,
-                ];
+                let byte_buffer = [(self.bit_buffer >> 4) as u8];
                 (self.write_bytes)(&byte_buffer).map_err(DecodeError::WriteError)?;
             }
             (18, 1) | (18, 0) => {
                 // A multiple of four of alphabet symbols, followed by three more symbols,
                 // optionally followed by one padding character (which make a total multiple of four).
-                let byte_buffer = [
-                    (self.bit_buffer >> 10) as u8,
-                    (self.bit_buffer >> 2) as u8,
-                ];
+                let byte_buffer = [(self.bit_buffer >> 10) as u8, (self.bit_buffer >> 2) as u8];
                 (self.write_bytes)(&byte_buffer).map_err(DecodeError::WriteError)?;
             }
-            (6, _) => {
-                Err(InvalidBase64Details::LoneAlphabetSymbol)?
-            }
-            _ => {
-                Err(InvalidBase64Details::Padding)?
-            }
+            (6, _) => Err(InvalidBase64Details::LoneAlphabetSymbol)?,
+            _ => Err(InvalidBase64Details::Padding)?,
         }
         Ok(())
     }
 }
 
-
 /// Generated by `make_base64_decode_table.py` based on "Table 1: The Base 64 Alphabet"
 /// at <https://tools.ietf.org/html/rfc4648#section-4>
 ///
@@ -148,6 +144,7 @@ impl<F, E> Decoder<F, E> where F: FnMut(&[u8]) -> Result<(), E> {
 /// Array values are their positions in the base64 alphabet,
 /// or -1 for symbols not in the alphabet.
 /// The position contributes 6 bits to the decoded bytes.
+#[rustfmt::skip]
 const BASE64_DECODE_TABLE: [i8; 256] = [
     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,

+ 46 - 30
data-url/src/lib.rs

@@ -15,14 +15,15 @@
 //! assert!(fragment.is_none());
 //! ```
 
-#[macro_use] extern crate matches;
+#[macro_use]
+extern crate matches;
 
 macro_rules! require {
     ($condition: expr) => {
         if !$condition {
-            return None
+            return None;
         }
-    }
+    };
 }
 
 pub mod forgiving_base64;
@@ -53,7 +54,11 @@ impl<'a> DataUrl<'a> {
 
         let (mime_type, base64) = parse_header(from_colon_to_comma);
 
-        Ok(DataUrl { mime_type, base64, encoded_body_plus_fragment })
+        Ok(DataUrl {
+            mime_type,
+            base64,
+            encoded_body_plus_fragment,
+        })
     }
 
     pub fn mime_type(&self) -> &mime::Mime {
@@ -62,9 +67,12 @@ impl<'a> DataUrl<'a> {
 
     /// Streaming-decode the data URL’s body to `write_body_bytes`,
     /// and return the URL’s fragment identifier if it has one.
-    pub fn decode<F, E>(&self, write_body_bytes: F)
-                        -> Result<Option<FragmentIdentifier<'a>>, forgiving_base64::DecodeError<E>>
-        where F: FnMut(&[u8]) -> Result<(), E>
+    pub fn decode<F, E>(
+        &self,
+        write_body_bytes: F,
+    ) -> Result<Option<FragmentIdentifier<'a>>, forgiving_base64::DecodeError<E>>
+    where
+        F: FnMut(&[u8]) -> Result<(), E>,
     {
         if self.base64 {
             decode_with_base64(self.encoded_body_plus_fragment, write_body_bytes)
@@ -75,9 +83,9 @@ impl<'a> DataUrl<'a> {
     }
 
     /// Return the decoded body, and the URL’s fragment identifier if it has one.
-    pub fn decode_to_vec(&self)
-        -> Result<(Vec<u8>, Option<FragmentIdentifier<'a>>), forgiving_base64::InvalidBase64>
-    {
+    pub fn decode_to_vec(
+        &self,
+    ) -> Result<(Vec<u8>, Option<FragmentIdentifier<'a>>), forgiving_base64::InvalidBase64> {
         let mut body = Vec::new();
         let fragment = self.decode(|bytes| Ok(body.extend_from_slice(bytes)))?;
         Ok((body, fragment))
@@ -100,7 +108,7 @@ impl<'a> FragmentIdentifier<'a> {
                     percent_encode(byte, &mut string)
                 }
                 // Printable ASCII
-                _ => string.push(byte as char)
+                _ => string.push(byte as char),
             }
         }
         string
@@ -125,7 +133,9 @@ fn pretend_parse_data_url(input: &str) -> Option<&str> {
     let mut bytes = left_trimmed.bytes();
     {
         // Ignore ASCII tabs or newlines like the URL parser would
-        let mut iter = bytes.by_ref().filter(|&byte| !matches!(byte, b'\t' | b'\n' | b'\r'));
+        let mut iter = bytes
+            .by_ref()
+            .filter(|&byte| !matches!(byte, b'\t' | b'\n' | b'\r'));
         require!(iter.next()?.to_ascii_lowercase() == b'd');
         require!(iter.next()?.to_ascii_lowercase() == b'a');
         require!(iter.next()?.to_ascii_lowercase() == b't');
@@ -142,10 +152,10 @@ fn pretend_parse_data_url(input: &str) -> Option<&str> {
 fn find_comma_before_fragment(after_colon: &str) -> Option<(&str, &str)> {
     for (i, byte) in after_colon.bytes().enumerate() {
         if byte == b',' {
-            return Some((&after_colon[..i], &after_colon[i + 1..]))
+            return Some((&after_colon[..i], &after_colon[i + 1..]));
         }
         if byte == b'#' {
-            break
+            break;
         }
     }
     None
@@ -187,18 +197,16 @@ fn parse_header(from_colon_to_comma: &str) -> (mime::Mime, bool) {
             }
 
             // Printable ASCII
-            _ => string.push(byte as char)
+            _ => string.push(byte as char),
         }
     }
 
     // FIXME: does Mime::from_str match the MIME Sniffing Standard’s parsing algorithm?
     // <https://mimesniff.spec.whatwg.org/#parse-a-mime-type>
-    let mime_type = string.parse().unwrap_or_else(|_| {
-        mime::Mime {
-            type_: String::from("text"),
-            subtype: String::from("plain"),
-            parameters: vec![(String::from("charset"), String::from("US-ASCII"))],
-        }
+    let mime_type = string.parse().unwrap_or_else(|_| mime::Mime {
+        type_: String::from("text"),
+        subtype: String::from("plain"),
+        parameters: vec![(String::from("charset"), String::from("US-ASCII"))],
     });
 
     (mime_type, base64)
@@ -209,7 +217,9 @@ fn remove_base64_suffix(s: &str) -> Option<&str> {
     let mut bytes = s.bytes();
     {
         // Ignore ASCII tabs or newlines like the URL parser would
-        let iter = bytes.by_ref().filter(|&byte| !matches!(byte, b'\t' | b'\n' | b'\r'));
+        let iter = bytes
+            .by_ref()
+            .filter(|&byte| !matches!(byte, b'\t' | b'\n' | b'\r'));
 
         // Search from the end
         let mut iter = iter.rev();
@@ -240,9 +250,12 @@ fn percent_encode(byte: u8, string: &mut String) {
 /// Anything that would have been UTF-8 percent-encoded by the URL parser
 /// would be percent-decoded here.
 /// We skip that round-trip and pass it through unchanged.
-fn decode_without_base64<F, E>(encoded_body_plus_fragment: &str, mut write_bytes: F)
-                               -> Result<Option<FragmentIdentifier>, E>
-    where F: FnMut(&[u8]) -> Result<(), E>
+fn decode_without_base64<F, E>(
+    encoded_body_plus_fragment: &str,
+    mut write_bytes: F,
+) -> Result<Option<FragmentIdentifier>, E>
+where
+    F: FnMut(&[u8]) -> Result<(), E>,
 {
     let bytes = encoded_body_plus_fragment.as_bytes();
     let mut slice_start = 0;
@@ -275,11 +288,11 @@ fn decode_without_base64<F, E>(encoded_body_plus_fragment: &str, mut write_bytes
                 b'#' => {
                     let fragment_start = i + 1;
                     let fragment = &encoded_body_plus_fragment[fragment_start..];
-                    return Ok(Some(FragmentIdentifier(fragment)))
+                    return Ok(Some(FragmentIdentifier(fragment)));
                 }
 
                 // Ignore over '\t' | '\n' | '\r'
-                _ => slice_start = i + 1
+                _ => slice_start = i + 1,
             }
         }
     }
@@ -290,9 +303,12 @@ fn decode_without_base64<F, E>(encoded_body_plus_fragment: &str, mut write_bytes
 /// `decode_without_base64()` composed with
 /// <https://infra.spec.whatwg.org/#isomorphic-decode> composed with
 /// <https://infra.spec.whatwg.org/#forgiving-base64-decode>.
-fn decode_with_base64<F, E>(encoded_body_plus_fragment: &str, write_bytes: F)
-                            -> Result<Option<FragmentIdentifier>, forgiving_base64::DecodeError<E>>
-    where F: FnMut(&[u8]) -> Result<(), E>
+fn decode_with_base64<F, E>(
+    encoded_body_plus_fragment: &str,
+    write_bytes: F,
+) -> Result<Option<FragmentIdentifier>, forgiving_base64::DecodeError<E>>
+where
+    F: FnMut(&[u8]) -> Result<(), E>,
 {
     let mut decoder = forgiving_base64::Decoder::new(write_bytes);
     let fragment = decode_without_base64(encoded_body_plus_fragment, |bytes| decoder.feed(bytes))?;

+ 12 - 9
data-url/src/mime.rs

@@ -7,14 +7,16 @@ pub struct Mime {
     pub type_: String,
     pub subtype: String,
     /// (name, value)
-    pub parameters: Vec<(String, String)>
+    pub parameters: Vec<(String, String)>,
 }
 
 impl Mime {
     pub fn get_parameter<P>(&self, name: &P) -> Option<&str>
-        where P: ?Sized + PartialEq<str>
+    where
+        P: ?Sized + PartialEq<str>,
     {
-        self.parameters.iter()
+        self.parameters
+            .iter()
             .find(|&&(ref n, _)| name == &**n)
             .map(|&(_, ref v)| &**v)
     }
@@ -67,11 +69,11 @@ fn parse_parameters(s: &str, parameters: &mut Vec<(String, String)>) {
         let piece = piece.trim_left_matches(ascii_whitespace);
         let (name, value) = split2(piece, '=');
         if name.is_empty() || !only_http_token_code_points(name) || contains(&parameters, name) {
-            continue
+            continue;
         }
         if let Some(value) = value {
             let value = if value.starts_with('"') {
-                let max_len = value.len().saturating_sub(2);  // without start or end quotes
+                let max_len = value.len().saturating_sub(2); // without start or end quotes
                 let mut unescaped_value = String::with_capacity(max_len);
                 let mut chars = value[1..].chars();
                 'until_closing_quote: loop {
@@ -79,7 +81,7 @@ fn parse_parameters(s: &str, parameters: &mut Vec<(String, String)>) {
                         match c {
                             '"' => break 'until_closing_quote,
                             '\\' => unescaped_value.push(chars.next().unwrap_or('\\')),
-                            _ => unescaped_value.push(c)
+                            _ => unescaped_value.push(c),
                         }
                     }
                     if let Some(piece) = semicolon_separated.next() {
@@ -88,17 +90,17 @@ fn parse_parameters(s: &str, parameters: &mut Vec<(String, String)>) {
                         unescaped_value.push(';');
                         chars = piece.chars()
                     } else {
-                        break
+                        break;
                     }
                 }
                 if !valid_value(&unescaped_value) {
-                    continue
+                    continue;
                 }
                 unescaped_value
             } else {
                 let value = value.trim_right_matches(ascii_whitespace);
                 if !valid_value(value) {
-                    continue
+                    continue;
                 }
                 value.to_owned()
             };
@@ -160,6 +162,7 @@ macro_rules! byte_map {
 }
 
 // Copied from https://github.com/hyperium/mime/blob/v0.3.5/src/parse.rs#L293
+#[rustfmt::skip]
 static IS_HTTP_TOKEN: [bool; 256] = byte_map![
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

+ 17 - 18
data-url/tests/wpt.rs

@@ -1,6 +1,7 @@
 extern crate data_url;
 extern crate rustc_test;
-#[macro_use] extern crate serde;
+#[macro_use]
+extern crate serde;
 extern crate serde_json;
 
 fn run_data_url(input: String, expected_mime: Option<String>, expected_body: Option<Vec<u8>>) {
@@ -22,11 +23,10 @@ fn run_data_url(input: String, expected_mime: Option<String>, expected_body: Opt
 }
 
 fn collect_data_url<F>(add_test: &mut F)
-    where F: FnMut(String, bool, rustc_test::TestFn)
+where
+    F: FnMut(String, bool, rustc_test::TestFn),
 {
-    let known_failures = [
-        "data://test:test/,X",
-    ];
+    let known_failures = ["data://test:test/,X"];
 
     #[derive(Deserialize)]
     #[serde(untagged)]
@@ -47,7 +47,7 @@ fn collect_data_url<F>(add_test: &mut F)
             should_panic,
             rustc_test::TestFn::dyn_test_fn(move || {
                 run_data_url(input, expected_mime, expected_body)
-            })
+            }),
         );
     }
 }
@@ -62,9 +62,9 @@ fn run_base64(input: String, expected: Option<Vec<u8>>) {
     }
 }
 
-
 fn collect_base64<F>(add_test: &mut F)
-    where F: FnMut(String, bool, rustc_test::TestFn)
+where
+    F: FnMut(String, bool, rustc_test::TestFn),
 {
     let known_failures = [];
 
@@ -75,9 +75,7 @@ fn collect_base64<F>(add_test: &mut F)
         add_test(
             format!("base64 {:?}", input),
             should_panic,
-            rustc_test::TestFn::dyn_test_fn(move || {
-                run_base64(input, expected)
-            })
+            rustc_test::TestFn::dyn_test_fn(move || run_base64(input, expected)),
         );
     }
 }
@@ -92,9 +90,9 @@ fn run_mime(input: String, expected: Option<String>) {
     }
 }
 
-
 fn collect_mime<F>(add_test: &mut F)
-    where F: FnMut(String, bool, rustc_test::TestFn)
+where
+    F: FnMut(String, bool, rustc_test::TestFn),
 {
     let known_failures = [];
 
@@ -102,7 +100,10 @@ fn collect_mime<F>(add_test: &mut F)
     #[serde(untagged)]
     enum Entry {
         Comment(String),
-        TestCase { input: String, output: Option<String> }
+        TestCase {
+            input: String,
+            output: Option<String>,
+        },
     }
 
     let v: Vec<Entry> = serde_json::from_str(include_str!("mime-types.json")).unwrap();
@@ -115,7 +116,7 @@ fn collect_mime<F>(add_test: &mut F)
             Entry::TestCase { input, output } => (input, output),
             Entry::Comment(s) => {
                 last_comment = Some(s);
-                continue
+                continue;
             }
         };
 
@@ -127,9 +128,7 @@ fn collect_mime<F>(add_test: &mut F)
                 format!("MIME type {:?}", input)
             },
             should_panic,
-            rustc_test::TestFn::dyn_test_fn(move || {
-                run_mime(input, expected)
-            })
+            rustc_test::TestFn::dyn_test_fn(move || run_mime(input, expected)),
         );
     }
 }

+ 0 - 0
docs/.nojekyll


+ 0 - 3
docs/404.html

@@ -1,3 +0,0 @@
-<meta http-equiv="refresh" content="0; url=https://docs.rs/url/">
-<link rel="canonical" href="https://docs.rs/url/">
-<a href="https://docs.rs/url/">Moved to docs.rs</a>

+ 0 - 3
docs/index.html

@@ -1,3 +0,0 @@
-<meta http-equiv="refresh" content="0; url=https://docs.rs/url/">
-<link rel="canonical" href="https://docs.rs/url/">
-<a href="https://docs.rs/url/">Moved to docs.rs</a>

+ 2 - 1
idna/Cargo.toml

@@ -1,10 +1,11 @@
 [package]
 name = "idna"
-version = "0.1.5"
+version = "0.2.0"
 authors = ["The rust-url developers"]
 description = "IDNA (Internationalizing Domain Names in Applications) and Punycode."
 repository = "https://github.com/servo/rust-url/"
 license = "MIT/Apache-2.0"
+autotests = false
 
 [lib]
 doctest = false

+ 16 - 14
idna/src/lib.rs

@@ -32,12 +32,15 @@
 //! > that minimizes the impact of this transition for client software,
 //! > allowing client software to access domains that are valid under either system.
 
-#[macro_use] extern crate matches;
+#[macro_use]
+extern crate matches;
 extern crate unicode_bidi;
 extern crate unicode_normalization;
 
 pub mod punycode;
-pub mod uts46;
+mod uts46;
+
+pub use uts46::{Config, Errors};
 
 /// The [domain to ASCII](https://url.spec.whatwg.org/#concept-domain-to-ascii) algorithm.
 ///
@@ -47,11 +50,16 @@ pub mod uts46;
 ///
 /// This process may fail.
 pub fn domain_to_ascii(domain: &str) -> Result<String, uts46::Errors> {
-    uts46::to_ascii(domain, uts46::Flags {
-        use_std3_ascii_rules: false,
-        transitional_processing: false,
-        verify_dns_length: false,
-    })
+    Config::default().to_ascii(domain)
+}
+
+/// The [domain to ASCII](https://url.spec.whatwg.org/#concept-domain-to-ascii) algorithm,
+/// with the `beStrict` flag set.
+pub fn domain_to_ascii_strict(domain: &str) -> Result<String, uts46::Errors> {
+    Config::default()
+        .use_std3_ascii_rules(true)
+        .verify_dns_length(true)
+        .to_ascii(domain)
 }
 
 /// The [domain to Unicode](https://url.spec.whatwg.org/#concept-domain-to-unicode) algorithm.
@@ -63,11 +71,5 @@ pub fn domain_to_ascii(domain: &str) -> Result<String, uts46::Errors> {
 /// This may indicate [syntax violations](https://url.spec.whatwg.org/#syntax-violation)
 /// but always returns a string for the mapped domain.
 pub fn domain_to_unicode(domain: &str) -> (String, Result<(), uts46::Errors>) {
-    uts46::to_unicode(domain, uts46::Flags {
-        use_std3_ascii_rules: false,
-
-        // Unused:
-        transitional_processing: false,
-        verify_dns_length: false,
-    })
+    Config::default().to_unicode(domain)
 }

+ 47 - 38
idna/src/punycode.rs

@@ -13,10 +13,8 @@
 //! `encode_str` and `decode_to_string` provide convenience wrappers
 //! that convert from and to Rust’s UTF-8 based `str` and `String` types.
 
-use std::u32;
 use std::char;
-#[allow(unused_imports, deprecated)]
-use std::ascii::AsciiExt;
+use std::u32;
 
 // Bootstring parameters for Punycode
 static BASE: u32 = 36;
@@ -28,7 +26,6 @@ static INITIAL_BIAS: u32 = 72;
 static INITIAL_N: u32 = 0x80;
 static DELIMITER: char = '-';
 
-
 #[inline]
 fn adapt(mut delta: u32, num_points: u32, first_time: bool) -> u32 {
     delta /= if first_time { DAMP } else { 2 };
@@ -41,7 +38,6 @@ fn adapt(mut delta: u32, num_points: u32, first_time: bool) -> u32 {
     k + (((BASE - T_MIN + 1) * delta) / (delta + SKEW))
 }
 
-
 /// Convert Punycode to an Unicode `String`.
 ///
 /// This is a convenience wrapper around `decode`.
@@ -50,7 +46,6 @@ pub fn decode_to_string(input: &str) -> Option<String> {
     decode(input).map(|chars| chars.into_iter().collect())
 }
 
-
 /// Convert Punycode to Unicode.
 ///
 /// Return None on malformed input or overflow.
@@ -63,8 +58,12 @@ pub fn decode(input: &str) -> Option<Vec<char>> {
         None => (Vec::new(), input),
         Some(position) => (
             input[..position].chars().collect(),
-            if position > 0 { &input[position + 1..] } else { input }
-        )
+            if position > 0 {
+                &input[position + 1..]
+            } else {
+                input
+            },
+        ),
     };
     let mut code_point = INITIAL_N;
     let mut bias = INITIAL_BIAS;
@@ -82,35 +81,39 @@ pub fn decode(input: &str) -> Option<Vec<char>> {
         // which gets added to i.
         loop {
             let digit = match byte {
-                byte @ b'0' ... b'9' => byte - b'0' + 26,
-                byte @ b'A' ... b'Z' => byte - b'A',
-                byte @ b'a' ... b'z' => byte - b'a',
-                _ => return None
+                byte @ b'0'..=b'9' => byte - b'0' + 26,
+                byte @ b'A'..=b'Z' => byte - b'A',
+                byte @ b'a'..=b'z' => byte - b'a',
+                _ => return None,
             } as u32;
             if digit > (u32::MAX - i) / weight {
-                return None  // Overflow
+                return None; // Overflow
             }
             i += digit * weight;
-            let t = if k <= bias { T_MIN }
-                    else if k >= bias + T_MAX { T_MAX }
-                    else { k - bias };
+            let t = if k <= bias {
+                T_MIN
+            } else if k >= bias + T_MAX {
+                T_MAX
+            } else {
+                k - bias
+            };
             if digit < t {
-                break
+                break;
             }
             if weight > u32::MAX / (BASE - t) {
-                return None  // Overflow
+                return None; // Overflow
             }
             weight *= BASE - t;
             k += BASE;
             byte = match iter.next() {
-                None => return None,  // End of input before the end of this delta
+                None => return None, // End of input before the end of this delta
                 Some(byte) => byte,
             };
         }
         let length = output.len() as u32;
         bias = adapt(i - previous_i, length + 1, previous_i == 0);
         if i / (length + 1) > u32::MAX - code_point {
-            return None  // Overflow
+            return None; // Overflow
         }
         // i was supposed to wrap around from length+1 to 0,
         // incrementing code_point each time.
@@ -118,7 +121,7 @@ pub fn decode(input: &str) -> Option<Vec<char>> {
         i %= length + 1;
         let c = match char::from_u32(code_point) {
             Some(c) => c,
-            None => return None
+            None => return None,
         };
         output.insert(i as usize, c);
         i += 1;
@@ -126,7 +129,6 @@ pub fn decode(input: &str) -> Option<Vec<char>> {
     Some(output)
 }
 
-
 /// Convert an Unicode `str` to Punycode.
 ///
 /// This is a convenience wrapper around `encode`.
@@ -135,16 +137,16 @@ pub fn encode_str(input: &str) -> Option<String> {
     encode(&input.chars().collect::<Vec<char>>())
 }
 
-
 /// Convert Unicode to Punycode.
 ///
 /// Return None on overflow, which can only happen on inputs that would take more than
 /// 63 encoded bytes, the DNS limit on domain name labels.
 pub fn encode(input: &[char]) -> Option<String> {
     // Handle "basic" (ASCII) code points. They are encoded as-is.
-    let output_bytes = input.iter().filter_map(|&c|
-        if c.is_ascii() { Some(c as u8) } else { None }
-    ).collect();
+    let output_bytes = input
+        .iter()
+        .filter_map(|&c| if c.is_ascii() { Some(c as u8) } else { None })
+        .collect();
     let mut output = unsafe { String::from_utf8_unchecked(output_bytes) };
     let basic_length = output.len() as u32;
     if basic_length > 0 {
@@ -158,10 +160,14 @@ pub fn encode(input: &[char]) -> Option<String> {
     while processed < input_length {
         // All code points < code_point have been handled already.
         // Find the next larger one.
-        let min_code_point = input.iter().map(|&c| c as u32)
-                                  .filter(|&c| c >= code_point).min().unwrap();
+        let min_code_point = input
+            .iter()
+            .map(|&c| c as u32)
+            .filter(|&c| c >= code_point)
+            .min()
+            .unwrap();
         if min_code_point - code_point > (u32::MAX - delta) / (processed + 1) {
-            return None  // Overflow
+            return None; // Overflow
         }
         // Increase delta to advance the decoder’s <code_point,i> state to <min_code_point,0>
         delta += (min_code_point - code_point) * (processed + 1);
@@ -171,7 +177,7 @@ pub fn encode(input: &[char]) -> Option<String> {
             if c < code_point {
                 delta += 1;
                 if delta == 0 {
-                    return None  // Overflow
+                    return None; // Overflow
                 }
             }
             if c == code_point {
@@ -179,11 +185,15 @@ pub fn encode(input: &[char]) -> Option<String> {
                 let mut q = delta;
                 let mut k = BASE;
                 loop {
-                    let t = if k <= bias { T_MIN }
-                            else if k >= bias + T_MAX { T_MAX }
-                            else { k - bias };
+                    let t = if k <= bias {
+                        T_MIN
+                    } else if k >= bias + T_MAX {
+                        T_MAX
+                    } else {
+                        k - bias
+                    };
                     if q < t {
-                        break
+                        break;
                     }
                     let value = t + ((q - t) % (BASE - t));
                     output.push(value_to_digit(value));
@@ -202,12 +212,11 @@ pub fn encode(input: &[char]) -> Option<String> {
     Some(output)
 }
 
-
 #[inline]
 fn value_to_digit(value: u32) -> char {
     match value {
-        0 ... 25 => (value as u8 + 'a' as u8) as char,  // a..z
-        26 ... 35 => (value as u8 - 26 + '0' as u8) as char,  // 0..9
-        _ => panic!()
+        0..=25 => (value as u8 + 'a' as u8) as char, // a..z
+        26..=35 => (value as u8 - 26 + '0' as u8) as char, // 0..9
+        _ => panic!(),
     }
 }

+ 195 - 133
idna/src/uts46.rs

@@ -11,18 +11,14 @@
 
 use self::Mapping::*;
 use punycode;
-#[allow(unused_imports, deprecated)]
-use std::ascii::AsciiExt;
-use std::cmp::Ordering::{Equal, Less, Greater};
-use unicode_bidi::{BidiClass, bidi_class};
-use unicode_normalization::UnicodeNormalization;
+use std::cmp::Ordering::{Equal, Greater, Less};
+use unicode_bidi::{bidi_class, BidiClass};
 use unicode_normalization::char::is_combining_mark;
+use unicode_normalization::UnicodeNormalization;
 
 include!("uts46_mapping_table.rs");
 
-
-pub static PUNYCODE_PREFIX: &'static str = "xn--";
-
+const PUNYCODE_PREFIX: &'static str = "xn--";
 
 #[derive(Debug)]
 struct StringTableSlice {
@@ -68,28 +64,30 @@ fn find_char(codepoint: char) -> &'static Mapping {
             Equal
         }
     });
-    r.ok().map(|i| {
-        const SINGLE_MARKER: u16 = 1 << 15;
+    r.ok()
+        .map(|i| {
+            const SINGLE_MARKER: u16 = 1 << 15;
 
-        let x = INDEX_TABLE[i];
-        let single = (x & SINGLE_MARKER) != 0;
-        let offset = !SINGLE_MARKER & x;
+            let x = INDEX_TABLE[i];
+            let single = (x & SINGLE_MARKER) != 0;
+            let offset = !SINGLE_MARKER & x;
 
-        if single {
-            &MAPPING_TABLE[offset as usize]
-        } else {
-            &MAPPING_TABLE[(offset + (codepoint as u16 - TABLE[i].from as u16)) as usize]
-        }
-    }).unwrap()
+            if single {
+                &MAPPING_TABLE[offset as usize]
+            } else {
+                &MAPPING_TABLE[(offset + (codepoint as u16 - TABLE[i].from as u16)) as usize]
+            }
+        })
+        .unwrap()
 }
 
-fn map_char(codepoint: char, flags: Flags, output: &mut String, errors: &mut Vec<Error>) {
+fn map_char(codepoint: char, config: Config, output: &mut String, errors: &mut Vec<Error>) {
     match *find_char(codepoint) {
         Mapping::Valid => output.push(codepoint),
-        Mapping::Ignored => {},
+        Mapping::Ignored => {}
         Mapping::Mapped(ref slice) => output.push_str(decode_slice(slice)),
         Mapping::Deviation(ref slice) => {
-            if flags.transitional_processing {
+            if config.transitional_processing {
                 output.push_str(decode_slice(slice))
             } else {
                 output.push(codepoint)
@@ -100,13 +98,13 @@ fn map_char(codepoint: char, flags: Flags, output: &mut String, errors: &mut Vec
             output.push(codepoint);
         }
         Mapping::DisallowedStd3Valid => {
-            if flags.use_std3_ascii_rules {
+            if config.use_std3_ascii_rules {
                 errors.push(Error::DissallowedByStd3AsciiRules);
             }
             output.push(codepoint)
         }
         Mapping::DisallowedStd3Mapped(ref slice) => {
-            if flags.use_std3_ascii_rules {
+            if config.use_std3_ascii_rules {
                 errors.push(Error::DissallowedMappedInStd3);
             }
             output.push_str(decode_slice(slice))
@@ -135,16 +133,23 @@ fn passes_bidi(label: &str, is_bidi_domain: bool) -> bool {
             loop {
                 match chars.next() {
                     Some(c) => {
-                        if !matches!(bidi_class(c),
-                                     BidiClass::L | BidiClass::EN |
-                                     BidiClass::ES | BidiClass::CS |
-                                     BidiClass::ET | BidiClass::ON |
-                                     BidiClass::BN | BidiClass::NSM
-                                    ) {
+                        if !matches!(
+                            bidi_class(c),
+                            BidiClass::L
+                                | BidiClass::EN
+                                | BidiClass::ES
+                                | BidiClass::CS
+                                | BidiClass::ET
+                                | BidiClass::ON
+                                | BidiClass::BN
+                                | BidiClass::NSM
+                        ) {
                             return false;
                         }
-                    },
-                    None => { break; },
+                    }
+                    None => {
+                        break;
+                    }
                 }
             }
 
@@ -158,16 +163,18 @@ fn passes_bidi(label: &str, is_bidi_domain: bool) -> bool {
                         last_non_nsm = rev_chars.next();
                         continue;
                     }
-                    _ => { break; },
+                    _ => {
+                        break;
+                    }
                 }
             }
             match last_non_nsm {
-                Some(c) if bidi_class(c) == BidiClass::L
-                    || bidi_class(c) == BidiClass::EN => {},
-                Some(_) => { return false; },
+                Some(c) if bidi_class(c) == BidiClass::L || bidi_class(c) == BidiClass::EN => {}
+                Some(_) => {
+                    return false;
+                }
                 _ => {}
             }
-
         }
 
         // RTL label
@@ -188,33 +195,51 @@ fn passes_bidi(label: &str, is_bidi_domain: bool) -> bool {
                             found_an = true;
                         }
 
-                        if !matches!(char_class, BidiClass::R | BidiClass::AL |
-                                     BidiClass::AN | BidiClass::EN |
-                                     BidiClass::ES | BidiClass::CS |
-                                     BidiClass::ET | BidiClass::ON |
-                                     BidiClass::BN | BidiClass::NSM) {
+                        if !matches!(
+                            char_class,
+                            BidiClass::R
+                                | BidiClass::AL
+                                | BidiClass::AN
+                                | BidiClass::EN
+                                | BidiClass::ES
+                                | BidiClass::CS
+                                | BidiClass::ET
+                                | BidiClass::ON
+                                | BidiClass::BN
+                                | BidiClass::NSM
+                        ) {
                             return false;
                         }
-                    },
-                    None => { break; },
+                    }
+                    None => {
+                        break;
+                    }
                 }
             }
             // Rule 3
             let mut rev_chars = label.chars().rev();
             let mut last = rev_chars.next();
-            loop { // must end in L or EN followed by 0 or more NSM
+            loop {
+                // must end in L or EN followed by 0 or more NSM
                 match last {
                     Some(c) if bidi_class(c) == BidiClass::NSM => {
                         last = rev_chars.next();
                         continue;
                     }
-                    _ => { break; },
+                    _ => {
+                        break;
+                    }
                 }
             }
             match last {
-                Some(c) if matches!(bidi_class(c), BidiClass::R | BidiClass::AL |
-                                    BidiClass::EN | BidiClass::AN) => {},
-                _ => { return false; }
+                Some(c)
+                    if matches!(
+                        bidi_class(c),
+                        BidiClass::R | BidiClass::AL | BidiClass::EN | BidiClass::AN
+                    ) => {}
+                _ => {
+                    return false;
+                }
             }
 
             // Rule 4
@@ -233,34 +258,30 @@ fn passes_bidi(label: &str, is_bidi_domain: bool) -> bool {
 }
 
 /// http://www.unicode.org/reports/tr46/#Validity_Criteria
-fn validate_full(label: &str, is_bidi_domain: bool, flags: Flags, errors: &mut Vec<Error>) {
+fn validate_full(label: &str, is_bidi_domain: bool, config: Config, errors: &mut Vec<Error>) {
     // V1: Must be in NFC form.
     if label.nfc().ne(label.chars()) {
         errors.push(Error::ValidityCriteria);
     } else {
-        validate(label, is_bidi_domain, flags, errors);
+        validate(label, is_bidi_domain, config, errors);
     }
 }
 
-fn validate(label: &str, is_bidi_domain: bool, flags: Flags, errors: &mut Vec<Error>) {
+fn validate(label: &str, is_bidi_domain: bool, config: Config, errors: &mut Vec<Error>) {
     let first_char = label.chars().next();
     if first_char == None {
         // Empty string, pass
     }
-
     // V2: No U+002D HYPHEN-MINUS in both third and fourth positions.
     //
     // NOTE: Spec says that the label must not contain a HYPHEN-MINUS character in both the
     // third and fourth positions. But nobody follows this criteria. See the spec issue below:
     // https://github.com/whatwg/url/issues/53
-    //
-    // TODO: Add *CheckHyphens* flag.
 
     // V3: neither begin nor end with a U+002D HYPHEN-MINUS
-    else if label.starts_with("-") || label.ends_with("-") {
+    else if config.check_hyphens && (label.starts_with("-") || label.ends_with("-")) {
         errors.push(Error::ValidityCriteria);
     }
-
     // V4: not contain a U+002E FULL STOP
     //
     // Here, label can't contain '.' since the input is from .split('.')
@@ -269,17 +290,15 @@ fn validate(label: &str, is_bidi_domain: bool, flags: Flags, errors: &mut Vec<Er
     else if is_combining_mark(first_char.unwrap()) {
         errors.push(Error::ValidityCriteria);
     }
-
     // V6: Check against Mapping Table
     else if label.chars().any(|c| match *find_char(c) {
         Mapping::Valid => false,
-        Mapping::Deviation(_) => flags.transitional_processing,
-        Mapping::DisallowedStd3Valid => flags.use_std3_ascii_rules,
+        Mapping::Deviation(_) => config.transitional_processing,
+        Mapping::DisallowedStd3Valid => config.use_std3_ascii_rules,
         _ => true,
     }) {
         errors.push(Error::ValidityCriteria);
     }
-
     // V7: ContextJ rules
     //
     // TODO: Implement rules and add *CheckJoiners* flag.
@@ -287,17 +306,16 @@ fn validate(label: &str, is_bidi_domain: bool, flags: Flags, errors: &mut Vec<Er
     // V8: Bidi rules
     //
     // TODO: Add *CheckBidi* flag
-    else if !passes_bidi(label, is_bidi_domain)
-    {
+    else if !passes_bidi(label, is_bidi_domain) {
         errors.push(Error::ValidityCriteria);
     }
 }
 
 /// http://www.unicode.org/reports/tr46/#Processing
-fn processing(domain: &str, flags: Flags, errors: &mut Vec<Error>) -> String {
+fn processing(domain: &str, config: Config, errors: &mut Vec<Error>) -> String {
     let mut mapped = String::with_capacity(domain.len());
     for c in domain.chars() {
-        map_char(c, flags, &mut mapped, errors)
+        map_char(c, config, &mut mapped, errors)
     }
     let mut normalized = String::with_capacity(mapped.len());
     normalized.extend(mapped.nfc());
@@ -305,18 +323,18 @@ fn processing(domain: &str, flags: Flags, errors: &mut Vec<Error>) -> String {
     // Find out if it's a Bidi Domain Name
     //
     // First, check for literal bidi chars
-    let mut is_bidi_domain = domain.chars().any(|c|
-        matches!(bidi_class(c), BidiClass::R | BidiClass::AL | BidiClass::AN)
-    );
+    let mut is_bidi_domain = domain
+        .chars()
+        .any(|c| matches!(bidi_class(c), BidiClass::R | BidiClass::AL | BidiClass::AN));
     if !is_bidi_domain {
         // Then check for punycode-encoded bidi chars
         for label in normalized.split('.') {
             if label.starts_with(PUNYCODE_PREFIX) {
                 match punycode::decode_to_string(&label[PUNYCODE_PREFIX.len()..]) {
                     Some(decoded_label) => {
-                        if decoded_label.chars().any(|c|
+                        if decoded_label.chars().any(|c| {
                             matches!(bidi_class(c), BidiClass::R | BidiClass::AL | BidiClass::AN)
-                        ) {
+                        }) {
                             is_bidi_domain = true;
                         }
                     }
@@ -338,26 +356,124 @@ fn processing(domain: &str, flags: Flags, errors: &mut Vec<Error>) -> String {
         if label.starts_with(PUNYCODE_PREFIX) {
             match punycode::decode_to_string(&label[PUNYCODE_PREFIX.len()..]) {
                 Some(decoded_label) => {
-                    let flags = Flags { transitional_processing: false, ..flags };
-                    validate_full(&decoded_label, is_bidi_domain, flags, errors);
+                    let config = config.transitional_processing(false);
+                    validate_full(&decoded_label, is_bidi_domain, config, errors);
                     validated.push_str(&decoded_label)
                 }
-                None => errors.push(Error::PunycodeError)
+                None => errors.push(Error::PunycodeError),
             }
         } else {
             // `normalized` is already `NFC` so we can skip that check
-            validate(label, is_bidi_domain, flags, errors);
+            validate(label, is_bidi_domain, config, errors);
             validated.push_str(label)
         }
     }
     validated
 }
 
-#[derive(Copy, Clone)]
-pub struct Flags {
-   pub use_std3_ascii_rules: bool,
-   pub transitional_processing: bool,
-   pub verify_dns_length: bool,
+#[derive(Clone, Copy)]
+pub struct Config {
+    use_std3_ascii_rules: bool,
+    transitional_processing: bool,
+    verify_dns_length: bool,
+    check_hyphens: bool,
+}
+
+/// The defaults are that of https://url.spec.whatwg.org/#idna
+impl Default for Config {
+    fn default() -> Self {
+        Config {
+            use_std3_ascii_rules: false,
+            transitional_processing: false,
+            check_hyphens: false,
+            // check_bidi: true,
+            // check_joiners: true,
+
+            // Only use for to_ascii, not to_unicode
+            verify_dns_length: false,
+        }
+    }
+}
+
+impl Config {
+    #[inline]
+    pub fn use_std3_ascii_rules(mut self, value: bool) -> Self {
+        self.use_std3_ascii_rules = value;
+        self
+    }
+
+    #[inline]
+    pub fn transitional_processing(mut self, value: bool) -> Self {
+        self.transitional_processing = value;
+        self
+    }
+
+    #[inline]
+    pub fn verify_dns_length(mut self, value: bool) -> Self {
+        self.verify_dns_length = value;
+        self
+    }
+
+    #[inline]
+    pub fn check_hyphens(mut self, value: bool) -> Self {
+        self.check_hyphens = value;
+        self
+    }
+
+    /// http://www.unicode.org/reports/tr46/#ToASCII
+    pub fn to_ascii(self, domain: &str) -> Result<String, Errors> {
+        let mut errors = Vec::new();
+        let mut result = String::new();
+        let mut first = true;
+        for label in processing(domain, self, &mut errors).split('.') {
+            if !first {
+                result.push('.');
+            }
+            first = false;
+            if label.is_ascii() {
+                result.push_str(label);
+            } else {
+                match punycode::encode_str(label) {
+                    Some(x) => {
+                        result.push_str(PUNYCODE_PREFIX);
+                        result.push_str(&x);
+                    }
+                    None => errors.push(Error::PunycodeError),
+                }
+            }
+        }
+
+        if self.verify_dns_length {
+            let domain = if result.ends_with(".") {
+                &result[..result.len() - 1]
+            } else {
+                &*result
+            };
+            if domain.len() < 1 || domain.split('.').any(|label| label.len() < 1) {
+                errors.push(Error::TooShortForDns)
+            }
+            if domain.len() > 253 || domain.split('.').any(|label| label.len() > 63) {
+                errors.push(Error::TooLongForDns)
+            }
+        }
+        if errors.is_empty() {
+            Ok(result)
+        } else {
+            Err(Errors(errors))
+        }
+    }
+
+    /// http://www.unicode.org/reports/tr46/#ToUnicode
+    pub fn to_unicode(self, domain: &str) -> (String, Result<(), Errors>) {
+        let mut errors = Vec::new();
+        let domain = processing(domain, self, &mut errors);
+        let errors = if errors.is_empty() {
+            Ok(())
+        } else {
+            Err(Errors(errors))
+        };
+        (domain, errors)
+    }
 }
 
 #[derive(PartialEq, Eq, Clone, Copy, Debug)]
@@ -377,57 +493,3 @@ enum Error {
 /// More details may be exposed in the future.
 #[derive(Debug)]
 pub struct Errors(Vec<Error>);
-
-/// http://www.unicode.org/reports/tr46/#ToASCII
-pub fn to_ascii(domain: &str, flags: Flags) -> Result<String, Errors> {
-    let mut errors = Vec::new();
-    let mut result = String::new();
-    let mut first = true;
-    for label in processing(domain, flags, &mut errors).split('.') {
-        if !first {
-            result.push('.');
-        }
-        first = false;
-        if label.is_ascii() {
-            result.push_str(label);
-        } else {
-            match punycode::encode_str(label) {
-                Some(x) => {
-                    result.push_str(PUNYCODE_PREFIX);
-                    result.push_str(&x);
-                },
-                None => errors.push(Error::PunycodeError)
-            }
-        }
-    }
-
-    if flags.verify_dns_length {
-        let domain = if result.ends_with(".") { &result[..result.len()-1]  } else { &*result };
-        if domain.len() < 1 || domain.split('.').any(|label| label.len() < 1) {
-            errors.push(Error::TooShortForDns)
-        }
-        if domain.len() > 253 || domain.split('.').any(|label| label.len() > 63) {
-            errors.push(Error::TooLongForDns)
-        }
-    }
-    if errors.is_empty() {
-        Ok(result)
-    } else {
-        Err(Errors(errors))
-    }
-}
-
-/// http://www.unicode.org/reports/tr46/#ToUnicode
-///
-/// Only `use_std3_ascii_rules` is used in `flags`.
-pub fn to_unicode(domain: &str, mut flags: Flags) -> (String, Result<(), Errors>) {
-    flags.transitional_processing = false;
-    let mut errors = Vec::new();
-    let domain = processing(domain, flags, &mut errors);
-    let errors = if errors.is_empty() {
-        Ok(())
-    } else {
-        Err(Errors(errors))
-    };
-    (domain, errors)
-}

+ 35 - 25
idna/tests/punycode.rs

@@ -15,19 +15,25 @@ fn one_test(decoded: &str, encoded: &str) {
         None => panic!("Decoding {} failed.", encoded),
         Some(result) => {
             let result = result.into_iter().collect::<String>();
-            assert!(result == decoded,
-                    format!("Incorrect decoding of \"{}\":\n   \"{}\"\n!= \"{}\"\n",
-                            encoded, result, decoded))
+            assert!(
+                result == decoded,
+                format!(
+                    "Incorrect decoding of \"{}\":\n   \"{}\"\n!= \"{}\"\n",
+                    encoded, result, decoded
+                )
+            )
         }
     }
 
     match encode_str(decoded) {
         None => panic!("Encoding {} failed.", decoded),
-        Some(result) => {
-            assert!(result == encoded,
-                    format!("Incorrect encoding of \"{}\":\n   \"{}\"\n!= \"{}\"\n",
-                            decoded, result, encoded))
-        }
+        Some(result) => assert!(
+            result == encoded,
+            format!(
+                "Incorrect encoding of \"{}\":\n   \"{}\"\n!= \"{}\"\n",
+                decoded, result, encoded
+            )
+        ),
     }
 }
 
@@ -41,25 +47,29 @@ fn get_string<'a>(map: &'a Object, key: &str) -> &'a str {
 
 pub fn collect_tests<F: FnMut(String, TestFn)>(add_test: &mut F) {
     match Json::from_str(include_str!("punycode_tests.json")) {
-        Ok(Json::Array(tests)) => for (i, test) in tests.into_iter().enumerate() {
-            match test {
-                Json::Object(o) => {
-                    let test_name = {
-                        let desc = get_string(&o, "description");
+        Ok(Json::Array(tests)) => {
+            for (i, test) in tests.into_iter().enumerate() {
+                match test {
+                    Json::Object(o) => {
+                        let test_name = {
+                            let desc = get_string(&o, "description");
                             if desc.is_empty() {
-                            format!("Punycode {}", i + 1)
-                        } else {
-                            format!("Punycode {}: {}", i + 1, desc)
-                        }
-                    };
-                    add_test(test_name, TestFn::dyn_test_fn(move || one_test(
-                        get_string(&o, "decoded"),
-                        get_string(&o, "encoded"),
-                    )))
+                                format!("Punycode {}", i + 1)
+                            } else {
+                                format!("Punycode {}: {}", i + 1, desc)
+                            }
+                        };
+                        add_test(
+                            test_name,
+                            TestFn::dyn_test_fn(move || {
+                                one_test(get_string(&o, "decoded"), get_string(&o, "encoded"))
+                            }),
+                        )
+                    }
+                    _ => panic!(),
                 }
-                _ => panic!(),
             }
-        },
-        other => panic!("{:?}", other)
+        }
+        other => panic!("{:?}", other),
     }
 }

+ 9 - 9
idna/tests/unit.rs

@@ -1,16 +1,13 @@
 extern crate idna;
 extern crate unicode_normalization;
 
-use idna::uts46;
 use unicode_normalization::char::is_combining_mark;
 
-
-fn _to_ascii(domain: &str) -> Result<String, uts46::Errors> {
-    uts46::to_ascii(domain, uts46::Flags {
-        transitional_processing: false,
-        use_std3_ascii_rules: true,
-        verify_dns_length: true,
-    })
+fn _to_ascii(domain: &str) -> Result<String, idna::Errors> {
+    idna::Config::default()
+        .verify_dns_length(true)
+        .use_std3_ascii_rules(true)
+        .to_ascii(domain)
 }
 
 #[test]
@@ -29,7 +26,10 @@ fn test_v8_bidi_rules() {
     assert_eq!(_to_ascii("אבּג").unwrap(), "xn--kdb3bdf");
     assert_eq!(_to_ascii("ابج").unwrap(), "xn--mgbcm");
     assert_eq!(_to_ascii("abc.ابج").unwrap(), "abc.xn--mgbcm");
-    assert_eq!(_to_ascii("אבּג.ابج").unwrap(), "xn--kdb3bdf.xn--mgbcm");
+    assert_eq!(
+        _to_ascii("אבּג.ابج").unwrap(),
+        "xn--kdb3bdf.xn--mgbcm"
+    );
 
     // Bidi domain names cannot start with digits
     assert!(_to_ascii("0a.\u{05D0}").is_err());

+ 75 - 49
idna/tests/uts46.rs

@@ -7,19 +7,18 @@
 // except according to those terms.
 
 use std::char;
-use idna::uts46;
 use test::TestFn;
 
 pub fn collect_tests<F: FnMut(String, TestFn)>(add_test: &mut F) {
     // http://www.unicode.org/Public/idna/latest/IdnaTest.txt
     for (i, line) in include_str!("IdnaTest.txt").lines().enumerate() {
         if line == "" || line.starts_with("#") {
-            continue
+            continue;
         }
         // Remove comments
         let mut line = match line.find("#") {
             Some(index) => &line[0..index],
-            None => line
+            None => line,
         };
 
         let mut expected_failure = false;
@@ -35,61 +34,85 @@ pub fn collect_tests<F: FnMut(String, TestFn)>(add_test: &mut F) {
         let source = unescape(original);
         let to_unicode = pieces.remove(0);
         let to_ascii = pieces.remove(0);
-        let nv8 = if pieces.len() > 0 { pieces.remove(0) } else { "" };
+        let nv8 = if pieces.len() > 0 {
+            pieces.remove(0)
+        } else {
+            ""
+        };
 
         if expected_failure {
             continue;
         }
 
         let test_name = format!("UTS #46 line {}", i + 1);
-        add_test(test_name, TestFn::dyn_test_fn(move || {
-            let result = uts46::to_ascii(&source, uts46::Flags {
-                use_std3_ascii_rules: true,
-                transitional_processing: test_type == "T",
-                verify_dns_length: true,
-            });
+        add_test(
+            test_name,
+            TestFn::dyn_test_fn(move || {
+                let result = idna::Config::default()
+                    .use_std3_ascii_rules(true)
+                    .verify_dns_length(true)
+                    .check_hyphens(true)
+                    .transitional_processing(test_type == "T")
+                    .to_ascii(&source);
 
-            if to_ascii.starts_with("[") {
-                if to_ascii.starts_with("[C") {
-                    // http://unicode.org/reports/tr46/#Deviations
-                    // applications that perform IDNA2008 lookup are not required to check
-                    // for these contexts
-                    return;
-                }
-                if to_ascii == "[V2]" {
-                    // Everybody ignores V2
-                    // https://github.com/servo/rust-url/pull/240
-                    // https://github.com/whatwg/url/issues/53#issuecomment-181528158
-                    // http://www.unicode.org/review/pri317/
+                if to_ascii.starts_with("[") {
+                    if to_ascii.starts_with("[C") {
+                        // http://unicode.org/reports/tr46/#Deviations
+                        // applications that perform IDNA2008 lookup are not required to check
+                        // for these contexts
+                        return;
+                    }
+                    if to_ascii == "[V2]" {
+                        // Everybody ignores V2
+                        // https://github.com/servo/rust-url/pull/240
+                        // https://github.com/whatwg/url/issues/53#issuecomment-181528158
+                        // http://www.unicode.org/review/pri317/
+                        return;
+                    }
+                    let res = result.ok();
+                    assert!(
+                        res == None,
+                        "Expected error. result: {} | original: {} | source: {}",
+                        res.unwrap(),
+                        original,
+                        source
+                    );
                     return;
                 }
-                let res = result.ok();
-                assert!(res == None, "Expected error. result: {} | original: {} | source: {}",
-                        res.unwrap(), original, source);
-                return;
-            }
 
-            let to_ascii = if to_ascii.len() > 0 {
-                to_ascii.to_string()
-            } else {
-                if to_unicode.len() > 0 {
-                    to_unicode.to_string()
+                let to_ascii = if to_ascii.len() > 0 {
+                    to_ascii.to_string()
                 } else {
-                    source.clone()
-                }
-            };
+                    if to_unicode.len() > 0 {
+                        to_unicode.to_string()
+                    } else {
+                        source.clone()
+                    }
+                };
 
-            if nv8 == "NV8" {
-                // This result isn't valid under IDNA2008. Skip it
-                return;
-            }
+                if nv8 == "NV8" {
+                    // This result isn't valid under IDNA2008. Skip it
+                    return;
+                }
 
-            assert!(result.is_ok(), "Couldn't parse {} | original: {} | error: {:?}",
-                    source, original, result.err());
-            let output = result.ok().unwrap();
-            assert!(output == to_ascii, "result: {} | expected: {} | original: {} | source: {}",
-                    output, to_ascii, original, source);
-        }))
+                assert!(
+                    result.is_ok(),
+                    "Couldn't parse {} | original: {} | error: {:?}",
+                    source,
+                    original,
+                    result.err()
+                );
+                let output = result.ok().unwrap();
+                assert!(
+                    output == to_ascii,
+                    "result: {} | expected: {} | original: {} | source: {}",
+                    output,
+                    to_ascii,
+                    original,
+                    source
+                );
+            }),
+        )
     }
 }
 
@@ -99,7 +122,7 @@ fn unescape(input: &str) -> String {
     loop {
         match chars.next() {
             None => return output,
-            Some(c) =>
+            Some(c) => {
                 if c == '\\' {
                     match chars.next().unwrap() {
                         '\\' => output.push('\\'),
@@ -108,10 +131,12 @@ fn unescape(input: &str) -> String {
                             let c2 = chars.next().unwrap().to_digit(16).unwrap();
                             let c3 = chars.next().unwrap().to_digit(16).unwrap();
                             let c4 = chars.next().unwrap().to_digit(16).unwrap();
-                            match char::from_u32(((c1 * 16 + c2) * 16 + c3) * 16 + c4)
-                            {
+                            match char::from_u32(((c1 * 16 + c2) * 16 + c3) * 16 + c4) {
                                 Some(c) => output.push(c),
-                                None => { output.push_str(&format!("\\u{:X}{:X}{:X}{:X}",c1,c2,c3,c4)); }
+                                None => {
+                                    output
+                                        .push_str(&format!("\\u{:X}{:X}{:X}{:X}", c1, c2, c3, c4));
+                                }
                             };
                         }
                         _ => panic!("Invalid test data input"),
@@ -119,6 +144,7 @@ fn unescape(input: &str) -> String {
                 } else {
                     output.push(c);
                 }
+            }
         }
     }
 }

+ 37 - 48
percent_encoding/lib.rs

@@ -32,7 +32,6 @@
 //! assert_eq!(utf8_percent_encode("foo bar?", DEFAULT_ENCODE_SET).to_string(), "foo%20bar%3F");
 //! ```
 
-use std::ascii::AsciiExt;
 use std::borrow::Cow;
 use std::fmt;
 use std::slice;
@@ -176,23 +175,23 @@ define_encode_set! {
 pub fn percent_encode_byte(byte: u8) -> &'static str {
     let index = usize::from(byte) * 3;
     &"\
-        %00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F\
-        %10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F\
-        %20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F\
-        %30%31%32%33%34%35%36%37%38%39%3A%3B%3C%3D%3E%3F\
-        %40%41%42%43%44%45%46%47%48%49%4A%4B%4C%4D%4E%4F\
-        %50%51%52%53%54%55%56%57%58%59%5A%5B%5C%5D%5E%5F\
-        %60%61%62%63%64%65%66%67%68%69%6A%6B%6C%6D%6E%6F\
-        %70%71%72%73%74%75%76%77%78%79%7A%7B%7C%7D%7E%7F\
-        %80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F\
-        %90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F\
-        %A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF\
-        %B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF\
-        %C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF\
-        %D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF\
-        %E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF\
-        %F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF\
-    "[index..index + 3]
+      %00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F\
+      %10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F\
+      %20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F\
+      %30%31%32%33%34%35%36%37%38%39%3A%3B%3C%3D%3E%3F\
+      %40%41%42%43%44%45%46%47%48%49%4A%4B%4C%4D%4E%4F\
+      %50%51%52%53%54%55%56%57%58%59%5A%5B%5C%5D%5E%5F\
+      %60%61%62%63%64%65%66%67%68%69%6A%6B%6C%6D%6E%6F\
+      %70%71%72%73%74%75%76%77%78%79%7A%7B%7C%7D%7E%7F\
+      %80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F\
+      %90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F\
+      %A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF\
+      %B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF\
+      %C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF\
+      %D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF\
+      %E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF\
+      %F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF\
+      "[index..index + 3]
 }
 
 /// Percent-encode the given bytes with the given encode set.
@@ -260,7 +259,7 @@ impl<'a, E: EncodeSet> Iterator for PercentEncode<'a, E> {
                         // 1 for first_byte + i for previous iterations of this loop
                         let (unchanged_slice, remaining) = self.bytes.split_at(1 + i);
                         self.bytes = remaining;
-                        return Some(unsafe { str::from_utf8_unchecked(unchanged_slice) })
+                        return Some(unsafe { str::from_utf8_unchecked(unchanged_slice) });
                     } else {
                         assert!(byte.is_ascii());
                     }
@@ -296,17 +295,15 @@ impl<'a, E: EncodeSet> From<PercentEncode<'a, E>> for Cow<'a, str> {
     fn from(mut iter: PercentEncode<'a, E>) -> Self {
         match iter.next() {
             None => "".into(),
-            Some(first) => {
-                match iter.next() {
-                    None => first.into(),
-                    Some(second) => {
-                        let mut string = first.to_owned();
-                        string.push_str(second);
-                        string.extend(iter);
-                        string.into()
-                    }
+            Some(first) => match iter.next() {
+                None => first.into(),
+                Some(second) => {
+                    let mut string = first.to_owned();
+                    string.push_str(second);
+                    string.extend(iter);
+                    string.into()
                 }
-            }
+            },
         }
     }
 }
@@ -328,7 +325,7 @@ impl<'a, E: EncodeSet> From<PercentEncode<'a, E>> for Cow<'a, str> {
 #[inline]
 pub fn percent_decode(input: &[u8]) -> PercentDecode {
     PercentDecode {
-        bytes: input.iter()
+        bytes: input.iter(),
     }
 }
 
@@ -388,10 +385,8 @@ impl<'a> PercentDecode<'a> {
                 let unchanged_bytes_len = initial_bytes.len() - bytes_iter.len() - 3;
                 let mut decoded = initial_bytes[..unchanged_bytes_len].to_owned();
                 decoded.push(decoded_byte);
-                decoded.extend(PercentDecode {
-                    bytes: bytes_iter
-                });
-                return Some(decoded)
+                decoded.extend(PercentDecode { bytes: bytes_iter });
+                return Some(decoded);
             }
         }
         // Nothing to decode
@@ -403,18 +398,14 @@ impl<'a> PercentDecode<'a> {
     /// This is return `Err` when the percent-decoded bytes are not well-formed in UTF-8.
     pub fn decode_utf8(self) -> Result<Cow<'a, str>, str::Utf8Error> {
         match self.clone().into() {
-            Cow::Borrowed(bytes) => {
-                match str::from_utf8(bytes) {
-                    Ok(s) => Ok(s.into()),
-                    Err(e) => Err(e),
-                }
-            }
-            Cow::Owned(bytes) => {
-                match String::from_utf8(bytes) {
-                    Ok(s) => Ok(s.into()),
-                    Err(e) => Err(e.utf8_error()),
-                }
-            }
+            Cow::Borrowed(bytes) => match str::from_utf8(bytes) {
+                Ok(s) => Ok(s.into()),
+                Err(e) => Err(e),
+            },
+            Cow::Owned(bytes) => match String::from_utf8(bytes) {
+                Ok(s) => Ok(s.into()),
+                Err(e) => Err(e.utf8_error()),
+            },
         }
     }
 
@@ -443,5 +434,3 @@ fn decode_utf8_lossy(input: Cow<[u8]>) -> Cow<str> {
         }
     }
 }
-
-

+ 0 - 146
src/encoding.rs

@@ -1,146 +0,0 @@
-// Copyright 2013-2014 The rust-url developers.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-
-//! Abstraction that conditionally compiles either to rust-encoding,
-//! or to only support UTF-8.
-
-#[cfg(feature = "query_encoding")] extern crate encoding;
-
-use std::borrow::Cow;
-#[cfg(feature = "query_encoding")] use std::fmt::{self, Debug, Formatter};
-
-#[cfg(feature = "query_encoding")] use self::encoding::types::{DecoderTrap, EncoderTrap};
-#[cfg(feature = "query_encoding")] use self::encoding::label::encoding_from_whatwg_label;
-#[cfg(feature = "query_encoding")] pub use self::encoding::types::EncodingRef;
-
-#[cfg(feature = "query_encoding")]
-#[derive(Copy, Clone)]
-pub struct EncodingOverride {
-    /// `None` means UTF-8.
-    encoding: Option<EncodingRef>
-}
-
-#[cfg(feature = "query_encoding")]
-impl EncodingOverride {
-    pub fn from_opt_encoding(encoding: Option<EncodingRef>) -> Self {
-        encoding.map(Self::from_encoding).unwrap_or_else(Self::utf8)
-    }
-
-    pub fn from_encoding(encoding: EncodingRef) -> Self {
-        EncodingOverride {
-            encoding: if encoding.name() == "utf-8" { None } else { Some(encoding) }
-        }
-    }
-
-    #[inline]
-    pub fn utf8() -> Self {
-        EncodingOverride { encoding: None }
-    }
-
-    pub fn lookup(label: &[u8]) -> Option<Self> {
-        // Don't use String::from_utf8_lossy since no encoding label contains U+FFFD
-        // https://encoding.spec.whatwg.org/#names-and-labels
-        ::std::str::from_utf8(label)
-        .ok()
-        .and_then(encoding_from_whatwg_label)
-        .map(Self::from_encoding)
-    }
-
-    /// https://encoding.spec.whatwg.org/#get-an-output-encoding
-    pub fn to_output_encoding(self) -> Self {
-        if let Some(encoding) = self.encoding {
-            if matches!(encoding.name(), "utf-16le" | "utf-16be") {
-                return Self::utf8()
-            }
-        }
-        self
-    }
-
-    pub fn is_utf8(&self) -> bool {
-        self.encoding.is_none()
-    }
-
-    pub fn name(&self) -> &'static str {
-        match self.encoding {
-            Some(encoding) => encoding.name(),
-            None => "utf-8",
-        }
-    }
-
-    pub fn decode<'a>(&self, input: Cow<'a, [u8]>) -> Cow<'a, str> {
-        match self.encoding {
-            // `encoding.decode` never returns `Err` when called with `DecoderTrap::Replace`
-            Some(encoding) => encoding.decode(&input, DecoderTrap::Replace).unwrap().into(),
-            None => decode_utf8_lossy(input),
-        }
-    }
-
-    pub fn encode<'a>(&self, input: Cow<'a, str>) -> Cow<'a, [u8]> {
-        match self.encoding {
-            // `encoding.encode` never returns `Err` when called with `EncoderTrap::NcrEscape`
-            Some(encoding) => Cow::Owned(encoding.encode(&input, EncoderTrap::NcrEscape).unwrap()),
-            None => encode_utf8(input)
-        }
-    }
-}
-
-#[cfg(feature = "query_encoding")]
-impl Debug for EncodingOverride {
-    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
-        write!(f, "EncodingOverride {{ encoding: ")?;
-        match self.encoding {
-            Some(e) => write!(f, "{} }}", e.name()),
-            None => write!(f, "None }}")
-        }
-    }
-}
-
-#[cfg(not(feature = "query_encoding"))]
-#[derive(Copy, Clone, Debug)]
-pub struct EncodingOverride;
-
-#[cfg(not(feature = "query_encoding"))]
-impl EncodingOverride {
-    #[inline]
-    pub fn utf8() -> Self {
-        EncodingOverride
-    }
-
-    pub fn decode<'a>(&self, input: Cow<'a, [u8]>) -> Cow<'a, str> {
-        decode_utf8_lossy(input)
-    }
-
-    pub fn encode<'a>(&self, input: Cow<'a, str>) -> Cow<'a, [u8]> {
-        encode_utf8(input)
-    }
-}
-
-pub fn decode_utf8_lossy(input: Cow<[u8]>) -> Cow<str> {
-    match input {
-        Cow::Borrowed(bytes) => String::from_utf8_lossy(bytes),
-        Cow::Owned(bytes) => {
-            let raw_utf8: *const [u8];
-            match String::from_utf8_lossy(&bytes) {
-                Cow::Borrowed(utf8) => raw_utf8 = utf8.as_bytes(),
-                Cow::Owned(s) => return s.into(),
-            }
-            // from_utf8_lossy returned a borrow of `bytes` unchanged.
-            debug_assert!(raw_utf8 == &*bytes as *const [u8]);
-            // Reuse the existing `Vec` allocation.
-            unsafe { String::from_utf8_unchecked(bytes) }.into()
-        }
-    }
-}
-
-pub fn encode_utf8(input: Cow<str>) -> Cow<[u8]> {
-    match input {
-        Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
-        Cow::Owned(s) => Cow::Owned(s.into_bytes())
-    }
-}

+ 78 - 142
src/form_urlencoded.rs

@@ -13,13 +13,11 @@
 //! Converts between a string (such as an URL’s query string)
 //! and a sequence of (name, value) pairs.
 
-use encoding::EncodingOverride;
-use percent_encoding::{percent_encode_byte, percent_decode};
+use percent_encoding::{percent_decode, percent_encode_byte};
+use query_encoding::{self, decode_utf8_lossy, EncodingOverride};
 use std::borrow::{Borrow, Cow};
-use std::fmt;
 use std::str;
 
-
 /// Convert a byte string in the `application/x-www-form-urlencoded` syntax
 /// into a iterator of (name, value) pairs.
 ///
@@ -29,63 +27,12 @@ use std::str;
 /// converted to `[("#first", "%try%")]`.
 #[inline]
 pub fn parse(input: &[u8]) -> Parse {
-    Parse {
-        input: input,
-        encoding: EncodingOverride::utf8(),
-    }
+    Parse { input: input }
 }
-
-
-/// Convert a byte string in the `application/x-www-form-urlencoded` syntax
-/// into a iterator of (name, value) pairs.
-///
-/// Use `parse(input.as_bytes())` to parse a `&str` string.
-///
-/// This function is only available if the `query_encoding`
-/// [feature](http://doc.crates.io/manifest.html#the-features-section]) is enabled.
-///
-/// Arguments:
-///
-/// * `encoding_override`: The character encoding each name and values is decoded as
-///    after percent-decoding. Defaults to UTF-8.
-///    `EncodingRef` is defined in [rust-encoding](https://github.com/lifthrasiir/rust-encoding).
-/// * `use_charset`: The *use _charset_ flag*. If in doubt, set to `false`.
-#[cfg(feature = "query_encoding")]
-pub fn parse_with_encoding<'a>(input: &'a [u8],
-                               encoding_override: Option<::encoding::EncodingRef>,
-                               use_charset: bool)
-                               -> Result<Parse<'a>, ()> {
-    use std::ascii::AsciiExt;
-
-    let mut encoding = EncodingOverride::from_opt_encoding(encoding_override);
-    if !(encoding.is_utf8() || input.is_ascii()) {
-        return Err(())
-    }
-    if use_charset {
-        for sequence in input.split(|&b| b == b'&') {
-            // No '+' in "_charset_" to replace with ' '.
-            if sequence.starts_with(b"_charset_=") {
-                let value = &sequence[b"_charset_=".len()..];
-                // Skip replacing '+' with ' ' in value since no encoding label contains either:
-                // https://encoding.spec.whatwg.org/#names-and-labels
-                if let Some(e) = EncodingOverride::lookup(value) {
-                    encoding = e;
-                    break
-                }
-            }
-        }
-    }
-    Ok(Parse {
-        input: input,
-        encoding: encoding,
-    })
-}
-
 /// The return type of `parse()`.
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone)]
 pub struct Parse<'a> {
     input: &'a [u8],
-    encoding: EncodingOverride,
 }
 
 impl<'a> Iterator for Parse<'a> {
@@ -94,28 +41,25 @@ impl<'a> Iterator for Parse<'a> {
     fn next(&mut self) -> Option<Self::Item> {
         loop {
             if self.input.is_empty() {
-                return None
+                return None;
             }
             let mut split2 = self.input.splitn(2, |&b| b == b'&');
             let sequence = split2.next().unwrap();
             self.input = split2.next().unwrap_or(&[][..]);
             if sequence.is_empty() {
-                continue
+                continue;
             }
             let mut split2 = sequence.splitn(2, |&b| b == b'=');
             let name = split2.next().unwrap();
             let value = split2.next().unwrap_or(&[][..]);
-            return Some((
-                decode(name, self.encoding),
-                decode(value, self.encoding),
-            ))
+            return Some((decode(name), decode(value)));
         }
     }
 }
 
-fn decode(input: &[u8], encoding: EncodingOverride) -> Cow<str> {
+fn decode(input: &[u8]) -> Cow<str> {
     let replaced = replace_plus(input);
-    encoding.decode(match percent_decode(&replaced).if_any() {
+    decode_utf8_lossy(match percent_decode(&replaced).if_any() {
         Some(vec) => Cow::Owned(vec),
         None => replaced,
     })
@@ -146,16 +90,17 @@ impl<'a> Parse<'a> {
 }
 
 /// Like `Parse`, but yields pairs of `String` instead of pairs of `Cow<str>`.
-#[derive(Debug)]
 pub struct ParseIntoOwned<'a> {
-    inner: Parse<'a>
+    inner: Parse<'a>,
 }
 
 impl<'a> Iterator for ParseIntoOwned<'a> {
     type Item = (String, String);
 
     fn next(&mut self) -> Option<Self::Item> {
-        self.inner.next().map(|(k, v)| (k.into_owned(), v.into_owned()))
+        self.inner
+            .next()
+            .map(|(k, v)| (k.into_owned(), v.into_owned()))
     }
 }
 
@@ -164,9 +109,7 @@ impl<'a> Iterator for ParseIntoOwned<'a> {
 ///
 /// Return an iterator of `&str` slices.
 pub fn byte_serialize(input: &[u8]) -> ByteSerialize {
-    ByteSerialize {
-        bytes: input,
-    }
+    ByteSerialize { bytes: input }
 }
 
 /// Return value of `byte_serialize()`.
@@ -176,7 +119,7 @@ pub struct ByteSerialize<'a> {
 }
 
 fn byte_serialized_unchanged(byte: u8) -> bool {
-    matches!(byte, b'*' | b'-' | b'.' | b'0' ... b'9' | b'A' ... b'Z' | b'_' | b'a' ... b'z')
+    matches!(byte, b'*' | b'-' | b'.' | b'0' ..= b'9' | b'A' ..= b'Z' | b'_' | b'a' ..= b'z')
 }
 
 impl<'a> Iterator for ByteSerialize<'a> {
@@ -186,7 +129,11 @@ impl<'a> Iterator for ByteSerialize<'a> {
         if let Some((&first, tail)) = self.bytes.split_first() {
             if !byte_serialized_unchanged(first) {
                 self.bytes = tail;
-                return Some(if first == b' ' { "+" } else { percent_encode_byte(first) })
+                return Some(if first == b' ' {
+                    "+"
+                } else {
+                    percent_encode_byte(first)
+                });
             }
             let position = tail.iter().position(|&b| !byte_serialized_unchanged(b));
             let (unchanged_slice, remaining) = match position {
@@ -212,20 +159,10 @@ impl<'a> Iterator for ByteSerialize<'a> {
 
 /// The [`application/x-www-form-urlencoded` serializer](
 /// https://url.spec.whatwg.org/#concept-urlencoded-serializer).
-#[derive(Debug)]
-pub struct Serializer<T: Target> {
+pub struct Serializer<'a, T: Target> {
     target: Option<T>,
     start_position: usize,
-    encoding: EncodingOverride,
-    custom_encoding: Option<SilentDebug<Box<FnMut(&str) -> Cow<[u8]>>>>,
-}
-
-struct SilentDebug<T>(T);
-
-impl<T> fmt::Debug for SilentDebug<T> {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        f.write_str("…")
-    }
+    encoding: EncodingOverride<'a>,
 }
 
 pub trait Target {
@@ -235,14 +172,22 @@ pub trait Target {
 }
 
 impl Target for String {
-    fn as_mut_string(&mut self) -> &mut String { self }
-    fn finish(self) -> Self { self }
+    fn as_mut_string(&mut self) -> &mut String {
+        self
+    }
+    fn finish(self) -> Self {
+        self
+    }
     type Finished = Self;
 }
 
 impl<'a> Target for &'a mut String {
-    fn as_mut_string(&mut self) -> &mut String { &mut **self }
-    fn finish(self) -> Self { self }
+    fn as_mut_string(&mut self) -> &mut String {
+        &mut **self
+    }
+    fn finish(self) -> Self {
+        self
+    }
     type Finished = Self;
 }
 
@@ -270,7 +215,7 @@ impl<'a> Target for ::UrlQuery<'a> {
     type Finished = &'a mut ::Url;
 }
 
-impl<T: Target> Serializer<T> {
+impl<'a, T: Target> Serializer<'a, T> {
     /// Create a new `application/x-www-form-urlencoded` serializer for the given target.
     ///
     /// If the target is non-empty,
@@ -285,12 +230,11 @@ impl<T: Target> Serializer<T> {
     /// If that suffix is non-empty,
     /// its content is assumed to already be in `application/x-www-form-urlencoded` syntax.
     pub fn for_suffix(mut target: T, start_position: usize) -> Self {
-        &target.as_mut_string()[start_position..];  // Panic if out of bounds
+        &target.as_mut_string()[start_position..]; // Panic if out of bounds
         Serializer {
             target: Some(target),
             start_position: start_position,
-            encoding: EncodingOverride::utf8(),
-            custom_encoding: None,
+            encoding: None,
         }
     }
 
@@ -303,17 +247,8 @@ impl<T: Target> Serializer<T> {
     }
 
     /// Set the character encoding to be used for names and values before percent-encoding.
-    #[cfg(feature = "query_encoding")]
-    pub fn encoding_override(&mut self, new: Option<::encoding::EncodingRef>) -> &mut Self {
-        self.encoding = EncodingOverride::from_opt_encoding(new).to_output_encoding();
-        self
-    }
-
-    /// Set the character encoding to be used for names and values before percent-encoding.
-    pub fn custom_encoding_override<F>(&mut self, encode: F) -> &mut Self
-        where F: FnMut(&str) -> Cow<[u8]> + 'static
-    {
-        self.custom_encoding = Some(SilentDebug(Box::new(encode)));
+    pub fn encoding_override(&mut self, new: EncodingOverride<'a>) -> &mut Self {
+        self.encoding = new;
         self
     }
 
@@ -321,8 +256,13 @@ impl<T: Target> Serializer<T> {
     ///
     /// Panics if called after `.finish()`.
     pub fn append_pair(&mut self, name: &str, value: &str) -> &mut Self {
-        append_pair(string(&mut self.target), self.start_position, self.encoding,
-                    &mut self.custom_encoding, name, value);
+        append_pair(
+            string(&mut self.target),
+            self.start_position,
+            self.encoding,
+            name,
+            value,
+        );
         self
     }
 
@@ -334,36 +274,28 @@ impl<T: Target> Serializer<T> {
     ///
     /// Panics if called after `.finish()`.
     pub fn extend_pairs<I, K, V>(&mut self, iter: I) -> &mut Self
-    where I: IntoIterator, I::Item: Borrow<(K, V)>, K: AsRef<str>, V: AsRef<str> {
+    where
+        I: IntoIterator,
+        I::Item: Borrow<(K, V)>,
+        K: AsRef<str>,
+        V: AsRef<str>,
+    {
         {
             let string = string(&mut self.target);
             for pair in iter {
                 let &(ref k, ref v) = pair.borrow();
-                append_pair(string, self.start_position, self.encoding,
-                            &mut self.custom_encoding, k.as_ref(), v.as_ref());
+                append_pair(
+                    string,
+                    self.start_position,
+                    self.encoding,
+                    k.as_ref(),
+                    v.as_ref(),
+                );
             }
         }
         self
     }
 
-    /// Add a name/value pair whose name is `_charset_`
-    /// and whose value is the character encoding’s name.
-    /// (See the `encoding_override()` method.)
-    ///
-    /// Panics if called after `.finish()`.
-    #[cfg(feature = "query_encoding")]
-    pub fn append_charset(&mut self) -> &mut Self {
-        assert!(self.custom_encoding.is_none(),
-                "Cannot use both custom_encoding_override() and append_charset()");
-        {
-            let string = string(&mut self.target);
-            append_separator_if_needed(string, self.start_position);
-            string.push_str("_charset_=");
-            string.push_str(self.encoding.name());
-        }
-        self
-    }
-
     /// If this serializer was constructed with a string, take and return that string.
     ///
     /// ```rust
@@ -377,7 +309,10 @@ impl<T: Target> Serializer<T> {
     ///
     /// Panics if called more than once.
     pub fn finish(&mut self) -> T::Finished {
-        self.target.take().expect("url::form_urlencoded::Serializer double finish").finish()
+        self.target
+            .take()
+            .expect("url::form_urlencoded::Serializer double finish")
+            .finish()
     }
 }
 
@@ -388,24 +323,25 @@ fn append_separator_if_needed(string: &mut String, start_position: usize) {
 }
 
 fn string<T: Target>(target: &mut Option<T>) -> &mut String {
-    target.as_mut().expect("url::form_urlencoded::Serializer finished").as_mut_string()
+    target
+        .as_mut()
+        .expect("url::form_urlencoded::Serializer finished")
+        .as_mut_string()
 }
 
-fn append_pair(string: &mut String, start_position: usize, encoding: EncodingOverride,
-               custom_encoding: &mut Option<SilentDebug<Box<FnMut(&str) -> Cow<[u8]>>>>,
-               name: &str, value: &str) {
+fn append_pair(
+    string: &mut String,
+    start_position: usize,
+    encoding: EncodingOverride,
+    name: &str,
+    value: &str,
+) {
     append_separator_if_needed(string, start_position);
-    append_encoded(name, string, encoding, custom_encoding);
+    append_encoded(name, string, encoding);
     string.push('=');
-    append_encoded(value, string, encoding, custom_encoding);
+    append_encoded(value, string, encoding);
 }
 
-fn append_encoded(s: &str, string: &mut String, encoding: EncodingOverride,
-               custom_encoding: &mut Option<SilentDebug<Box<FnMut(&str) -> Cow<[u8]>>>>) {
-    let bytes = if let Some(SilentDebug(ref mut custom)) = *custom_encoding {
-        custom(s)
-    } else {
-        encoding.encode(s.into())
-    };
-    string.extend(byte_serialize(&bytes));
+fn append_encoded(s: &str, string: &mut String, encoding: EncodingOverride) {
+    string.extend(byte_serialize(&query_encoding::encode(encoding, s.into())))
 }

+ 118 - 154
src/host.rs

@@ -6,15 +6,12 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-#[cfg(feature = "heapsize")] use heapsize::HeapSizeOf;
+use idna;
+use parser::{ParseError, ParseResult};
+use percent_encoding::{percent_decode, utf8_percent_encode, SIMPLE_ENCODE_SET};
 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, utf8_percent_encode, SIMPLE_ENCODE_SET};
-use idna;
+use std::net::{Ipv4Addr, Ipv6Addr};
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
 pub enum HostInternal {
@@ -24,12 +21,12 @@ pub enum HostInternal {
     Ipv6(Ipv6Addr),
 }
 
-#[cfg(feature = "heapsize")]
-known_heap_size!(0, HostInternal);
-
-#[cfg(feature="serde")]
+#[cfg(feature = "serde")]
 impl ::serde::Serialize for HostInternal {
-    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: ::serde::Serializer {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: ::serde::Serializer,
+    {
         // This doesn’t use `derive` because that involves
         // large dependencies (that take a long time to build), and
         // either Macros 1.1 which are not stable yet or a cumbersome build script.
@@ -42,13 +39,17 @@ impl ::serde::Serialize for HostInternal {
             HostInternal::Domain => Some(None),
             HostInternal::Ipv4(addr) => Some(Some(IpAddr::V4(addr))),
             HostInternal::Ipv6(addr) => Some(Some(IpAddr::V6(addr))),
-        }.serialize(serializer)
+        }
+        .serialize(serializer)
     }
 }
 
-#[cfg(feature="serde")]
-impl ::serde::Deserialize for HostInternal {
-    fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error> where D: ::serde::Deserializer {
+#[cfg(feature = "serde")]
+impl<'de> ::serde::Deserialize<'de> for HostInternal {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: ::serde::Deserializer<'de>,
+    {
         use std::net::IpAddr;
         Ok(match ::serde::Deserialize::deserialize(deserializer)? {
             None => HostInternal::None,
@@ -71,7 +72,7 @@ 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> {
+pub enum Host<S = String> {
     /// A DNS domain name, as '.' dot-separated labels.
     /// 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
@@ -91,21 +92,28 @@ pub enum Host<S=String> {
     Ipv6(Ipv6Addr),
 }
 
-#[cfg(feature="serde")]
-impl<S: ::serde::Serialize>  ::serde::Serialize for Host<S> {
-    fn serialize<R>(&self, serializer: &mut R) -> Result<(), R::Error> where R: ::serde::Serializer {
+#[cfg(feature = "serde")]
+impl<S: ::serde::Serialize> ::serde::Serialize for Host<S> {
+    fn serialize<R>(&self, serializer: R) -> Result<R::Ok, R::Error>
+    where
+        R: ::serde::Serializer,
+    {
         use std::net::IpAddr;
         match *self {
             Host::Domain(ref s) => Ok(s),
             Host::Ipv4(addr) => Err(IpAddr::V4(addr)),
             Host::Ipv6(addr) => Err(IpAddr::V6(addr)),
-        }.serialize(serializer)
+        }
+        .serialize(serializer)
     }
 }
 
-#[cfg(feature="serde")]
-impl<S: ::serde::Deserialize> ::serde::Deserialize for Host<S> {
-    fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error> where D: ::serde::Deserializer {
+#[cfg(feature = "serde")]
+impl<'de, S: ::serde::Deserialize<'de>> ::serde::Deserialize<'de> for Host<S> {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: ::serde::Deserializer<'de>,
+    {
         use std::net::IpAddr;
         Ok(match ::serde::Deserialize::deserialize(deserializer)? {
             Ok(s) => Host::Domain(s),
@@ -115,16 +123,6 @@ impl<S: ::serde::Deserialize> ::serde::Deserialize for Host<S> {
     }
 }
 
-#[cfg(feature = "heapsize")]
-impl<S: HeapSizeOf> HeapSizeOf for Host<S> {
-    fn heap_size_of_children(&self) -> usize {
-        match *self {
-            Host::Domain(ref s) => s.heap_size_of_children(),
-            _ => 0,
-        }
-    }
-}
-
 impl<'a> Host<&'a str> {
     /// Return a copy of `self` that owns an allocated `String` but does not borrow an `&Url`.
     pub fn to_owned(&self) -> Host<String> {
@@ -143,16 +141,34 @@ impl Host<String> {
     pub fn parse(input: &str) -> Result<Self, ParseError> {
         if input.starts_with('[') {
             if !input.ends_with(']') {
-                return Err(ParseError::InvalidIpv6Address)
+                return Err(ParseError::InvalidIpv6Address);
             }
-            return parse_ipv6addr(&input[1..input.len() - 1]).map(Host::Ipv6)
+            return parse_ipv6addr(&input[1..input.len() - 1]).map(Host::Ipv6);
         }
         let domain = percent_decode(input.as_bytes()).decode_utf8_lossy();
         let domain = idna::domain_to_ascii(&domain)?;
-        if domain.find(|c| matches!(c,
-            '\0' | '\t' | '\n' | '\r' | ' ' | '#' | '%' | '/' | ':' | '?' | '@' | '[' | '\\' | ']'
-        )).is_some() {
-            return Err(ParseError::InvalidDomainCharacter)
+        if domain
+            .find(|c| {
+                matches!(
+                    c,
+                    '\0' | '\t'
+                        | '\n'
+                        | '\r'
+                        | ' '
+                        | '#'
+                        | '%'
+                        | '/'
+                        | ':'
+                        | '?'
+                        | '@'
+                        | '['
+                        | '\\'
+                        | ']'
+                )
+            })
+            .is_some()
+        {
+            return Err(ParseError::InvalidDomainCharacter);
         }
         if let Some(address) = parse_ipv4addr(&domain)? {
             Ok(Host::Ipv4(address))
@@ -165,14 +181,31 @@ impl Host<String> {
     pub fn parse_opaque(input: &str) -> Result<Self, ParseError> {
         if input.starts_with('[') {
             if !input.ends_with(']') {
-                return Err(ParseError::InvalidIpv6Address)
+                return Err(ParseError::InvalidIpv6Address);
             }
-            return parse_ipv6addr(&input[1..input.len() - 1]).map(Host::Ipv6)
+            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)
+        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))
@@ -193,80 +226,6 @@ impl<S: AsRef<str>> fmt::Display for Host<S> {
     }
 }
 
-/// This mostly exists because coherence rules don’t allow us to implement
-/// `ToSocketAddrs for (Host<S>, u16)`.
-#[derive(Clone, Debug)]
-pub struct HostAndPort<S=String> {
-    pub host: Host<S>,
-    pub port: u16,
-}
-
-impl<'a> HostAndPort<&'a str> {
-    /// Return a copy of `self` that owns an allocated `String` but does not borrow an `&Url`.
-    pub fn to_owned(&self) -> HostAndPort<String> {
-        HostAndPort {
-            host: self.host.to_owned(),
-            port: self.port
-        }
-    }
-}
-
-impl<S: AsRef<str>> fmt::Display for HostAndPort<S> {
-    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
-        self.host.fmt(f)?;
-        f.write_str(":")?;
-        self.port.fmt(f)
-    }
-}
-
-
-impl<S: AsRef<str>> ToSocketAddrs for HostAndPort<S> {
-    type Iter = SocketAddrs;
-
-    fn to_socket_addrs(&self) -> io::Result<Self::Iter> {
-        let port = self.port;
-        match self.host {
-            Host::Domain(ref domain) => Ok(SocketAddrs {
-                // FIXME: use std::net::lookup_host when it’s stable.
-                state: SocketAddrsState::Domain((domain.as_ref(), port).to_socket_addrs()?)
-            }),
-            Host::Ipv4(address) => Ok(SocketAddrs {
-                state: SocketAddrsState::One(SocketAddr::V4(SocketAddrV4::new(address, port)))
-            }),
-            Host::Ipv6(address) => Ok(SocketAddrs {
-                state: SocketAddrsState::One(SocketAddr::V6(SocketAddrV6::new(address, port, 0, 0)))
-            }),
-        }
-    }
-}
-
-/// Socket addresses for an URL.
-#[derive(Debug)]
-pub struct SocketAddrs {
-    state: SocketAddrsState
-}
-
-#[derive(Debug)]
-enum SocketAddrsState {
-    Domain(vec::IntoIter<SocketAddr>),
-    One(SocketAddr),
-    Done,
-}
-
-impl Iterator for SocketAddrs {
-    type Item = SocketAddr;
-    fn next(&mut self) -> Option<SocketAddr> {
-        match self.state {
-            SocketAddrsState::Domain(ref mut iter) => iter.next(),
-            SocketAddrsState::One(s) => {
-                self.state = SocketAddrsState::Done;
-                Some(s)
-            }
-            SocketAddrsState::Done => None
-        }
-    }
-}
-
 fn write_ipv6(addr: &Ipv6Addr, f: &mut Formatter) -> fmt::Result {
     let segments = addr.segments();
     let (compress_start, compress_end) = longest_zero_sequence(&segments);
@@ -344,10 +303,12 @@ fn parse_ipv4number(mut input: &str) -> Result<Option<u32>, ()> {
     // 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
+        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 {
@@ -369,7 +330,7 @@ fn parse_ipv4number(mut input: &str) -> Result<Option<u32>, ()> {
 /// <https://url.spec.whatwg.org/#concept-ipv4-parser>
 fn parse_ipv4addr(input: &str) -> ParseResult<Option<Ipv4Addr>> {
     if input.is_empty() {
-        return Ok(None)
+        return Ok(None);
     }
     let mut parts: Vec<&str> = input.split('.').collect();
     if parts.last() == Some(&"") {
@@ -387,7 +348,7 @@ fn parse_ipv4addr(input: &str) -> ParseResult<Option<Ipv4Addr>> {
         match parse_ipv4number(part) {
             Ok(Some(n)) => numbers.push(n),
             Ok(None) => return Ok(None),
-            Err(()) => overflow = true
+            Err(()) => overflow = true,
         };
     }
     if overflow {
@@ -395,7 +356,7 @@ fn parse_ipv4addr(input: &str) -> ParseResult<Option<Ipv4Addr>> {
     }
     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)  {
+    if ipv4 > u32::max_value() >> (8 * numbers.len() as u32) {
         return Err(ParseError::InvalidIpv4Address);
     }
     if numbers.iter().any(|x| *x > 255) {
@@ -418,12 +379,12 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
     let mut i = 0;
 
     if len < 2 {
-        return Err(ParseError::InvalidIpv6Address)
+        return Err(ParseError::InvalidIpv6Address);
     }
 
     if input[0] == b':' {
         if input[1] != b':' {
-            return Err(ParseError::InvalidIpv6Address)
+            return Err(ParseError::InvalidIpv6Address);
         }
         i = 2;
         piece_pointer = 1;
@@ -432,16 +393,16 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
 
     while i < len {
         if piece_pointer == 8 {
-            return Err(ParseError::InvalidIpv6Address)
+            return Err(ParseError::InvalidIpv6Address);
         }
         if input[i] == b':' {
             if compress_pointer.is_some() {
-                return Err(ParseError::InvalidIpv6Address)
+                return Err(ParseError::InvalidIpv6Address);
             }
             i += 1;
             piece_pointer += 1;
             compress_pointer = Some(piece_pointer);
-            continue
+            continue;
         }
         let start = i;
         let end = cmp::min(len, start + 4);
@@ -451,33 +412,33 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
                 Some(digit) => {
                     value = value * 0x10 + digit as u16;
                     i += 1;
-                },
-                None => break
+                }
+                None => break,
             }
         }
         if i < len {
             match input[i] {
                 b'.' => {
                     if i == start {
-                        return Err(ParseError::InvalidIpv6Address)
+                        return Err(ParseError::InvalidIpv6Address);
                     }
                     i = start;
                     if piece_pointer > 6 {
-                        return Err(ParseError::InvalidIpv6Address)
+                        return Err(ParseError::InvalidIpv6Address);
                     }
                     is_ip_v4 = true;
-                },
+                }
                 b':' => {
                     i += 1;
                     if i == len {
-                        return Err(ParseError::InvalidIpv6Address)
+                        return Err(ParseError::InvalidIpv6Address);
                     }
-                },
-                _ => return Err(ParseError::InvalidIpv6Address)
+                }
+                _ => return Err(ParseError::InvalidIpv6Address),
             }
         }
         if is_ip_v4 {
-            break
+            break;
         }
         pieces[piece_pointer] = value;
         piece_pointer += 1;
@@ -485,7 +446,7 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
 
     if is_ip_v4 {
         if piece_pointer > 6 {
-            return Err(ParseError::InvalidIpv6Address)
+            return Err(ParseError::InvalidIpv6Address);
         }
         let mut numbers_seen = 0;
         while i < len {
@@ -493,23 +454,23 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
                 if numbers_seen < 4 && (i < len && input[i] == b'.') {
                     i += 1
                 } else {
-                    return Err(ParseError::InvalidIpv6Address)
+                    return Err(ParseError::InvalidIpv6Address);
                 }
             }
 
             let mut ipv4_piece = None;
             while i < len {
                 let digit = match input[i] {
-                    c @ b'0' ... b'9' => c - b'0',
-                    _ => break
+                    c @ b'0'..=b'9' => c - b'0',
+                    _ => break,
                 };
                 match ipv4_piece {
                     None => ipv4_piece = Some(digit as u16),
-                    Some(0) => return Err(ParseError::InvalidIpv6Address),  // No leading zero
+                    Some(0) => return Err(ParseError::InvalidIpv6Address), // No leading zero
                     Some(ref mut v) => {
                         *v = *v * 10 + digit as u16;
                         if *v > 255 {
-                            return Err(ParseError::InvalidIpv6Address)
+                            return Err(ParseError::InvalidIpv6Address);
                         }
                     }
                 }
@@ -519,7 +480,7 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
             pieces[piece_pointer] = if let Some(v) = ipv4_piece {
                 pieces[piece_pointer] * 0x100 + v
             } else {
-                return Err(ParseError::InvalidIpv6Address)
+                return Err(ParseError::InvalidIpv6Address);
             };
             numbers_seen += 1;
 
@@ -529,12 +490,12 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
         }
 
         if numbers_seen != 4 {
-            return Err(ParseError::InvalidIpv6Address)
+            return Err(ParseError::InvalidIpv6Address);
         }
     }
 
     if i < len {
-        return Err(ParseError::InvalidIpv6Address)
+        return Err(ParseError::InvalidIpv6Address);
     }
 
     match compress_pointer {
@@ -547,10 +508,13 @@ fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
                 piece_pointer -= 1;
             }
         }
-        _ => if piece_pointer != 8 {
-            return Err(ParseError::InvalidIpv6Address)
+        _ => {
+            if piece_pointer != 8 {
+                return Err(ParseError::InvalidIpv6Address);
+            }
         }
     }
-    Ok(Ipv6Addr::new(pieces[0], pieces[1], pieces[2], pieces[3],
-                     pieces[4], pieces[5], pieces[6], pieces[7]))
+    Ok(Ipv6Addr::new(
+        pieces[0], pieces[1], pieces[2], pieces[3], pieces[4], pieces[5], pieces[6], pieces[7],
+    ))
 }

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 280 - 259
src/lib.rs


+ 12 - 29
src/origin.rs

@@ -6,11 +6,10 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-#[cfg(feature = "heapsize")] use heapsize::HeapSizeOf;
 use host::Host;
 use idna::domain_to_unicode;
 use parser::default_port;
-use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
+use std::sync::atomic::{AtomicUsize, Ordering};
 use Url;
 
 pub fn url_origin(url: &Url) -> Origin {
@@ -20,16 +19,17 @@ pub fn url_origin(url: &Url) -> Origin {
             let result = Url::parse(url.path());
             match result {
                 Ok(ref url) => url_origin(url),
-                Err(_)  => Origin::new_opaque()
+                Err(_) => Origin::new_opaque(),
             }
-        },
-        "ftp" | "gopher" | "http" | "https" | "ws" | "wss" => {
-            Origin::Tuple(scheme.to_owned(), url.host().unwrap().to_owned(),
-                url.port_or_known_default().unwrap())
-        },
+        }
+        "ftp" | "gopher" | "http" | "https" | "ws" | "wss" => Origin::Tuple(
+            scheme.to_owned(),
+            url.host().unwrap().to_owned(),
+            url.port_or_known_default().unwrap(),
+        ),
         // TODO: Figure out what to do if the scheme is a file
         "file" => Origin::new_opaque(),
-        _ => Origin::new_opaque()
+        _ => Origin::new_opaque(),
     }
 }
 
@@ -56,27 +56,13 @@ pub enum Origin {
     Opaque(OpaqueOrigin),
 
     /// Consists of the URL's scheme, host and port
-    Tuple(String, Host<String>, u16)
-}
-
-#[cfg(feature = "heapsize")]
-impl HeapSizeOf for Origin {
-    fn heap_size_of_children(&self) -> usize {
-        match *self {
-            Origin::Tuple(ref scheme, ref host, _) => {
-                scheme.heap_size_of_children() +
-                host.heap_size_of_children()
-            },
-            _ => 0,
-        }
-    }
+    Tuple(String, Host<String>, u16),
 }
 
-
 impl Origin {
     /// Creates a new opaque origin that is only equal to itself.
     pub fn new_opaque() -> Origin {
-        static COUNTER: AtomicUsize = ATOMIC_USIZE_INIT;
+        static COUNTER: AtomicUsize = AtomicUsize::new(0);
         Origin::Opaque(OpaqueOrigin(COUNTER.fetch_add(1, Ordering::SeqCst)))
     }
 
@@ -110,7 +96,7 @@ impl Origin {
                         let (domain, _errors) = domain_to_unicode(domain);
                         Host::Domain(domain)
                     }
-                    _ => host.clone()
+                    _ => host.clone(),
                 };
                 if default_port(scheme) == Some(port) {
                     format!("{}://{}", scheme, host)
@@ -125,6 +111,3 @@ impl Origin {
 /// Opaque identifier for URLs that have file or other schemes
 #[derive(Eq, PartialEq, Hash, Clone, Debug)]
 pub struct OpaqueOrigin(usize);
-
-#[cfg(feature = "heapsize")]
-known_heap_size!(0, OpaqueOrigin);

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 298 - 225
src/parser.rs


+ 21 - 9
src/path_segments.rs

@@ -6,7 +6,7 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-use parser::{self, SchemeType, to_u32};
+use parser::{self, to_u32, SchemeType};
 use std::str;
 use Url;
 
@@ -56,7 +56,8 @@ pub fn new(url: &mut Url) -> PathSegmentsMut {
 
 impl<'a> Drop for PathSegmentsMut<'a> {
     fn drop(&mut self) {
-        self.url.restore_after_path(self.old_after_path_position, &self.after_path)
+        self.url
+            .restore_after_path(self.old_after_path_position, &self.after_path)
     }
 }
 
@@ -126,8 +127,12 @@ impl<'a> PathSegmentsMut<'a> {
     ///
     /// Returns `&mut Self` so that method calls can be chained.
     pub fn pop(&mut self) -> &mut Self {
-        let last_slash = self.url.serialization[self.after_first_slash..].rfind('/').unwrap_or(0);
-        self.url.serialization.truncate(self.after_first_slash + last_slash);
+        let last_slash = self.url.serialization[self.after_first_slash..]
+            .rfind('/')
+            .unwrap_or(0);
+        self.url
+            .serialization
+            .truncate(self.after_first_slash + last_slash);
         self
     }
 
@@ -194,7 +199,10 @@ impl<'a> PathSegmentsMut<'a> {
     /// # run().unwrap();
     /// ```
     pub fn extend<I>(&mut self, segments: I) -> &mut Self
-    where I: IntoIterator, I::Item: AsRef<str> {
+    where
+        I: IntoIterator,
+        I::Item: AsRef<str>,
+    {
         let scheme_type = SchemeType::from(self.url.scheme());
         let path_start = self.url.path_start as usize;
         self.url.mutate(|parser| {
@@ -202,14 +210,18 @@ impl<'a> PathSegmentsMut<'a> {
             for segment in segments {
                 let segment = segment.as_ref();
                 if matches!(segment, "." | "..") {
-                    continue
+                    continue;
                 }
                 if parser.serialization.len() > path_start + 1 {
                     parser.serialization.push('/');
                 }
-                let mut has_host = true;  // FIXME account for this?
-                parser.parse_path(scheme_type, &mut has_host, path_start,
-                                  parser::Input::new(segment));
+                let mut has_host = true; // FIXME account for this?
+                parser.parse_path(
+                    scheme_type,
+                    &mut has_host,
+                    path_start,
+                    parser::Input::new(segment),
+                );
             }
         });
         self

+ 35 - 0
src/query_encoding.rs

@@ -0,0 +1,35 @@
+// Copyright 2019 The rust-url developers.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::borrow::Cow;
+
+pub type EncodingOverride<'a> = Option<&'a dyn Fn(&str) -> Cow<[u8]>>;
+
+pub(crate) fn encode<'a>(encoding_override: EncodingOverride, input: &'a str) -> Cow<'a, [u8]> {
+    if let Some(o) = encoding_override {
+        return o(input);
+    }
+    input.as_bytes().into()
+}
+
+pub(crate) fn decode_utf8_lossy(input: Cow<[u8]>) -> Cow<str> {
+    match input {
+        Cow::Borrowed(bytes) => String::from_utf8_lossy(bytes),
+        Cow::Owned(bytes) => {
+            let raw_utf8: *const [u8];
+            match String::from_utf8_lossy(&bytes) {
+                Cow::Borrowed(utf8) => raw_utf8 = utf8.as_bytes(),
+                Cow::Owned(s) => return s.into(),
+            }
+            // from_utf8_lossy returned a borrow of `bytes` unchanged.
+            debug_assert!(raw_utf8 == &*bytes as *const [u8]);
+            // Reuse the existing `Vec` allocation.
+            unsafe { String::from_utf8_unchecked(bytes) }.into()
+        }
+    }
+}

+ 19 - 10
src/quirks.rs

@@ -11,8 +11,8 @@
 //! Unless you need to be interoperable with web browsers,
 //! you probably want to use `Url` method instead.
 
-use {Url, Position, Host, ParseError, idna};
-use parser::{Parser, SchemeType, default_port, Context, Input};
+use parser::{default_port, Context, Input, Parser, SchemeType};
+use {idna, Host, ParseError, Position, Url};
 
 /// https://url.spec.whatwg.org/#dom-url-domaintoascii
 pub fn domain_to_ascii(domain: &str) -> String {
@@ -84,7 +84,11 @@ pub fn password(url: &Url) -> &str {
 
 /// Setter for https://url.spec.whatwg.org/#dom-url-password
 pub fn set_password(url: &mut Url, new_password: &str) -> Result<(), ()> {
-    url.set_password(if new_password.is_empty() { None } else { Some(new_password) })
+    url.set_password(if new_password.is_empty() {
+        None
+    } else {
+        Some(new_password)
+    })
 }
 
 /// Getter for https://url.spec.whatwg.org/#dom-url-host
@@ -96,7 +100,7 @@ pub fn host(url: &Url) -> &str {
 /// Setter for https://url.spec.whatwg.org/#dom-url-host
 pub fn set_host(url: &mut Url, new_host: &str) -> Result<(), ()> {
     if url.cannot_be_a_base() {
-        return Err(())
+        return Err(());
     }
     let host;
     let opt_port;
@@ -108,12 +112,13 @@ pub fn set_host(url: &mut Url, new_host: &str) -> Result<(), ()> {
                 host = h;
                 opt_port = if let Some(remaining) = remaining.split_prefix(':') {
                     Parser::parse_port(remaining, || default_port(scheme), Context::Setter)
-                    .ok().map(|(port, _remaining)| port)
+                        .ok()
+                        .map(|(port, _remaining)| port)
                 } else {
                     None
                 };
             }
-            Err(_) => return Err(())
+            Err(_) => return Err(()),
         }
     }
     url.set_host_internal(host, opt_port);
@@ -129,7 +134,7 @@ pub fn hostname(url: &Url) -> &str {
 /// Setter for https://url.spec.whatwg.org/#dom-url-hostname
 pub fn set_hostname(url: &mut Url, new_hostname: &str) -> Result<(), ()> {
     if url.cannot_be_a_base() {
-        return Err(())
+        return Err(());
     }
     let result = Parser::parse_host(Input::new(new_hostname), SchemeType::from(url.scheme()));
     if let Ok((host, _remaining)) = result {
@@ -153,9 +158,13 @@ pub fn set_port(url: &mut Url, new_port: &str) -> Result<(), ()> {
         // has_host implies !cannot_be_a_base
         let scheme = url.scheme();
         if !url.has_host() || url.host() == Some(Host::Domain("")) || scheme == "file" {
-            return Err(())
+            return Err(());
         }
-        result = Parser::parse_port(Input::new(new_port), || default_port(scheme), Context::Setter)
+        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);
@@ -168,7 +177,7 @@ pub fn set_port(url: &mut Url, new_port: &str) -> Result<(), ()> {
 /// Getter for https://url.spec.whatwg.org/#dom-url-pathname
 #[inline]
 pub fn pathname(url: &Url) -> &str {
-     url.path()
+    url.path()
 }
 
 /// Setter for https://url.spec.whatwg.org/#dom-url-pathname

+ 37 - 32
src/slicing.rs

@@ -6,7 +6,7 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-use std::ops::{Range, RangeFrom, RangeTo, RangeFull, Index};
+use std::ops::{Index, Range, RangeFrom, RangeFull, RangeTo};
 use Url;
 
 impl Index<RangeFull> for Url {
@@ -94,7 +94,7 @@ pub enum Position {
     BeforeQuery,
     AfterQuery,
     BeforeFragment,
-    AfterFragment
+    AfterFragment,
 }
 
 impl Url {
@@ -105,43 +105,49 @@ impl Url {
 
             Position::AfterScheme => self.scheme_end as usize,
 
-            Position::BeforeUsername => if self.has_authority() {
-                self.scheme_end as usize + "://".len()
-            } else {
-                debug_assert!(self.byte_at(self.scheme_end) == b':');
-                debug_assert!(self.scheme_end + ":".len() as u32 == self.username_end);
-                self.scheme_end as usize + ":".len()
-            },
+            Position::BeforeUsername => {
+                if self.has_authority() {
+                    self.scheme_end as usize + "://".len()
+                } else {
+                    debug_assert!(self.byte_at(self.scheme_end) == b':');
+                    debug_assert!(self.scheme_end + ":".len() as u32 == self.username_end);
+                    self.scheme_end as usize + ":".len()
+                }
+            }
 
             Position::AfterUsername => self.username_end as usize,
 
-            Position::BeforePassword => if self.has_authority() &&
-                                           self.byte_at(self.username_end) == b':' {
-                self.username_end as usize + ":".len()
-            } else {
-                debug_assert!(self.username_end == self.host_start);
-                self.username_end as usize
-            },
-
-            Position::AfterPassword => if self.has_authority() &&
-                                          self.byte_at(self.username_end) == b':' {
-                debug_assert!(self.byte_at(self.host_start - "@".len() as u32) == b'@');
-                self.host_start as usize - "@".len()
-            } else {
-                debug_assert!(self.username_end == self.host_start);
-                self.host_start as usize
-            },
+            Position::BeforePassword => {
+                if self.has_authority() && self.byte_at(self.username_end) == b':' {
+                    self.username_end as usize + ":".len()
+                } else {
+                    debug_assert!(self.username_end == self.host_start);
+                    self.username_end as usize
+                }
+            }
+
+            Position::AfterPassword => {
+                if self.has_authority() && self.byte_at(self.username_end) == b':' {
+                    debug_assert!(self.byte_at(self.host_start - "@".len() as u32) == b'@');
+                    self.host_start as usize - "@".len()
+                } else {
+                    debug_assert!(self.username_end == self.host_start);
+                    self.host_start as usize
+                }
+            }
 
             Position::BeforeHost => self.host_start as usize,
 
             Position::AfterHost => self.host_end as usize,
 
-            Position::BeforePort => if self.port.is_some() {
-                debug_assert!(self.byte_at(self.host_end) == b':');
-                self.host_end as usize + ":".len()
-            } else {
-                self.host_end as usize
-            },
+            Position::BeforePort => {
+                if self.port.is_some() {
+                    debug_assert!(self.byte_at(self.host_end) == b':');
+                    self.host_end as usize + ":".len()
+                } else {
+                    self.host_end as usize
+                }
+            }
 
             Position::AfterPort => self.path_start as usize,
 
@@ -179,4 +185,3 @@ impl Url {
         }
     }
 }
-

+ 47 - 37
tests/data.rs

@@ -8,29 +8,29 @@
 
 //! Data-driven tests
 
-extern crate rustc_serialize;
 extern crate rustc_test as test;
+extern crate serde_json;
 extern crate url;
 
-use rustc_serialize::json::{self, Json};
-use url::{Url, quirks};
+use serde_json::Value;
+use std::str::FromStr;
+use url::{quirks, Url};
 
 fn check_invariants(url: &Url) {
     url.check_invariants().unwrap();
-    #[cfg(feature="serde")] {
-        extern crate serde_json;
+    #[cfg(feature = "serde")]
+    {
         let bytes = serde_json::to_vec(url).unwrap();
         let new_url: Url = serde_json::from_slice(&bytes).unwrap();
         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)
+        Err(message) => panic!("Error parsing base {:?}: {}", base, message),
     };
     let (url, expected) = match (base.join(&input), expected) {
         (Ok(url), Ok(expected)) => (url, expected),
@@ -42,14 +42,18 @@ fn run_parsing(input: &str, base: &str, expected: Result<ExpectedAttributes, ()>
     check_invariants(&url);
 
     macro_rules! assert_eq {
-        ($expected: expr, $got: expr) => {
-            {
-                let expected = $expected;
-                let got = $got;
-                assert!(expected == got, "{:?} != {} {:?} for URL {:?}",
-                        got, stringify!($expected), expected, url);
-            }
-        }
+        ($expected: expr, $got: expr) => {{
+            let expected = $expected;
+            let got = $got;
+            assert!(
+                expected == got,
+                "{:?} != {} {:?} for URL {:?}",
+                got,
+                stringify!($expected),
+                expected,
+                url
+            );
+        }};
     }
 
     macro_rules! assert_attributes {
@@ -84,46 +88,45 @@ struct ExpectedAttributes {
 }
 
 trait JsonExt {
-    fn take(&mut self, key: &str) -> Option<Json>;
-    fn object(self) -> json::Object;
+    fn take_key(&mut self, key: &str) -> Option<Value>;
     fn string(self) -> String;
     fn take_string(&mut self, key: &str) -> String;
 }
 
-impl JsonExt for Json {
-    fn take(&mut self, key: &str) -> Option<Json> {
+impl JsonExt for Value {
+    fn take_key(&mut self, key: &str) -> Option<Value> {
         self.as_object_mut().unwrap().remove(key)
     }
 
-    fn object(self) -> json::Object {
-        if let Json::Object(o) = self { o } else { panic!("Not a Json::Object") }
-    }
-
     fn string(self) -> String {
-        if let Json::String(s) = self { s } else { panic!("Not a Json::String") }
+        if let Value::String(s) = self {
+            s
+        } else {
+            panic!("Not a Value::String")
+        }
     }
 
     fn take_string(&mut self, key: &str) -> String {
-        self.take(key).unwrap().string()
+        self.take_key(key).unwrap().string()
     }
 }
 
 fn collect_parsing<F: FnMut(String, test::TestFn)>(add_test: &mut F) {
     // Copied form https://github.com/w3c/web-platform-tests/blob/master/url/
-    let mut json = Json::from_str(include_str!("urltestdata.json"))
+    let mut json = Value::from_str(include_str!("urltestdata.json"))
         .expect("JSON parse error in urltestdata.json");
     for entry in json.as_array_mut().unwrap() {
         if entry.is_string() {
-            continue  // ignore comments
+            continue; // ignore comments
         }
         let base = entry.take_string("base");
         let input = entry.take_string("input");
-        let expected = if entry.find("failure").is_some() {
+        let expected = if entry.take_key("failure").is_some() {
             Err(())
         } else {
             Ok(ExpectedAttributes {
                 href: entry.take_string("href"),
-                origin: entry.take("origin").map(Json::string),
+                origin: entry.take_key("origin").map(|s| s.string()),
                 protocol: entry.take_string("protocol"),
                 username: entry.take_string("username"),
                 password: entry.take_string("password"),
@@ -135,24 +138,31 @@ fn collect_parsing<F: FnMut(String, test::TestFn)>(add_test: &mut F) {
                 hash: entry.take_string("hash"),
             })
         };
-        add_test(format!("{:?} @ base {:?}", input, base),
-                 test::TestFn::dyn_test_fn(move || run_parsing(&input, &base, expected)));
+        add_test(
+            format!("{:?} @ base {:?}", input, base),
+            test::TestFn::dyn_test_fn(move || run_parsing(&input, &base, expected)),
+        );
     }
 }
 
-fn collect_setters<F>(add_test: &mut F) where F: FnMut(String, test::TestFn) {
-    let mut json = Json::from_str(include_str!("setters_tests.json"))
+fn collect_setters<F>(add_test: &mut F)
+where
+    F: FnMut(String, test::TestFn),
+{
+    let mut json = Value::from_str(include_str!("setters_tests.json"))
         .expect("JSON parse error in setters_tests.json");
 
     macro_rules! setter {
         ($attr: expr, $setter: ident) => {{
-            let mut tests = json.take($attr).unwrap();
+            let mut tests = json.take_key($attr).unwrap();
             for mut test in tests.as_array_mut().unwrap().drain(..) {
-                let comment = test.take("comment").map(Json::string).unwrap_or(String::new());
+                let comment = test.take_key("comment")
+                    .map(|s| s.string())
+                    .unwrap_or(String::new());
                 let href = test.take_string("href");
                 let new_value = test.take_string("new_value");
                 let name = format!("{:?}.{} = {:?} {}", href, $attr, new_value, comment);
-                let mut expected = test.take("expected").unwrap();
+                let mut expected = test.take_key("expected").unwrap();
                 add_test(name, test::TestFn::dyn_test_fn(move || {
                     let mut url = Url::parse(&href).unwrap();
                     check_invariants(&url);
@@ -167,7 +177,7 @@ fn collect_setters<F>(add_test: &mut F) where F: FnMut(String, test::TestFn) {
     macro_rules! assert_attributes {
         ($url: expr, $expected: expr, $($attr: ident)+) => {
             $(
-                if let Some(value) = $expected.take(stringify!($attr)) {
+                if let Some(value) = $expected.take_key(stringify!($attr)) {
                     assert_eq!(quirks::$attr(&$url), value.string())
                 }
             )+

+ 127 - 96
tests/unit.rs

@@ -8,15 +8,15 @@
 
 //! Unit tests
 
-#[macro_use]
 extern crate url;
+#[macro_use]
+extern crate percent_encoding;
 
-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};
+use url::{form_urlencoded, Host, Url};
 
 #[test]
 fn size() {
@@ -25,7 +25,9 @@ fn size() {
 }
 
 macro_rules! assert_from_file_path {
-    ($path: expr) => { assert_from_file_path!($path, $path) };
+    ($path: expr) => {
+        assert_from_file_path!($path, $path)
+    };
     ($path: expr, $url_path: expr) => {{
         let url = Url::from_file_path(Path::new($path)).unwrap();
         assert_eq!(url.host(), None);
@@ -34,8 +36,6 @@ macro_rules! assert_from_file_path {
     }};
 }
 
-
-
 #[test]
 fn new_file_paths() {
     if cfg!(unix) {
@@ -74,7 +74,10 @@ fn new_path_windows_fun() {
         assert_from_file_path!("C:\\foo\\ba\0r", "/C:/foo/ba%00r");
 
         // Invalid UTF-8
-        assert!(Url::parse("file:///C:/foo/ba%80r").unwrap().to_file_path().is_err());
+        assert!(Url::parse("file:///C:/foo/ba%80r")
+            .unwrap()
+            .to_file_path()
+            .is_err());
 
         // test windows canonicalized path
         let path = PathBuf::from(r"\\?\C:\foo\bar");
@@ -86,7 +89,6 @@ fn new_path_windows_fun() {
     }
 }
 
-
 #[test]
 fn new_directory_paths() {
     if cfg!(unix) {
@@ -100,7 +102,10 @@ fn new_directory_paths() {
     if cfg!(windows) {
         assert_eq!(Url::from_directory_path(Path::new("relative")), Err(()));
         assert_eq!(Url::from_directory_path(Path::new(r"..\relative")), Err(()));
-        assert_eq!(Url::from_directory_path(Path::new(r"\drive-relative")), Err(()));
+        assert_eq!(
+            Url::from_directory_path(Path::new(r"\drive-relative")),
+            Err(())
+        );
         assert_eq!(Url::from_directory_path(Path::new(r"\\ucn\")), Err(()));
 
         let url = Url::from_directory_path(Path::new(r"C:\foo\bar")).unwrap();
@@ -127,10 +132,16 @@ fn from_str() {
 
 #[test]
 fn parse_with_params() {
-    let url = Url::parse_with_params("http://testing.com/this?dont=clobberme",
-                                     &[("lang", "rust")]).unwrap();
+    let url = Url::parse_with_params(
+        "http://testing.com/this?dont=clobberme",
+        &[("lang", "rust")],
+    )
+    .unwrap();
 
-    assert_eq!(url.as_str(), "http://testing.com/this?dont=clobberme&lang=rust");
+    assert_eq!(
+        url.as_str(),
+        "http://testing.com/this?dont=clobberme&lang=rust"
+    );
 }
 
 #[test]
@@ -145,8 +156,8 @@ fn issue_124() {
 
 #[test]
 fn test_equality() {
-    use std::hash::{Hash, Hasher};
     use std::collections::hash_map::DefaultHasher;
+    use std::hash::{Hash, Hasher};
 
     fn check_eq(a: &Url, b: &Url) {
         assert_eq!(a, b);
@@ -196,13 +207,29 @@ fn host() {
         assert_eq!(Url::parse(input).unwrap().host(), Some(host));
     }
     assert_host("http://www.mozilla.org", Host::Domain("www.mozilla.org"));
-    assert_host("http://1.35.33.49", Host::Ipv4(Ipv4Addr::new(1, 35, 33, 49)));
-    assert_host("http://[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]", Host::Ipv6(Ipv6Addr::new(
-        0x2001, 0x0db8, 0x85a3, 0x08d3, 0x1319, 0x8a2e, 0x0370, 0x7344)));
+    assert_host(
+        "http://1.35.33.49",
+        Host::Ipv4(Ipv4Addr::new(1, 35, 33, 49)),
+    );
+    assert_host(
+        "http://[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]",
+        Host::Ipv6(Ipv6Addr::new(
+            0x2001, 0x0db8, 0x85a3, 0x08d3, 0x1319, 0x8a2e, 0x0370, 0x7344,
+        )),
+    );
     assert_host("http://1.35.+33.49", Host::Domain("1.35.+33.49"));
-    assert_host("http://[::]", Host::Ipv6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)));
-    assert_host("http://[::1]", Host::Ipv6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)));
-    assert_host("http://0x1.0X23.0x21.061", Host::Ipv4(Ipv4Addr::new(1, 35, 33, 49)));
+    assert_host(
+        "http://[::]",
+        Host::Ipv6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)),
+    );
+    assert_host(
+        "http://[::1]",
+        Host::Ipv6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
+    );
+    assert_host(
+        "http://0x1.0X23.0x21.061",
+        Host::Ipv4(Ipv4Addr::new(1, 35, 33, 49)),
+    );
     assert_host("http://0x1232131", Host::Ipv4(Ipv4Addr::new(1, 35, 33, 49)));
     assert_host("http://111", Host::Ipv4(Ipv4Addr::new(0, 0, 0, 111)));
     assert_host("http://2..2.3", Host::Domain("2..2.3"));
@@ -217,15 +244,26 @@ fn host_serialization() {
     // but https://url.spec.whatwg.org/#concept-ipv6-serializer specifies not to.
 
     // Not [::0.0.0.2] / [::ffff:0.0.0.2]
-    assert_eq!(Url::parse("http://[0::2]").unwrap().host_str(), Some("[::2]"));
-    assert_eq!(Url::parse("http://[0::ffff:0:2]").unwrap().host_str(), Some("[::ffff:0:2]"));
+    assert_eq!(
+        Url::parse("http://[0::2]").unwrap().host_str(),
+        Some("[::2]")
+    );
+    assert_eq!(
+        Url::parse("http://[0::ffff:0:2]").unwrap().host_str(),
+        Some("[::ffff:0:2]")
+    );
 }
 
 #[test]
 fn test_idna() {
     assert!("http://goșu.ro".parse::<Url>().is_ok());
-    assert_eq!(Url::parse("http://☃.net/").unwrap().host(), Some(Host::Domain("xn--n3h.net")));
-    assert!("https://r2---sn-huoa-cvhl.googlevideo.com/crossdomain.xml".parse::<Url>().is_ok());
+    assert_eq!(
+        Url::parse("http://☃.net/").unwrap().host(),
+        Some(Host::Domain("xn--n3h.net"))
+    );
+    assert!("https://r2---sn-huoa-cvhl.googlevideo.com/crossdomain.xml"
+        .parse::<Url>()
+        .is_ok());
 }
 
 #[test]
@@ -236,9 +274,18 @@ fn test_serialization() {
         ("http://@emptyuser.com/", "http://emptyuser.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")
+        (
+            "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();
@@ -251,11 +298,16 @@ fn test_form_urlencoded() {
     let pairs: &[(Cow<str>, Cow<str>)] = &[
         ("foo".into(), "é&".into()),
         ("bar".into(), "".into()),
-        ("foo".into(), "#".into())
+        ("foo".into(), "#".into()),
     ];
-    let encoded = form_urlencoded::Serializer::new(String::new()).extend_pairs(pairs).finish();
+    let encoded = form_urlencoded::Serializer::new(String::new())
+        .extend_pairs(pairs)
+        .finish();
     assert_eq!(encoded, "foo=%C3%A9%26&bar=&foo=%23");
-    assert_eq!(form_urlencoded::parse(encoded.as_bytes()).collect::<Vec<_>>(), pairs.to_vec());
+    assert_eq!(
+        form_urlencoded::parse(encoded.as_bytes()).collect::<Vec<_>>(),
+        pairs.to_vec()
+    );
 }
 
 #[test]
@@ -269,44 +321,14 @@ fn test_form_serialize() {
 }
 
 #[test]
-fn form_urlencoded_custom_encoding_override() {
+fn form_urlencoded_encoding_override() {
     let encoded = form_urlencoded::Serializer::new(String::new())
-        .custom_encoding_override(|s| s.as_bytes().to_ascii_uppercase().into())
+        .encoding_override(Some(&|s| s.as_bytes().to_ascii_uppercase().into()))
         .append_pair("foo", "bar")
         .finish();
     assert_eq!(encoded, "FOO=BAR");
 }
 
-#[test]
-fn host_and_port_display() {
-    assert_eq!(
-        format!(
-            "{}",
-            HostAndPort{ host: Host::Domain("www.mozilla.org"), port: 80}
-        ),
-        "www.mozilla.org:80"
-    );
-    assert_eq!(
-        format!(
-            "{}",
-            HostAndPort::<String>{ host: Host::Ipv4(Ipv4Addr::new(1, 35, 33, 49)), port: 65535 }
-        ),
-        "1.35.33.49:65535"
-    );
-    assert_eq!(
-        format!(
-            "{}",
-            HostAndPort::<String>{
-                host: Host::Ipv6(Ipv6Addr::new(
-                    0x2001, 0x0db8, 0x85a3, 0x08d3, 0x1319, 0x8a2e, 0x0370, 0x7344
-                )),
-                port: 1337
-            })
-        ,
-        "[2001:db8:85a3:8d3:1319:8a2e:370:7344]:1337"
-    )
-}
-
 #[test]
 /// https://github.com/servo/rust-url/issues/61
 fn issue_61() {
@@ -323,8 +345,13 @@ fn issue_61() {
 fn issue_197() {
     let mut url = Url::from_file_path("/").expect("Failed to parse path");
     url.check_invariants().unwrap();
-    assert_eq!(url, Url::parse("file:///").expect("Failed to parse path + protocol"));
-    url.path_segments_mut().expect("path_segments_mut").pop_if_empty();
+    assert_eq!(
+        url,
+        Url::parse("file:///").expect("Failed to parse path + protocol")
+    );
+    url.path_segments_mut()
+        .expect("path_segments_mut")
+        .pop_if_empty();
 }
 
 #[test]
@@ -346,12 +373,19 @@ fn append_trailing_slash() {
 /// https://github.com/servo/rust-url/issues/227
 fn extend_query_pairs_then_mutate() {
     let mut url: Url = "http://localhost:6767/foo/bar".parse().unwrap();
-    url.query_pairs_mut().extend_pairs(vec![ ("auth", "my-token") ].into_iter());
+    url.query_pairs_mut()
+        .extend_pairs(vec![("auth", "my-token")].into_iter());
     url.check_invariants().unwrap();
-    assert_eq!(url.to_string(), "http://localhost:6767/foo/bar?auth=my-token");
+    assert_eq!(
+        url.to_string(),
+        "http://localhost:6767/foo/bar?auth=my-token"
+    );
     url.path_segments_mut().unwrap().push("some_other_path");
     url.check_invariants().unwrap();
-    assert_eq!(url.to_string(), "http://localhost:6767/foo/bar/some_other_path?auth=my-token");
+    assert_eq!(
+        url.to_string(),
+        "http://localhost:6767/foo/bar/some_other_path?auth=my-token"
+    );
 }
 
 #[test]
@@ -388,7 +422,10 @@ fn test_set_host() {
 #[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!(
+        Host::parse(".org").unwrap(),
+        Host::Domain(".org".to_owned())
+    );
     assert_eq!(Url::parse("file://./foo").unwrap().domain(), Some("."));
 }
 
@@ -396,17 +433,20 @@ fn test_leading_dots() {
 // inside both a module and a function
 #[test]
 fn define_encode_set_scopes() {
-    use url::percent_encoding::{utf8_percent_encode, SIMPLE_ENCODE_SET};
+    use percent_encoding::{utf8_percent_encode, SIMPLE_ENCODE_SET};
 
     define_encode_set! {
         /// This encode set is used in the URL parser for query strings.
         pub QUERY_ENCODE_SET = [SIMPLE_ENCODE_SET] | {' ', '"', '#', '<', '>'}
     }
 
-    assert_eq!(utf8_percent_encode("foo bar", QUERY_ENCODE_SET).collect::<String>(), "foo%20bar");
+    assert_eq!(
+        utf8_percent_encode("foo bar", QUERY_ENCODE_SET).collect::<String>(),
+        "foo%20bar"
+    );
 
     mod m {
-        use url::percent_encoding::{utf8_percent_encode, SIMPLE_ENCODE_SET};
+        use percent_encoding::{utf8_percent_encode, SIMPLE_ENCODE_SET};
 
         define_encode_set! {
             /// This encode set is used in the URL parser for query strings.
@@ -414,7 +454,10 @@ fn define_encode_set_scopes() {
         }
 
         pub fn test() {
-            assert_eq!(utf8_percent_encode("foo bar", QUERY_ENCODE_SET).collect::<String>(), "foo%20bar");
+            assert_eq!(
+                utf8_percent_encode("foo bar", QUERY_ENCODE_SET).collect::<String>(),
+                "foo%20bar"
+            );
         }
     }
 
@@ -424,8 +467,8 @@ fn define_encode_set_scopes() {
 #[test]
 /// https://github.com/servo/rust-url/issues/302
 fn test_origin_hash() {
-    use std::hash::{Hash,Hasher};
     use std::collections::hash_map::DefaultHasher;
+    use std::hash::{Hash, Hasher};
 
     fn hash<T: Hash>(value: &T) -> u64 {
         let mut hasher = DefaultHasher::new();
@@ -444,7 +487,9 @@ fn test_origin_hash() {
         Url::parse("ftp://example.net").unwrap().origin(),
         Url::parse("file://example.net").unwrap().origin(),
         Url::parse("http://user@example.net/").unwrap().origin(),
-        Url::parse("http://user:pass@example.net/").unwrap().origin(),
+        Url::parse("http://user:pass@example.net/")
+            .unwrap()
+            .origin(),
     ];
 
     for origin_to_compare in &origins_to_compare {
@@ -466,7 +511,7 @@ fn test_origin_hash() {
 #[test]
 fn test_windows_unc_path() {
     if !cfg!(windows) {
-        return
+        return;
     }
 
     let url = Url::from_file_path(Path::new(r"\\host\share\path\file.txt")).unwrap();
@@ -490,28 +535,14 @@ fn test_windows_unc_path() {
     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();
+        .parse("http:////mozilla.org:42")
+        .unwrap();
     assert_eq!(url.port(), Some(42));
 
     let v = violation.take().unwrap();
@@ -527,13 +558,15 @@ fn test_syntax_violation_callback_lifetimes() {
 
     let url = Url::options()
         .syntax_violation_callback(Some(&vfn))
-        .parse("http:////mozilla.org:42").unwrap();
+        .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();
+        .parse("http://mozilla.org\\path")
+        .unwrap();
     assert_eq!(url.path(), "/path");
     assert_eq!(violation.take(), Some(Backslash));
 }
@@ -544,13 +577,11 @@ fn test_options_reuse() {
     let violations = RefCell::new(Vec::new());
     let vfn = |v| violations.borrow_mut().push(v);
 
-    let options = Url::options()
-        .syntax_violation_callback(Some(&vfn));
+    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));
+    assert_eq!(*violations.borrow(), vec!(ExpectedDoubleSlash, Backslash));
 }

+ 0 - 23
url_serde/Cargo.toml

@@ -1,23 +0,0 @@
-[package]
-
-name = "url_serde"
-version = "0.2.0"
-authors = ["The rust-url developers"]
-
-description = "Serde support for URL types"
-documentation = "https://docs.rs/url_serde/"
-repository = "https://github.com/servo/rust-url"
-readme = "README.md"
-keywords = ["url", "serde"]
-license = "MIT/Apache-2.0"
-
-[dependencies]
-serde = "1.0"
-url = {version = "1.0.0", path = ".."}
-
-[dev-dependencies]
-serde_json = "1.0"
-serde_derive = "1.0"
-
-[lib]
-doctest = false

+ 0 - 1
url_serde/LICENSE-APACHE

@@ -1 +0,0 @@
-../LICENSE-APACHE

+ 0 - 1
url_serde/LICENSE-MIT

@@ -1 +0,0 @@
-../LICENSE-MIT

+ 0 - 11
url_serde/README.md

@@ -1,11 +0,0 @@
-Serde support for rust-url types
-================================
-
-This crate provides wrappers and convenience functions to make `rust-url` and `serde`
-work hand in hand.
-
-Version `0.2` or newer of this crate offer support for `serde 1.0`.
-Version `0.1` of this crate offer support for `serde 0.9`.
-Versions of `serde` older than `0.9` are natively supported by `rust-url` crate directly.
-
-For more details, see the crate [documentation](https://docs.rs/url_serde/).

+ 0 - 421
url_serde/src/lib.rs

@@ -1,421 +0,0 @@
-// Copyright 2017 The rust-url developers.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-/*!
-
-This crate provides wrappers and convenience functions to make rust-url
-and Serde work hand in hand.
-
-The supported types are:
-
-* `url::Url`
-
-# How do I use a data type with a `Url` member with Serde?
-
-Use the serde attributes `deserialize_with` and `serialize_with`.
-
-```
-#[derive(serde::Serialize, serde::Deserialize)]
-struct MyStruct {
-    #[serde(serialize_with = "serialize")]
-    url: Url,
-}
-```
-
-# How do I use a data type with an unnamed `Url` member with serde?
-
-Same problem, same solution.
-
-```
-#[derive(serde::Serialize, serde::Deserialize)]
-enum MyEnum {
-    A(#[serde(with = "url_serde")] Url, OtherType),
-}
-```
-
-# How do I encode a `Url` value with `serde_json::to_string`?
-
-Use the `Ser` wrapper.
-
-```
-serde_json::to_string(&Ser::new(&url))
-```
-
-# How do I decode a `Url` value with `serde_json::parse`?
-
-Use the `De` wrapper.
-
-```
-serde_json::from_str(r"http:://www.rust-lang.org").map(De::into_inner)
-```
-
-# How do I send `Url` values as part of an IPC channel?
-
-Use the `Serde` wrapper. It implements `Deref` and `DerefMut` for convenience.
-
-```
-ipc::channel::<Serde<Url>>()
-```
-*/
-
-#![deny(missing_docs)]
-#![deny(unsafe_code)]
-
-extern crate serde;
-#[cfg(test)] #[macro_use] extern crate serde_derive;
-#[cfg(test)] extern crate serde_json;
-extern crate url;
-
-use serde::{Deserialize, Serialize, Serializer, Deserializer};
-use std::cmp::PartialEq;
-use std::error::Error;
-use std::fmt;
-use std::io::Write;
-use std::ops::{Deref, DerefMut};
-use std::str;
-use url::{Url, Host};
-
-/// Serialises `value` with a given serializer.
-///
-/// This is useful to serialize `rust-url` types used in structure fields or
-/// tuple members with `#[serde(serialize_with = "url_serde::serialize")]`.
-pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
-    where S: Serializer, for<'a> Ser<'a, T>: Serialize
-{
-    Ser::new(value).serialize(serializer)
-}
-
-/// A wrapper to serialize `rust-url` types.
-///
-/// This is useful with functions such as `serde_json::to_string`.
-///
-/// Values of this type can only be passed to the `serde::Serialize` trait.
-#[derive(Debug)]
-pub struct Ser<'a, T: 'a>(&'a T);
-
-impl<'a, T> Ser<'a, T> where Ser<'a, T>: Serialize {
-    /// Returns a new `Ser` wrapper.
-    #[inline(always)]
-    pub fn new(value: &'a T) -> Self {
-        Ser(value)
-    }
-}
-
-/// Serializes this URL into a `serde` stream.
-impl<'a> Serialize for Ser<'a, Url> {
-    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
-        serializer.serialize_str(self.0.as_str())
-    }
-}
-
-/// Serializes this Option<URL> into a `serde` stream.
-impl<'a> Serialize for Ser<'a, Option<Url>> {
-    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
-        if let Some(url) = self.0.as_ref() {
-            serializer.serialize_some(url.as_str())
-        } else {
-            serializer.serialize_none()
-        }
-    }
-}
-
-impl<'a, String> Serialize for Ser<'a, Host<String>> where String: AsRef<str> {
-    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
-        match *self.0 {
-            Host::Domain(ref s) => serializer.serialize_str(s.as_ref()),
-            Host::Ipv4(_) | Host::Ipv6(_) => {
-                // max("101.102.103.104".len(),
-                //     "[1000:1002:1003:1004:1005:1006:101.102.103.104]".len())
-                const MAX_LEN: usize = 47;
-                let mut buffer = [0; MAX_LEN];
-                serializer.serialize_str(display_into_buffer(&self.0, &mut buffer))
-            }
-        }
-    }
-}
-
-/// Like .to_string(), but doesn’t allocate memory for a `String`.
-///
-/// Panics if `buffer` is too small.
-fn display_into_buffer<'a, T: fmt::Display>(value: &T, buffer: &'a mut [u8]) -> &'a str {
-    let remaining_len;
-    {
-        let mut remaining = &mut *buffer;
-        write!(remaining, "{}", value).unwrap();
-        remaining_len = remaining.len()
-    }
-    let written_len = buffer.len() - remaining_len;
-    let written = &buffer[..written_len];
-
-    // write! only provides std::fmt::Formatter to Display implementations,
-    // which has methods write_str and write_char but no method to write arbitrary bytes.
-    // Therefore, `written` is well-formed in UTF-8.
-    #[allow(unsafe_code)]
-    unsafe {
-        str::from_utf8_unchecked(written)
-    }
-}
-
-/// Deserialises a `T` value with a given deserializer.
-///
-/// This is useful to deserialize Url types used in structure fields or
-/// tuple members with `#[serde(deserialize_with = "url_serde::deserialize")]`.
-pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
-    where D: Deserializer<'de>, De<T>: Deserialize<'de>
-{
-    De::deserialize(deserializer).map(De::into_inner)
-}
-
-/// A wrapper to deserialize `rust-url` types.
-///
-/// This is useful with functions such as `serde_json::from_str`.
-///
-/// Values of this type can only be obtained through
-/// the `serde::Deserialize` trait.
-#[derive(Debug)]
-pub struct De<T>(T);
-
-impl<'de, T> De<T> where De<T>: serde::Deserialize<'de> {
-    /// Consumes this wrapper, returning the deserialized value.
-    #[inline(always)]
-    pub fn into_inner(self) -> T {
-        self.0
-    }
-}
-
-/// Deserializes this URL from a `serde` stream.
-impl<'de> Deserialize<'de> for De<Url> {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
-        let string_representation: String = Deserialize::deserialize(deserializer)?;
-        Url::parse(&string_representation).map(De).map_err(|err| {
-            serde::de::Error::custom(err.description())
-        })
-    }
-}
-
-/// Deserializes this Option<URL> from a `serde` stream.
-impl<'de> Deserialize<'de> for De<Option<Url>> {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
-        let option_representation: Option<String> = Deserialize::deserialize(deserializer)?;
-        if let Some(s) = option_representation {
-            return Url::parse(&s)
-                .map(Some)
-                .map(De)
-                .map_err(|err| {serde::de::Error::custom(err.description())});
-        }
-        Ok(De(None))
-
-    }
-}
-
-impl<'de> Deserialize<'de> for De<Host> {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
-        let string_representation: String = Deserialize::deserialize(deserializer)?;
-        Host::parse(&string_representation).map(De).map_err(|err| {
-            serde::de::Error::custom(err.description())
-        })
-    }
-}
-
-/// A convenience wrapper to be used as a type parameter, for example when
-/// a `Vec<T>` or an `HashMap<K, V>` need to be passed to serde.
-#[derive(Clone, Eq, Hash, PartialEq)]
-pub struct Serde<T>(pub T);
-
-/// A convenience type alias for Serde<Url>.
-pub type SerdeUrl = Serde<Url>;
-
-impl<'de, T> Serde<T>
-where De<T>: Deserialize<'de>, for<'a> Ser<'a, T>: Serialize
-{
-    /// Consumes this wrapper, returning the inner value.
-    #[inline(always)]
-    pub fn into_inner(self) -> T {
-        self.0
-    }
-}
-
-impl<'de, T> fmt::Debug for Serde<T>
-where T: fmt::Debug, De<T>: Deserialize<'de>, for<'a> Ser<'a, T>: Serialize
-{
-    fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
-        self.0.fmt(formatter)
-    }
-}
-
-impl<'de, T> Deref for Serde<T>
-where De<T>: Deserialize<'de>, for<'a> Ser<'a, T>: Serialize
-{
-    type Target = T;
-
-    fn deref(&self) -> &T {
-        &self.0
-    }
-}
-
-impl<'de, T> DerefMut for Serde<T>
-where De<T>: Deserialize<'de>, for<'a> Ser<'a, T>: Serialize
-{
-    fn deref_mut(&mut self) -> &mut T {
-        &mut self.0
-    }
-}
-
-impl<'de, T: PartialEq> PartialEq<T> for Serde<T>
-where De<T>: Deserialize<'de>, for<'a> Ser<'a, T>: Serialize
-{
-    fn eq(&self, other: &T) -> bool {
-        self.0 == *other
-    }
-}
-
-impl<'de, T> Deserialize<'de> for Serde<T>
-where De<T>: Deserialize<'de>, for<'a> Ser<'a, T>: Serialize
-{
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-        where D: Deserializer<'de>
-    {
-        De::deserialize(deserializer).map(De::into_inner).map(Serde)
-    }
-}
-
-impl<'de, T> Serialize for Serde<T>
-where De<T>: Deserialize<'de>, for<'a> Ser<'a, T>: Serialize
-{
-    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-        where S: Serializer
-    {
-        Ser(&self.0).serialize(serializer)
-    }
-}
-
-#[test]
-fn test_ser_de_url() {
-    let url = Url::parse("http://www.test.com/foo/bar?$param=bazz").unwrap();
-    let s = serde_json::to_string(&Ser::new(&url)).unwrap();
-    let new_url: Url = serde_json::from_str(&s).map(De::into_inner).unwrap();
-    assert_eq!(url, new_url);
-}
-
-#[test]
-fn test_derive_deserialize_with_for_url() {
-    #[derive(Deserialize, Debug, Eq, PartialEq)]
-    struct Test {
-        #[serde(deserialize_with = "deserialize", rename = "_url_")]
-        url: Url
-    }
-
-    let url_str = "http://www.test.com/foo/bar?$param=bazz";
-
-    let expected = Test {
-        url: Url::parse(url_str).unwrap()
-    };
-    let json_string = format!(r#"{{"_url_": "{}"}}"#, url_str);
-    let got: Test = serde_json::from_str(&json_string).unwrap();
-    assert_eq!(expected, got);
-
-}
-
-#[test]
-fn test_derive_deserialize_with_for_option_url() {
-    #[derive(Deserialize, Debug, Eq, PartialEq)]
-    struct Test {
-        #[serde(deserialize_with = "deserialize", rename = "_url_")]
-        url: Option<Url>
-    }
-
-    let url_str = "http://www.test.com/foo/bar?$param=bazz";
-
-    let expected = Test {
-        url: Some(Url::parse(url_str).unwrap())
-    };
-    let json_string = format!(r#"{{"_url_": "{}"}}"#, url_str);
-    let got: Test = serde_json::from_str(&json_string).unwrap();
-    assert_eq!(expected, got);
-
-    let expected = Test {
-        url: None
-    };
-    let json_string = r#"{"_url_": null}"#;
-    let got: Test = serde_json::from_str(&json_string).unwrap();
-    assert_eq!(expected, got);
-}
-
-#[test]
-fn test_derive_serialize_with_for_url() {
-    #[derive(Serialize, Debug, Eq, PartialEq)]
-    struct Test {
-        #[serde(serialize_with = "serialize", rename = "_url_")]
-        url: Url
-    }
-
-    let url_str = "http://www.test.com/foo/bar?$param=bazz";
-
-    let expected = format!(r#"{{"_url_":"{}"}}"#, url_str);
-    let input = Test {url: Url::parse(url_str).unwrap()};
-    let got = serde_json::to_string(&input).unwrap();
-    assert_eq!(expected, got);
-}
-
-#[test]
-fn test_derive_serialize_with_for_option_url() {
-    #[derive(Serialize, Debug, Eq, PartialEq)]
-    struct Test {
-        #[serde(serialize_with = "serialize", rename = "_url_")]
-        url: Option<Url>
-    }
-
-    let url_str = "http://www.test.com/foo/bar?$param=bazz";
-
-    let expected = format!(r#"{{"_url_":"{}"}}"#, url_str);
-    let input = Test {url: Some(Url::parse(url_str).unwrap())};
-    let got = serde_json::to_string(&input).unwrap();
-    assert_eq!(expected, got);
-
-    let expected = format!(r#"{{"_url_":null}}"#);
-    let input = Test {url: None};
-    let got = serde_json::to_string(&input).unwrap();
-    assert_eq!(expected, got);
-}
-
-#[test]
-fn test_derive_with_for_url() {
-    #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
-    struct Test {
-        #[serde(with = "self", rename = "_url_")]
-        url: Url
-    }
-
-    let url_str = "http://www.test.com/foo/bar?$param=bazz";
-    let json_string = format!(r#"{{"_url_":"{}"}}"#, url_str);
-
-    // test deserialization
-    let expected = Test {
-        url: Url::parse(url_str).unwrap()
-    };
-    let got: Test = serde_json::from_str(&json_string).unwrap();
-    assert_eq!(expected, got);
-
-    // test serialization
-    let input = Test {url: Url::parse(url_str).unwrap()};
-    let got = serde_json::to_string(&input).unwrap();
-    assert_eq!(json_string, got);
-}
-
-#[test]
-fn test_host() {
-    for host in &[
-        Host::Domain("foo.com".to_owned()),
-        Host::Ipv4("127.0.0.1".parse().unwrap()),
-        Host::Ipv6("::1".parse().unwrap()),
-    ] {
-        let json = serde_json::to_string(&Ser(host)).unwrap();
-        let de: De<Host> = serde_json::from_str(&json).unwrap();
-        assert_eq!(de.into_inner(), *host)
-    }
-}

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.