Просмотр исходного кода

Auto merge of #403 - valenting:opaque-host-parser, r=SimonSapin

Use opaque host parser for non-special schemes

Addresses URL spec change:
https://github.com/whatwg/url/commit/30362553e9ce9fc706d3492bd61886e399fc94e2

For non-special schemes we use the opaque host parser - meaning the IDNA parser is not used, but instead we apply a UTF-8 percent encode using the simple encode set on the host input.

<!-- 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/403)
<!-- Reviewable:end -->
bors-servo 9 лет назад
Родитель
Сommit
8e3cbca6a1
5 измененных файлов с 38 добавлено и 6 удалено
  1. 21 2
      src/host.rs
  2. 5 1
      src/lib.rs
  3. 4 0
      src/parser.rs
  4. 5 0
      tests/unit.rs
  5. 3 3
      tests/urltestdata.json

+ 21 - 2
src/host.rs

@@ -13,7 +13,7 @@ use std::io;
 use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
 use std::vec;
 use parser::{ParseResult, ParseError};
-use percent_encoding::percent_decode;
+use percent_encoding::{percent_decode, utf8_percent_encode, SIMPLE_ENCODE_SET};
 use idna;
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -73,7 +73,9 @@ impl<S> From<Host<S>> for HostInternal {
 #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
 pub enum Host<S=String> {
     /// A DNS domain name, as '.' dot-separated labels.
-    /// Non-ASCII labels are encoded in punycode per IDNA.
+    /// Non-ASCII labels are encoded in punycode per IDNA if this is the host of
+    /// a special URL, or percent encoded for non-special URLs. Hosts for
+    /// non-special URLs are also called opaque hosts.
     Domain(S),
 
     /// An IPv4 address.
@@ -158,6 +160,23 @@ impl Host<String> {
             Ok(Host::Domain(domain.into()))
         }
     }
+
+    // <https://url.spec.whatwg.org/#concept-opaque-host-parser>
+    pub fn parse_opaque(input: &str) -> Result<Self, ParseError> {
+        if input.starts_with('[') {
+            if !input.ends_with(']') {
+                return Err(ParseError::InvalidIpv6Address)
+            }
+            return parse_ipv6addr(&input[1..input.len() - 1]).map(Host::Ipv6)
+        }
+        if input.find(|c| matches!(c,
+            '\0' | '\t' | '\n' | '\r' | ' ' | '#' | '/' | ':' | '?' | '@' | '[' | '\\' | ']'
+        )).is_some() {
+            return Err(ParseError::InvalidDomainCharacter)
+        }
+        let s = utf8_percent_encode(input, SIMPLE_ENCODE_SET).to_string();
+        Ok(Host::Domain(s))
+    }
 }
 
 impl<S: AsRef<str>> fmt::Display for Host<S> {

+ 5 - 1
src/lib.rs

@@ -1558,7 +1558,11 @@ impl Url {
             if host == "" && SchemeType::from(self.scheme()).is_special() {
                 return Err(ParseError::EmptyHost);
             }
-            self.set_host_internal(Host::parse(host)?, None)
+            if SchemeType::from(self.scheme()).is_special() {
+                self.set_host_internal(Host::parse(host)?, None)
+            } else {
+                self.set_host_internal(Host::parse_opaque(host)?, None)
+            }
         } else if self.has_host() {
             if SchemeType::from(self.scheme()).is_special() {
                 return Err(ParseError::EmptyHost)

+ 4 - 0
src/parser.rs

@@ -783,6 +783,10 @@ impl<'a> Parser<'a> {
         if scheme_type.is_special() && host_str.is_empty() {
             return Err(ParseError::EmptyHost)
         }
+        if !scheme_type.is_special() {
+            let host = Host::parse_opaque(host_str)?;
+            return Ok((host, input));
+        }
         let host = Host::parse(host_str)?;
         Ok((host, input))
     }

+ 5 - 0
tests/unit.rs

@@ -382,6 +382,11 @@ fn test_set_host() {
     let mut url = Url::parse("foobar://example.net/hello").unwrap();
     url.set_host(None).unwrap();
     assert_eq!(url.as_str(), "foobar:/hello");
+
+    let mut url = Url::parse("foo://ș").unwrap();
+    assert_eq!(url.as_str(), "foo://%C8%99/");
+    url.set_host(Some("goșu.ro")).unwrap();
+    assert_eq!(url.as_str(), "foo://go%C8%99u.ro/");
 }
 
 #[test]

+ 3 - 3
tests/urltestdata.json

@@ -4200,13 +4200,13 @@
   {
     "input": "sc://ñ.test/",
     "base": "about:blank",
-    "href": "sc://xn--ida.test/",
+    "href": "sc://%C3%B1.test/",
     "origin": "null",
     "protocol": "sc:",
     "username": "",
     "password": "",
-    "host": "xn--ida.test",
-    "hostname": "xn--ida.test",
+    "host": "%C3%B1.test",
+    "hostname": "%C3%B1.test",
     "port": "",
     "pathname": "/",
     "search": "",