Bladeren bron

Implement ToSocketAddrs

Simon Sapin 10 jaren geleden
bovenliggende
commit
1afe54fa41
3 gewijzigde bestanden met toevoegingen van 127 en 8 verwijderingen
  1. 64 1
      src/host.rs
  2. 62 6
      src/lib.rs
  3. 1 1
      src/origin.rs

+ 64 - 1
src/host.rs

@@ -8,7 +8,9 @@
 
 
 use std::cmp;
 use std::cmp;
 use std::fmt::{self, Formatter, Write};
 use std::fmt::{self, Formatter, Write};
-use std::net::{Ipv4Addr, Ipv6Addr};
+use std::io;
+use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
+use std::vec;
 use parser::{ParseResult, ParseError};
 use parser::{ParseResult, ParseError};
 use percent_encoding::lossy_utf8_percent_decode;
 use percent_encoding::lossy_utf8_percent_decode;
 use idna;
 use idna;
@@ -44,6 +46,7 @@ pub enum Host<S=String> {
 }
 }
 
 
 impl<'a> Host<&'a str> {
 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> {
     pub fn to_owned(&self) -> Host<String> {
         match *self {
         match *self {
             Host::Domain(domain) => Host::Domain(domain.to_owned()),
             Host::Domain(domain) => Host::Domain(domain.to_owned()),
@@ -93,6 +96,66 @@ 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)`.
+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>> 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 {
+                state: SocketAddrsState::Domain(try!((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)))
+            }),
+        }
+    }
+}
+
+pub struct SocketAddrs {
+    state: SocketAddrsState
+}
+
+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
+        }
+    }
+}
+
 /// Parse `input` as a host.
 /// Parse `input` as a host.
 /// If successful, write its serialization to `serialization`
 /// If successful, write its serialization to `serialization`
 /// and return the internal representation for `Url`.
 /// and return the internal representation for `Url`.

+ 62 - 6
src/lib.rs

@@ -130,14 +130,15 @@ use percent_encoding::{PATH_SEGMENT_ENCODE_SET, percent_encode, percent_decode};
 use std::cmp;
 use std::cmp;
 use std::fmt;
 use std::fmt;
 use std::hash;
 use std::hash;
-#[cfg(has_ipaddr)] use std::net::IpAddr;
+use std::io;
+use std::net::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;
 
 
 pub use encoding::EncodingOverride;
 pub use encoding::EncodingOverride;
 pub use origin::Origin;
 pub use origin::Origin;
-pub use host::Host;
+pub use host::{Host, HostAndPort, SocketAddrs};
 pub use parser::ParseError;
 pub use parser::ParseError;
 pub use slicing::Position;
 pub use slicing::Position;
 pub use webidl::WebIdl;
 pub use webidl::WebIdl;
@@ -288,12 +289,12 @@ impl Url {
     ///
     ///
     /// This does **not** resolve domain names.
     /// This does **not** resolve domain names.
     #[cfg(has_ipaddr)]
     #[cfg(has_ipaddr)]
-    pub fn ip_address(&self) -> Option<IpAddr> {
+    pub fn ip_address(&self) -> Option<net::IpAddr> {
         match self.host {
         match self.host {
             HostInternal::None => None,
             HostInternal::None => None,
             HostInternal::Domain => None,
             HostInternal::Domain => None,
-            HostInternal::Ipv4(address) => Some(IpAddr::V4(address)),
-            HostInternal::Ipv6(address) => Some(IpAddr::V6(address)),
+            HostInternal::Ipv4(address) => Some(net::IpAddr::V4(address)),
+            HostInternal::Ipv6(address) => Some(net::IpAddr::V6(address)),
         }
         }
     }
     }
 
 
@@ -311,10 +312,52 @@ impl Url {
     /// For URLs in these schemes, this method always returns `Some(_)`.
     /// For URLs in these schemes, this method always returns `Some(_)`.
     /// For other schemes, it is the same as `Url::port()`.
     /// For other schemes, it is the same as `Url::port()`.
     #[inline]
     #[inline]
-    pub fn port_or_default(&self) -> Option<u16> {
+    pub fn port_or_known_default(&self) -> Option<u16> {
         self.port.or_else(|| parser::default_port(self.scheme()))
         self.port.or_else(|| parser::default_port(self.scheme()))
     }
     }
 
 
+    /// If the URL has a host, return something that implements `ToSocketAddrs`.
+    ///
+    /// If the URL has no port number and the scheme’s default port number is not known
+    /// (see `Url::port_or_known_default`),
+    /// the closure is called to obtain a port number.
+    /// Typically, this closure can match on the result `Url::scheme`
+    /// to have per-scheme default port numbers,
+    /// and panic for schemes it’s not prepared to handle.
+    /// For example:
+    ///
+    /// ```rust
+    /// # use url::Url;
+    /// # use std::net::TcpStream;
+    /// # use std::io;
+    ///
+    /// fn connect(url: &Url) -> io::Result<TcpStream> {
+    ///     TcpStream::connect(try!(url.with_default_port(default_port)))
+    /// }
+    ///
+    /// fn default_port(url: &Url) -> Result<u16, ()> {
+    ///     match url.scheme() {
+    ///         "git" => Ok(9418),
+    ///         "git+ssh" => Ok(22),
+    ///         "git+https" => Ok(443),
+    ///         "git+http" => Ok(80),
+    ///         _ => Err(()),
+    ///     }
+    /// }
+    /// ```
+    pub fn with_default_port<F>(&self, f: F) -> io::Result<HostAndPort<&str>>
+    where F: FnOnce(&Url) -> Result<u16, ()> {
+        Ok(HostAndPort {
+            host: try!(self.host()
+                           .ok_or(())
+                           .or_else(|()| io_error("URL has no host"))),
+            port: try!(self.port_or_known_default()
+                           .ok_or(())
+                           .or_else(|()| f(self))
+                           .or_else(|()| io_error("URL has no port number")))
+        })
+    }
+
     /// 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 relative URLs, this starts with a '/' slash
     /// For relative URLs, this starts with a '/' slash
     /// and continues with slash-separated path segments.
     /// and continues with slash-separated path segments.
@@ -463,6 +506,15 @@ impl Url {
     }
     }
 }
 }
 
 
+/// Return an error if `Url::host` or `Url::port_or_known_default` return `None`.
+impl ToSocketAddrs for Url {
+    type Iter = SocketAddrs;
+
+    fn to_socket_addrs(&self) -> io::Result<Self::Iter> {
+        try!(self.with_default_port(|_| Err(()))).to_socket_addrs()
+    }
+}
+
 /// Parse a string as an URL, without a base URL or encoding override.
 /// Parse a string as an URL, without a base URL or encoding override.
 impl str::FromStr for Url {
 impl str::FromStr for Url {
     type Err = ParseError;
     type Err = ParseError;
@@ -695,3 +747,7 @@ fn file_url_segments_to_pathbuf_windows(mut segments: str::Split<char>) -> Resul
                   "to_file_path() failed to produce an absolute Path");
                   "to_file_path() failed to produce an absolute Path");
     Ok(path)
     Ok(path)
 }
 }
+
+fn io_error<T>(reason: &str) -> io::Result<T> {
+    Err(io::Error::new(io::ErrorKind::InvalidData, reason))
+}

+ 1 - 1
src/origin.rs

@@ -23,7 +23,7 @@ impl Url {
             },
             },
             "ftp" | "gopher" | "http" | "https" | "ws" | "wss" => {
             "ftp" | "gopher" | "http" | "https" | "ws" | "wss" => {
                 Origin::Tuple(scheme.to_owned(), self.host().unwrap().to_owned(),
                 Origin::Tuple(scheme.to_owned(), self.host().unwrap().to_owned(),
-                    self.port_or_default().unwrap())
+                    self.port_or_known_default().unwrap())
             },
             },
             // TODO: Figure out what to do if the scheme is a file
             // TODO: Figure out what to do if the scheme is a file
             "file" => Origin::new_opaque(),
             "file" => Origin::new_opaque(),