Pārlūkot izejas kodu

Auto merge of #535 - servo:socket_addrs, r=nox

Restore DNS resolution functionality from url 1.x

This is an alternative to https://github.com/servo/rust-url/pull/533.

I feel that an inherent method is more appropriate than an impl of the `ToSocketAddrs` trait: it is not the URL as a whole that is converted, only parts of it.

<!-- 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/535)
<!-- Reviewable:end -->
bors-servo 7 gadi atpakaļ
vecāks
revīzija
a93e8cb084
4 mainītis faili ar 62 papildinājumiem un 7 dzēšanām
  1. 1 1
      Cargo.toml
  2. 1 1
      idna/tests/punycode.rs
  3. 1 1
      idna/tests/tests.rs
  4. 59 4
      src/lib.rs

+ 1 - 1
Cargo.toml

@@ -2,7 +2,7 @@
 
 
 name = "url"
 name = "url"
 # When updating version, also modify html_root_url in the lib.rs
 # When updating version, also modify html_root_url in the lib.rs
-version = "2.0.0"
+version = "2.1.0"
 authors = ["The rust-url developers"]
 authors = ["The rust-url developers"]
 
 
 description = "URL library for Rust, based on the WHATWG URL Standard"
 description = "URL library for Rust, based on the WHATWG URL Standard"

+ 1 - 1
idna/tests/punycode.rs

@@ -7,8 +7,8 @@
 // except according to those terms.
 // except according to those terms.
 
 
 use idna::punycode::{decode, encode_str};
 use idna::punycode::{decode, encode_str};
-use serde_json::Value;
 use serde_json::map::Map;
 use serde_json::map::Map;
+use serde_json::Value;
 use std::str::FromStr;
 use std::str::FromStr;
 use test::TestFn;
 use test::TestFn;
 
 

+ 1 - 1
idna/tests/tests.rs

@@ -1,6 +1,6 @@
 extern crate idna;
 extern crate idna;
-extern crate serde_json;
 extern crate rustc_test as test;
 extern crate rustc_test as test;
+extern crate serde_json;
 
 
 mod punycode;
 mod punycode;
 mod uts46;
 mod uts46;

+ 59 - 4
src/lib.rs

@@ -123,8 +123,9 @@ use std::cmp;
 use std::error::Error;
 use std::error::Error;
 use std::fmt::{self, Write};
 use std::fmt::{self, Write};
 use std::hash;
 use std::hash;
+use std::io;
 use std::mem;
 use std::mem;
-use std::net::IpAddr;
+use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
 use std::ops::{Range, RangeFrom, RangeTo};
 use std::ops::{Range, RangeFrom, RangeTo};
 use std::path::{Path, PathBuf};
 use std::path::{Path, PathBuf};
 use std::str;
 use std::str;
@@ -945,6 +946,61 @@ impl Url {
         self.port.or_else(|| parser::default_port(self.scheme()))
         self.port.or_else(|| parser::default_port(self.scheme()))
     }
     }
 
 
+    /// Resolve a URL’s host and port number to `SocketAddr`.
+    ///
+    /// If the URL has the default port number of a scheme that is unknown to this library,
+    /// `default_port_number` provides an opportunity to provide the actual port number.
+    /// In non-example code this should be implemented either simply as `|| None`,
+    /// or by matching on the URL’s `.scheme()`.
+    ///
+    /// If the host is a domain, it is resolved using the standard library’s DNS support.
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// let url = url::Url::parse("https://example.net/").unwrap();
+    /// let addrs = url.socket_addrs(|| None).unwrap();
+    /// std::net::TcpStream::connect(&*addrs)
+    /// # ;
+    /// ```
+    ///
+    /// ```
+    /// /// With application-specific known default port numbers
+    /// fn socket_addrs(url: url::Url) -> std::io::Result<Vec<std::net::SocketAddr>> {
+    ///     url.socket_addrs(|| match url.scheme() {
+    ///         "socks5" | "socks5h" => Some(1080),
+    ///         _ => None,
+    ///     })
+    /// }
+    /// ```
+    pub fn socket_addrs(
+        &self,
+        default_port_number: impl Fn() -> Option<u16>,
+    ) -> io::Result<Vec<SocketAddr>> {
+        // Note: trying to avoid the Vec allocation by returning `impl AsRef<[SocketAddr]>`
+        // causes borrowck issues because the return value borrows `default_port_number`:
+        //
+        // https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md#scoping-for-type-and-lifetime-parameters
+        //
+        // > This RFC proposes that *all* type parameters are considered in scope
+        // > for `impl Trait` in return position
+
+        fn io_result<T>(opt: Option<T>, message: &str) -> io::Result<T> {
+            opt.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, message))
+        }
+
+        let host = io_result(self.host(), "No host name in the URL")?;
+        let port = io_result(
+            self.port_or_known_default().or_else(default_port_number),
+            "No port number in the URL",
+        )?;
+        Ok(match host {
+            Host::Domain(domain) => (domain, port).to_socket_addrs()?.collect(),
+            Host::Ipv4(ip) => vec![(ip, port).into()],
+            Host::Ipv6(ip) => vec![(ip, port).into()],
+        })
+    }
+
     /// Return the path for this URL, as a percent-encoded ASCII string.
     /// Return the path for this URL, as a percent-encoded ASCII string.
     /// For cannot-be-a-base URLs, this is an arbitrary string that doesn’t start with '/'.
     /// For cannot-be-a-base URLs, this is an arbitrary string that doesn’t start with '/'.
     /// For other URLs, this starts with a '/' slash
     /// For other URLs, this starts with a '/' slash
@@ -2296,9 +2352,8 @@ impl<'de> serde::Deserialize<'de> for Url {
             where
             where
                 E: Error,
                 E: Error,
             {
             {
-                Url::parse(s).map_err(|err| {
-                    Error::invalid_value(Unexpected::Str(s), &err.description())
-                })
+                Url::parse(s)
+                    .map_err(|err| Error::invalid_value(Unexpected::Str(s), &err.description()))
             }
             }
         }
         }