Repository: servo/rust-url
Pull request #655 (2020-12-02 09:08:08)

Fix out of bounds slicing in Url::username()


Patch:
diff --git a/idna/Cargo.toml b/idna/Cargo.toml
index 66321af..a662ab3 100644
--- a/idna/Cargo.toml
+++ b/idna/Cargo.toml
@@ -19,7 +19,6 @@ harness = false
 name = "unit"
 
 [dev-dependencies]
-assert_matches = "1.3"
 bencher = "0.1"
 rustc-test = "0.3"
 serde_json = "1.0"
diff --git a/idna/src/lib.rs b/idna/src/lib.rs
index b87d4a1..7375c63 100644
--- a/idna/src/lib.rs
+++ b/idna/src/lib.rs
@@ -38,7 +38,7 @@ extern crate matches;
 pub mod punycode;
 mod uts46;
 
-pub use crate::uts46::{Config, Errors, Idna};
+pub use crate::uts46::{Config, Errors};
 
 /// The [domain to ASCII](https://url.spec.whatwg.org/#concept-domain-to-ascii) algorithm.
 ///
diff --git a/idna/src/punycode.rs b/idna/src/punycode.rs
index a237504..07445cd 100644
--- a/idna/src/punycode.rs
+++ b/idna/src/punycode.rs
@@ -52,157 +52,81 @@ pub fn decode_to_string(input: &str) -> Option<String> {
 /// Overflow can only happen on inputs that take more than
 /// 63 encoded bytes, the DNS limit on domain name labels.
 pub fn decode(input: &str) -> Option<Vec<char>> {
-    Some(Decoder::default().decode(input).ok()?.collect())
-}
-
-#[derive(Default)]
-pub(crate) struct Decoder {
-    insertions: Vec<(usize, char)>,
-}
-
-impl Decoder {
-    /// Split the input iterator and return a Vec with insertions of encoded characters
-    pub(crate) fn decode<'a>(&'a mut self, input: &'a str) -> Result<Decode<'a>, ()> {
-        self.insertions.clear();
-        // Handle "basic" (ASCII) code points.
-        // They are encoded as-is before the last delimiter, if any.
-        let (base, input) = match input.rfind(DELIMITER) {
-            None => ("", input),
-            Some(position) => (
-                &input[..position],
-                if position > 0 {
-                    &input[position + 1..]
-                } else {
-                    input
-                },
-            ),
+    // Handle "basic" (ASCII) code points.
+    // They are encoded as-is before the last delimiter, if any.
+    let (mut output, input) = match input.rfind(DELIMITER) {
+        None => (Vec::new(), input),
+        Some(position) => (
+            input[..position].chars().collect(),
+            if position > 0 {
+                &input[position + 1..]
+            } else {
+                input
+            },
+        ),
+    };
+    let mut code_point = INITIAL_N;
+    let mut bias = INITIAL_BIAS;
+    let mut i = 0;
+    let mut iter = input.bytes();
+    loop {
+        let previous_i = i;
+        let mut weight = 1;
+        let mut k = BASE;
+        let mut byte = match iter.next() {
+            None => break,
+            Some(byte) => byte,
         };
-
-        let base_len = base.len();
-        let mut length = base_len as u32;
-        let mut code_point = INITIAL_N;
-        let mut bias = INITIAL_BIAS;
-        let mut i = 0;
-        let mut iter = input.bytes();
+        // Decode a generalized variable-length integer into delta,
+        // which gets added to i.
         loop {
-            let previous_i = i;
-            let mut weight = 1;
-            let mut k = BASE;
-            let mut byte = match iter.next() {
-                None => break,
-                Some(byte) => byte,
+            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,
+            } as u32;
+            if digit > (u32::MAX - i) / weight {
+                return None; // Overflow
+            }
+            i += digit * weight;
+            let t = if k <= bias {
+                T_MIN
+            } else if k >= bias + T_MAX {
+                T_MAX
+            } else {
+                k - bias
             };
-
-            // Decode a generalized variable-length integer into delta,
-            // 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 Err(()),
-                } as u32;
-                if digit > (u32::MAX - i) / weight {
-                    return Err(()); // Overflow
-                }
-                i += digit * weight;
-                let t = if k <= bias {
-                    T_MIN
-                } else if k >= bias + T_MAX {
-                    T_MAX
-                } else {
-                    k - bias
-                };
-                if digit < t {
-                    break;
-                }
-                if weight > u32::MAX / (BASE - t) {
-                    return Err(()); // Overflow
-                }
-                weight *= BASE - t;
-                k += BASE;
-                byte = match iter.next() {
-                    None => return Err(()), // End of input before the end of this delta
-                    Some(byte) => byte,
-                };
+            if digit < t {
+                break;
             }
-
-            bias = adapt(i - previous_i, length + 1, previous_i == 0);
-            if i / (length + 1) > u32::MAX - code_point {
-                return Err(()); // Overflow
+            if weight > u32::MAX / (BASE - t) {
+                return None; // Overflow
             }
-
-            // i was supposed to wrap around from length+1 to 0,
-            // incrementing code_point each time.
-            code_point += i / (length + 1);
-            i %= length + 1;
-            let c = match char::from_u32(code_point) {
-                Some(c) => c,
-                None => return Err(()),
+            weight *= BASE - t;
+            k += BASE;
+            byte = match iter.next() {
+                None => return None, // End of input before the end of this delta
+                Some(byte) => byte,
             };
-
-            // Move earlier insertions farther out in the string
-            for (idx, _) in &mut self.insertions {
-                if *idx >= i as usize {
-                    *idx += 1;
-                }
-            }
-            self.insertions.push((i as usize, c));
-            length += 1;
-            i += 1;
         }
-
-        self.insertions.sort_by_key(|(i, _)| *i);
-        Ok(Decode {
-            base: base.chars(),
-            insertions: &self.insertions,
-            inserted: 0,
-            position: 0,
-            len: base_len + self.insertions.len(),
-        })
-    }
-}
-
-pub(crate) struct Decode<'a> {
-    base: std::str::Chars<'a>,
-    pub(crate) insertions: &'a [(usize, char)],
-    inserted: usize,
-    position: usize,
-    len: usize,
-}
-
-impl<'a> Iterator for Decode<'a> {
-    type Item = char;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        loop {
-            match self.insertions.get(self.inserted) {
-                Some((pos, c)) if *pos == self.position => {
-                    self.inserted += 1;
-                    self.position += 1;
-                    return Some(*c);
-                }
-                _ => {}
-            }
-            if let Some(c) = self.base.next() {
-                self.position += 1;
-                return Some(c);
-            } else if self.inserted >= self.insertions.len() {
-                return None;
-            }
+        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
         }
+        // i was supposed to wrap around from length+1 to 0,
+        // incrementing code_point each time.
+        code_point += i / (length + 1);
+        i %= length + 1;
+        let c = match char::from_u32(code_point) {
+            Some(c) => c,
+            None => return None,
+        };
+        output.insert(i as usize, c);
+        i += 1;
     }
-
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        let len = self.len - self.position;
-        (len, Some(len))
-    }
-}
-
-impl<'a> ExactSizeIterator for Decode<'a> {
-    fn len(&self) -> usize {
-        self.len - self.position
-    }
+    Some(output)
 }
 
 /// Convert an Unicode `str` to Punycode.
@@ -225,7 +149,7 @@ pub fn encode(input: &[char]) -> Option<String> {
         .map(|()| buf)
 }
 
-pub(crate) fn encode_into<I>(input: I, output: &mut String) -> Result<(), ()>
+fn encode_into<I>(input: I, output: &mut String) -> Result<(), ()>
 where
     I: Iterator<Item = char> + Clone,
 {
diff --git a/idna/src/uts46.rs b/idna/src/uts46.rs
index 5501a65..90e47a8 100644
--- a/idna/src/uts46.rs
+++ b/idna/src/uts46.rs
@@ -82,65 +82,38 @@ fn find_char(codepoint: char) -> &'static Mapping {
         .unwrap()
 }
 
-struct Mapper<'a> {
-    chars: std::str::Chars<'a>,
-    config: Config,
-    errors: &'a mut Errors,
-    slice: Option<std::str::Chars<'static>>,
-}
-
-impl<'a> Iterator for Mapper<'a> {
-    type Item = char;
+fn map_char(codepoint: char, config: Config, output: &mut String, errors: &mut Errors) {
+    if let '.' | '-' | 'a'..='z' | '0'..='9' = codepoint {
+        output.push(codepoint);
+        return;
+    }
 
-    fn next(&mut self) -> Option<Self::Item> {
-        loop {
-            if let Some(s) = &mut self.slice {
-                match s.next() {
-                    Some(c) => return Some(c),
-                    None => {
-                        self.slice = None;
-                    }
-                }
+    match *find_char(codepoint) {
+        Mapping::Valid => output.push(codepoint),
+        Mapping::Ignored => {}
+        Mapping::Mapped(ref slice) => output.push_str(decode_slice(slice)),
+        Mapping::Deviation(ref slice) => {
+            if config.transitional_processing {
+                output.push_str(decode_slice(slice))
+            } else {
+                output.push(codepoint)
             }
-
-            let codepoint = self.chars.next()?;
-            if let '.' | '-' | 'a'..='z' | '0'..='9' = codepoint {
-                return Some(codepoint);
+        }
+        Mapping::Disallowed => {
+            errors.disallowed_character = true;
+            output.push(codepoint);
+        }
+        Mapping::DisallowedStd3Valid => {
+            if config.use_std3_ascii_rules {
+                errors.disallowed_by_std3_ascii_rules = true;
             }
-
-            return Some(match *find_char(codepoint) {
-                Mapping::Valid => codepoint,
-                Mapping::Ignored => continue,
-                Mapping::Mapped(ref slice) => {
-                    self.slice = Some(decode_slice(slice).chars());
-                    continue;
-                }
-                Mapping::Deviation(ref slice) => {
-                    if self.config.transitional_processing {
-                        self.slice = Some(decode_slice(slice).chars());
-                        continue;
-                    } else {
-                        codepoint
-                    }
-                }
-                Mapping::Disallowed => {
-                    self.errors.disallowed_character = true;
-                    codepoint
-                }
-                Mapping::DisallowedStd3Valid => {
-                    if self.config.use_std3_ascii_rules {
-                        self.errors.disallowed_by_std3_ascii_rules = true;
-                    };
-                    codepoint
-                }
-                Mapping::DisallowedStd3Mapped(ref slice) => {
-                    if self.config.use_std3_ascii_rules {
-                        self.errors.disallowed_mapped_in_std3 = true;
-                    };
-                    self.slice = Some(decode_slice(slice).chars());
-                    continue;
-                }
-            });
+            output.push(codepoint)
+        }
+        Mapping::DisallowedStd3Mapped(ref slice) => {
+            if config.use_std3_ascii_rules {
+                errors.disallowed_mapped_in_std3 = true;
+            }
+            output.push_str(decode_slice(slice))
         }
     }
 }
@@ -279,11 +252,11 @@ fn passes_bidi(label: &str, is_bidi_domain: bool) -> bool {
 /// V1 (NFC) and V8 (Bidi) are checked inside `processing()` to prevent doing duplicate work.
 ///
 /// http://www.unicode.org/reports/tr46/#Validity_Criteria
-fn check_validity(label: &str, config: Config, errors: &mut Errors) {
+fn is_valid(label: &str, config: Config) -> bool {
     let first_char = label.chars().next();
     if first_char == None {
         // Empty string, pass
-        return;
+        return true;
     }
 
     // V2: No U+002D HYPHEN-MINUS in both third and fourth positions.
@@ -294,8 +267,7 @@ fn check_validity(label: &str, config: Config, errors: &mut Errors) {
 
     // V3: neither begin nor end with a U+002D HYPHEN-MINUS
     if config.check_hyphens && (label.starts_with('-') || label.ends_with('-')) {
-        errors.check_hyphens = true;
-        return;
+        return false;
     }
 
     // V4: not contain a U+002E FULL STOP
@@ -304,8 +276,7 @@ fn check_validity(label: &str, config: Config, errors: &mut Errors) {
 
     // V5: not begin with a GC=Mark
     if is_combining_mark(first_char.unwrap()) {
-        errors.start_combining_mark = true;
-        return;
+        return false;
     }
 
     // V6: Check against Mapping Table
@@ -315,8 +286,7 @@ fn check_validity(label: &str, config: Config, errors: &mut Errors) {
         Mapping::DisallowedStd3Valid => config.use_std3_ascii_rules,
         _ => true,
     }) {
-        errors.invalid_mapping = true;
-        return;
+        return false;
     }
 
     // V7: ContextJ rules
@@ -324,16 +294,12 @@ fn check_validity(label: &str, config: Config, errors: &mut Errors) {
     // TODO: Implement rules and add *CheckJoiners* flag.
 
     // V8: Bidi rules are checked inside `processing()`
+    true
 }
 
 /// http://www.unicode.org/reports/tr46/#Processing
 #[allow(clippy::manual_strip)] // introduced in 1.45, MSRV is 1.36
-fn processing(
-    domain: &str,
-    config: Config,
-    normalized: &mut String,
-    output: &mut String,
-) -> Errors {
+fn processing(domain: &str, config: Config) -> (String, Errors) {
     // Weed out the simple cases: only allow all lowercase ASCII characters and digits where none
     // of the labels start with PUNYCODE_PREFIX and labels don't start or end with hyphen.
     let (mut prev, mut simple, mut puny_prefix) = ('?', !domain.is_empty(), 0);
@@ -365,53 +331,41 @@ fn processing(
         }
         prev = c;
     }
-
     if simple {
-        output.push_str(domain);
-        return Errors::default();
+        return (domain.to_owned(), Errors::default());
     }
 
-    normalized.clear();
     let mut errors = Errors::default();
-    let offset = output.len();
-
-    let iter = Mapper {
-        chars: domain.chars(),
-        config,
-        errors: &mut errors,
-        slice: None,
-    };
-
-    normalized.extend(iter.nfc());
+    let mut mapped = String::with_capacity(domain.len());
+    for c in domain.chars() {
+        map_char(c, config, &mut mapped, &mut errors)
+    }
+    let mut normalized = String::with_capacity(mapped.len());
+    normalized.extend(mapped.nfc());
 
-    let mut decoder = punycode::Decoder::default();
+    let mut validated = String::new();
     let non_transitional = config.transitional_processing(false);
-    let (mut first, mut has_bidi_labels) = (true, false);
+    let (mut first, mut valid, mut has_bidi_labels) = (true, true, false);
     for label in normalized.split('.') {
         if !first {
-            output.push('.');
+            validated.push('.');
         }
         first = false;
         if label.starts_with(PUNYCODE_PREFIX) {
-            match decoder.decode(&label[PUNYCODE_PREFIX.len()..]) {
-                Ok(decode) => {
-                    let start = output.len();
-                    output.extend(decode);
-                    let decoded_label = &output[start..];
-
+            match punycode::decode_to_string(&label[PUNYCODE_PREFIX.len()..]) {
+                Some(decoded_label) => {
                     if !has_bidi_labels {
-                        has_bidi_labels |= is_bidi_domain(decoded_label);
+                        has_bidi_labels |= is_bidi_domain(&decoded_label);
                     }
 
-                    if !errors.is_err() {
-                        if !is_nfc(&decoded_label) {
-                            errors.nfc = true;
-                        } else {
-                            check_validity(decoded_label, non_transitional, &mut errors);
-                        }
+                    if valid
+                        && (!is_nfc(&decoded_label) || !is_valid(&decoded_label, non_transitional))
+                    {
+                        valid = false;
                     }
+                    validated.push_str(&decoded_label)
                 }
-                Err(()) => {
+                None => {
                     has_bidi_labels = true;
                     errors.punycode = true;
                 }
@@ -422,86 +376,26 @@ fn processing(
             }
 
             // `normalized` is already `NFC` so we can skip that check
-            check_validity(label, config, &mut errors);
-            output.push_str(label)
+            valid &= is_valid(label, config);
+            validated.push_str(label)
         }
     }
 
-    for label in output[offset..].split('.') {
+    for label in validated.split('.') {
         // V8: Bidi rules
         //
         // TODO: Add *CheckBidi* flag
         if !passes_bidi(label, has_bidi_labels) {
-            errors.check_bidi = true;
+            valid = false;
             break;
         }
     }
 
-    errors
-}
-
-#[derive(Default)]
-pub struct Idna {
-    config: Config,
-    normalized: String,
-    output: String,
-}
-
-impl Idna {
-    pub fn new(config: Config) -> Self {
-        Self {
-            config,
-            normalized: String::new(),
-            output: String::new(),
-        }
-    }
-
-    /// http://www.unicode.org/reports/tr46/#ToASCII
-    #[allow(clippy::wrong_self_convention)]
-    pub fn to_ascii<'a>(&'a mut self, domain: &str, out: &mut String) -> Result<(), Errors> {
-        let mut errors = processing(domain, self.config, &mut self.normalized, &mut self.output);
-
-        let mut first = true;
-        for label in self.output.split('.') {
-            if !first {
-                out.push('.');
-            }
-            first = false;
-
-            if label.is_ascii() {
-                out.push_str(label);
-            } else {
-                let offset = out.len();
-                out.push_str(PUNYCODE_PREFIX);
-                if let Err(()) = punycode::encode_into(label.chars(), out) {
-                    errors.punycode = true;
-                    out.truncate(offset);
-                }
-            }
-        }
-
-        if self.config.verify_dns_length {
-            let domain = if out.ends_with('.') {
-                &out[..out.len() - 1]
-            } else {
-                &*out
-            };
-            if domain.is_empty() || domain.split('.').any(|label| label.is_empty()) {
-                errors.too_short_for_dns = true;
-            }
-            if domain.len() > 253 || domain.split('.').any(|label| label.len() > 63) {
-                errors.too_long_for_dns = true;
-            }
-        }
-
-        errors.into()
+    if !valid {
+        errors.validity_criteria = true;
     }
 
-    /// http://www.unicode.org/reports/tr46/#ToUnicode
-    #[allow(clippy::wrong_self_convention)]
-    pub fn to_unicode<'a>(&'a mut self, domain: &str, out: &mut String) -> Result<(), Errors> {
-        processing(domain, self.config, &mut self.normalized, out).into()
-    }
+    (validated, errors)
 }
 
 #[derive(Clone, Copy)]
@@ -556,16 +450,49 @@ impl Config {
     /// http://www.unicode.org/reports/tr46/#ToASCII
     pub fn to_ascii(self, domain: &str) -> Result<String, Errors> {
         let mut result = String::new();
-        let mut codec = Idna::new(self);
-        codec.to_ascii(domain, &mut result).map(|()| result)
+        let mut first = true;
+        let (domain, mut errors) = processing(domain, self);
+        for label in domain.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.punycode = true;
+                    }
+                }
+            }
+        }
+
+        if self.verify_dns_length {
+            let domain = if result.ends_with('.') {
+                &result[..result.len() - 1]
+            } else {
+                &*result
+            };
+            if domain.is_empty() || domain.split('.').any(|label| label.is_empty()) {
+                errors.too_short_for_dns = true;
+            }
+            if domain.len() > 253 || domain.split('.').any(|label| label.len() > 63) {
+                errors.too_long_for_dns = true;
+            }
+        }
+
+        Result::from(errors).map(|()| result)
     }
 
     /// http://www.unicode.org/reports/tr46/#ToUnicode
     pub fn to_unicode(self, domain: &str) -> (String, Result<(), Errors>) {
-        let mut codec = Idna::new(self);
-        let mut out = String::with_capacity(domain.len());
-        let result = codec.to_unicode(domain, &mut out);
-        (out, result)
+        let (domain, errors) = processing(domain, self);
+        (domain, errors.into())
     }
 }
 
@@ -586,14 +513,11 @@ fn is_bidi_domain(s: &str) -> bool {
 ///
 /// This is opaque for now, indicating what types of errors have been encountered at least once.
 /// More details may be exposed in the future.
-#[derive(Default)]
+#[derive(Debug, Default)]
 pub struct Errors {
     punycode: bool,
-    check_hyphens: bool,
-    check_bidi: bool,
-    start_combining_mark: bool,
-    invalid_mapping: bool,
-    nfc: bool,
+    // https://unicode.org/reports/tr46/#Validity_Criteria
+    validity_criteria: bool,
     disallowed_by_std3_ascii_rules: bool,
     disallowed_mapped_in_std3: bool,
     disallowed_character: bool,
@@ -601,91 +525,16 @@ pub struct Errors {
     too_short_for_dns: bool,
 }
 
-impl Errors {
-    fn is_err(&self) -> bool {
-        let Errors {
-            punycode,
-            check_hyphens,
-            check_bidi,
-            start_combining_mark,
-            invalid_mapping,
-            nfc,
-            disallowed_by_std3_ascii_rules,
-            disallowed_mapped_in_std3,
-            disallowed_character,
-            too_long_for_dns,
-            too_short_for_dns,
-        } = *self;
-        punycode
-            || check_hyphens
-            || check_bidi
-            || start_combining_mark
-            || invalid_mapping
-            || nfc
-            || disallowed_by_std3_ascii_rules
-            || disallowed_mapped_in_std3
-            || disallowed_character
-            || too_long_for_dns
-            || too_short_for_dns
-    }
-}
-
-impl fmt::Debug for Errors {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let Errors {
-            punycode,
-            check_hyphens,
-            check_bidi,
-            start_combining_mark,
-            invalid_mapping,
-            nfc,
-            disallowed_by_std3_ascii_rules,
-            disallowed_mapped_in_std3,
-            disallowed_character,
-            too_long_for_dns,
-            too_short_for_dns,
-        } = *self;
-
-        let fields = [
-            ("punycode", punycode),
-            ("check_hyphens", check_hyphens),
-            ("check_bidi", check_bidi),
-            ("start_combining_mark", start_combining_mark),
-            ("invalid_mapping", invalid_mapping),
-            ("nfc", nfc),
-            (
-                "disallowed_by_std3_ascii_rules",
-                disallowed_by_std3_ascii_rules,
-            ),
-            ("disallowed_mapped_in_std3", disallowed_mapped_in_std3),
-            ("disallowed_character", disallowed_character),
-            ("too_long_for_dns", too_long_for_dns),
-            ("too_short_for_dns", too_short_for_dns),
-        ];
-
-        let mut empty = true;
-        f.write_str("Errors { ")?;
-        for (name, val) in &fields {
-            if *val {
-                if !empty {
-                    f.write_str(", ")?;
-                }
-                f.write_str(*name)?;
-                empty = false;
-            }
-        }
-
-        if !empty {
-            f.write_str(" }")
-        } else {
-            f.write_str("}")
-        }
-    }
-}
-
 impl From<Errors> for Result<(), Errors> {
     fn from(e: Errors) -> Result<(), Errors> {
-        if !e.is_err() {
+        let failed = e.punycode
+            || e.validity_criteria
+            || e.disallowed_by_std3_ascii_rules
+            || e.disallowed_mapped_in_std3
+            || e.disallowed_character
+            || e.too_long_for_dns
+            || e.too_short_for_dns;
+        if !failed {
             Ok(())
         } else {
             Err(e)
diff --git a/url/src/lib.rs b/url/src/lib.rs
index cdb8cba..25a29d2 100644
--- a/url/src/lib.rs
+++ b/url/src/lib.rs
@@ -729,8 +729,9 @@ impl Url {
     /// # run().unwrap();
     /// ```
     pub fn username(&self) -> &str {
-        if self.has_authority() {
-            self.slice(self.scheme_end + ("://".len() as u32)..self.username_end)
+        let scheme_separator_len = "://".len() as u32;
+        if self.has_authority() && self.username_end > self.scheme_end + scheme_separator_len {
+            self.slice(self.scheme_end + scheme_separator_len..self.username_end)
         } else {
             ""
         }


Test patch:
diff --git a/idna/tests/unit.rs b/idna/tests/unit.rs
index a9aa375..0328440 100644
--- a/idna/tests/unit.rs
+++ b/idna/tests/unit.rs
@@ -1,4 +1,3 @@
-use assert_matches::assert_matches;
 use unicode_normalization::char::is_combining_mark;
 
 /// https://github.com/servo/rust-url/issues/373
@@ -39,48 +38,6 @@ fn test_punycode_prefix_without_length_check() {
     assert_eq!(config.to_ascii("xn--.example.org").unwrap(), ".example.org");
 }
 
-// http://www.unicode.org/reports/tr46/#Table_Example_Processing
-#[test]
-fn test_examples() {
-    let mut codec = idna::Idna::default();
-    let mut out = String::new();
-
-    assert_matches!(codec.to_unicode("Bloß.de", &mut out), Ok(()));
-    assert_eq!(out, "bloß.de");
-
-    out.clear();
-    assert_matches!(codec.to_unicode("xn--blo-7ka.de", &mut out), Ok(()));
-    assert_eq!(out, "bloß.de");
-
-    out.clear();
-    assert_matches!(codec.to_unicode("u\u{308}.com", &mut out), Ok(()));
-    assert_eq!(out, "ü.com");
-
-    out.clear();
-    assert_matches!(codec.to_unicode("xn--tda.com", &mut out), Ok(()));
-    assert_eq!(out, "ü.com");
-
-    out.clear();
-    assert_matches!(codec.to_unicode("xn--u-ccb.com", &mut out), Err(_));
-
-    out.clear();
-    assert_matches!(codec.to_unicode("a⒈com", &mut out), Err(_));
-
-    out.clear();
-    assert_matches!(codec.to_unicode("xn--a-ecp.ru", &mut out), Err(_));
-
-    out.clear();
-    assert_matches!(codec.to_unicode("xn--0.pt", &mut out), Err(_));
-
-    out.clear();
-    assert_matches!(codec.to_unicode("日本語。ＪＰ", &mut out), Ok(()));
-    assert_eq!(out, "日本語.jp");
-
-    out.clear();
-    assert_matches!(codec.to_unicode("☕.us", &mut out), Ok(()));
-    assert_eq!(out, "☕.us");
-}
-
 #[test]
 fn test_v5() {
     let config = idna::Config::default()
diff --git a/url/tests/unit.rs b/url/tests/unit.rs
index 21a7194..19ac8d1 100644
--- a/url/tests/unit.rs
+++ b/url/tests/unit.rs
@@ -665,3 +665,9 @@ fn test_set_scheme_to_file_with_host() {
     assert_eq!(url.to_string(), "http://localhost:6767/foo/bar");
     assert_eq!(result, Err(()));
 }
+
+#[test]
+fn no_panic() {
+    let mut url = Url::parse("arhttpsps:/.//eom/dae.com/\\\\t\\:").unwrap();
+    url::quirks::set_hostname(&mut url, "//eom/datcom/\\\\t\\://eom/data.cs").unwrap();
+}