Skip to content

Commit 185495d

Browse files
committed
[idna] Preserve leading dots in host
A retry of #171 This diff changes the behavior of ToASCII step to match the spec and prevent failures on some cases when a domain name starts with leading dots (FULL STOPs), as requested in #166. The change in the code results in a few failures for test cases of the Conformance Testing data provided with UTS #46. But, as the header of the test data file (IdnaTest.txt) says: "If the file does not indicate an error, then the implementation must either have an error, or must have a matching result." Therefore, failing on those test cases does not break conformance with UTS #46, and to some level, anticipated. As mentioned in #166, a feedback is submitted for this inconsistency and the test logic can be improved later if the data file addresses the comments. Until then, we can throw less errors and maintain passing conformance tests with this diff. To keep the side-effects of ignoring errors during test runs as minimum as possible, I have separated `TooShortForDns` error from `TooLongForDns`. The `Error` struct has been kept private, so the change won't affect any library users. Fix #166
1 parent 0a96a99 commit 185495d

File tree

2 files changed

+18
-4
lines changed

2 files changed

+18
-4
lines changed

idna/src/uts46.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,10 +239,12 @@ fn processing(domain: &str, flags: Flags, errors: &mut Vec<Error>) -> String {
239239
}
240240
let normalized: String = mapped.nfc().collect();
241241
let mut validated = String::new();
242+
let mut first = true;
242243
for label in normalized.split('.') {
243-
if validated.len() > 0 {
244+
if !first {
244245
validated.push('.');
245246
}
247+
first = false;
246248
if label.starts_with("xn--") {
247249
match punycode::decode_to_string(&label["xn--".len()..]) {
248250
Some(decoded_label) => {
@@ -275,6 +277,7 @@ enum Error {
275277
DissallowedMappedInStd3,
276278
DissallowedCharacter,
277279
TooLongForDns,
280+
TooShortForDns,
278281
}
279282

280283
/// Errors recorded during UTS #46 processing.
@@ -288,10 +291,12 @@ pub struct Errors(Vec<Error>);
288291
pub fn to_ascii(domain: &str, flags: Flags) -> Result<String, Errors> {
289292
let mut errors = Vec::new();
290293
let mut result = String::new();
294+
let mut first = true;
291295
for label in processing(domain, flags, &mut errors).split('.') {
292-
if result.len() > 0 {
296+
if !first {
293297
result.push('.');
294298
}
299+
first = false;
295300
if label.is_ascii() {
296301
result.push_str(label);
297302
} else {
@@ -307,8 +312,10 @@ pub fn to_ascii(domain: &str, flags: Flags) -> Result<String, Errors> {
307312

308313
if flags.verify_dns_length {
309314
let domain = if result.ends_with(".") { &result[..result.len()-1] } else { &*result };
310-
if domain.len() < 1 || domain.len() > 253 ||
311-
domain.split('.').any(|label| label.len() < 1 || label.len() > 63) {
315+
if domain.len() < 1 || domain.split('.').any(|label| label.len() < 1) {
316+
errors.push(Error::TooShortForDns)
317+
}
318+
if domain.len() > 253 || domain.split('.').any(|label| label.len() > 63) {
312319
errors.push(Error::TooLongForDns)
313320
}
314321
}

tests/unit.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,13 @@ fn test_set_host() {
374374
assert_eq!(url.as_str(), "foobar:/hello");
375375
}
376376

377+
#[test]
378+
// https://github.com/servo/rust-url/issues/166
379+
fn test_leading_dots() {
380+
assert_eq!(Host::parse(".org").unwrap(), Host::Domain(".org".to_owned()));
381+
assert_eq!(Url::parse("file://./foo").unwrap().domain(), Some("."));
382+
}
383+
377384
// This is testing that the macro produces buildable code when invoked
378385
// inside both a module and a function
379386
#[test]

0 commit comments

Comments
 (0)