use std::error::Error as StdError;
use std::mem;
use bytes::{BufMut, Bytes, BytesMut};
use futures::Future;
use http::{uri, Uri};
use tokio_io::{AsyncRead, AsyncWrite};
#[cfg(feature = "runtime")] mod dns;
#[cfg(feature = "runtime")] mod http;
#[cfg(feature = "runtime")] pub use self::http::HttpConnector;
pub trait Connect: Send + Sync {
type Transport: AsyncRead + AsyncWrite + Send + 'static;
type Error: Into<Box<StdError + Send + Sync>>;
type Future: Future<Item=(Self::Transport, Connected), Error=Self::Error> + Send;
fn connect(&self, dst: Destination) -> Self::Future;
}
#[derive(Clone, Debug)]
pub struct Destination {
pub(super) uri: Uri,
}
#[derive(Debug)]
pub struct Connected {
pub(super) is_proxied: bool,
}
impl Destination {
#[inline]
pub fn scheme(&self) -> &str {
self.uri
.scheme_part()
.map(|s| s.as_str())
.unwrap_or("")
}
#[inline]
pub fn host(&self) -> &str {
self.uri
.host()
.unwrap_or("")
}
#[inline]
pub fn port(&self) -> Option<u16> {
self.uri.port()
}
pub fn set_scheme(&mut self, scheme: &str) -> ::Result<()> {
let scheme = scheme.parse().map_err(::error::Parse::from)?;
self.update_uri(move |parts| {
parts.scheme = Some(scheme);
})
}
pub fn set_host(&mut self, host: &str) -> ::Result<()> {
if host.contains('@') {
return Err(::error::Parse::Uri.into());
}
let auth = if let Some(port) = self.port() {
let bytes = Bytes::from(format!("{}:{}", host, port));
uri::Authority::from_shared(bytes)
.map_err(::error::Parse::from)?
} else {
let auth = host.parse::<uri::Authority>().map_err(::error::Parse::from)?;
if auth.port().is_some() {
return Err(::error::Parse::Uri.into());
}
auth
};
self.update_uri(move |parts| {
parts.authority = Some(auth);
})
}
pub fn set_port<P>(&mut self, port: P)
where
P: Into<Option<u16>>,
{
self.set_port_opt(port.into());
}
fn set_port_opt(&mut self, port: Option<u16>) {
use std::fmt::Write;
let auth = if let Some(port) = port {
let host = self.host();
let cap = host.len() + 1 + 5;
let mut buf = BytesMut::with_capacity(cap);
buf.put_slice(host.as_bytes());
buf.put_u8(b':');
write!(buf, "{}", port)
.expect("should have space for 5 digits");
uri::Authority::from_shared(buf.freeze())
.expect("valid host + :port should be valid authority")
} else {
self.host().parse()
.expect("valid host without port should be valid authority")
};
self.update_uri(move |parts| {
parts.authority = Some(auth);
})
.expect("valid uri should be valid with port");
}
fn update_uri<F>(&mut self, f: F) -> ::Result<()>
where
F: FnOnce(&mut uri::Parts)
{
let old_uri = mem::replace(&mut self.uri, Uri::default());
let mut parts: uri::Parts = old_uri.clone().into();
f(&mut parts);
match Uri::from_parts(parts) {
Ok(uri) => {
self.uri = uri;
Ok(())
},
Err(err) => {
self.uri = old_uri;
Err(::error::Parse::from(err).into())
},
}
}
}
impl Connected {
pub fn new() -> Connected {
Connected {
is_proxied: false,
}
}
pub fn proxy(mut self, is_proxied: bool) -> Connected {
self.is_proxied = is_proxied;
self
}
}
#[cfg(test)]
mod tests {
use super::Destination;
#[test]
fn test_destination_set_scheme() {
let mut dst = Destination {
uri: "http://hyper.rs".parse().expect("initial parse"),
};
assert_eq!(dst.scheme(), "http");
assert_eq!(dst.host(), "hyper.rs");
dst.set_scheme("https").expect("set https");
assert_eq!(dst.scheme(), "https");
assert_eq!(dst.host(), "hyper.rs");
dst.set_scheme("<im not a scheme//?>").unwrap_err();
assert_eq!(dst.scheme(), "https", "error doesn't modify dst");
assert_eq!(dst.host(), "hyper.rs", "error doesn't modify dst");
}
#[test]
fn test_destination_set_host() {
let mut dst = Destination {
uri: "http://hyper.rs".parse().expect("initial parse"),
};
assert_eq!(dst.scheme(), "http");
assert_eq!(dst.host(), "hyper.rs");
assert_eq!(dst.port(), None);
dst.set_host("seanmonstar.com").expect("set https");
assert_eq!(dst.scheme(), "http");
assert_eq!(dst.host(), "seanmonstar.com");
assert_eq!(dst.port(), None);
dst.set_host("/im-not a host! >:)").unwrap_err();
assert_eq!(dst.scheme(), "http", "error doesn't modify dst");
assert_eq!(dst.host(), "seanmonstar.com", "error doesn't modify dst");
assert_eq!(dst.port(), None, "error doesn't modify dst");
dst.set_host("seanmonstar.com:3030").expect_err("set_host sneaky port");
assert_eq!(dst.scheme(), "http", "error doesn't modify dst");
assert_eq!(dst.host(), "seanmonstar.com", "error doesn't modify dst");
assert_eq!(dst.port(), None, "error doesn't modify dst");
dst.set_host("sean@nope").expect_err("set_host sneaky userinfo");
assert_eq!(dst.scheme(), "http", "error doesn't modify dst");
assert_eq!(dst.host(), "seanmonstar.com", "error doesn't modify dst");
assert_eq!(dst.port(), None, "error doesn't modify dst");
dst.set_host("[::1]").expect("set_host with IPv6");
assert_eq!(dst.host(), "::1");
assert_eq!(dst.port(), None, "IPv6 didn't affect port");
dst.set_host("[::2]:1337").expect_err("set_host with IPv6 and sneaky port");
assert_eq!(dst.host(), "::1");
assert_eq!(dst.port(), None);
let mut dst = Destination {
uri: "http://hyper.rs:8080".parse().expect("initial parse 2"),
};
assert_eq!(dst.scheme(), "http");
assert_eq!(dst.host(), "hyper.rs");
assert_eq!(dst.port(), Some(8080));
dst.set_host("seanmonstar.com").expect("set host");
assert_eq!(dst.scheme(), "http");
assert_eq!(dst.host(), "seanmonstar.com");
assert_eq!(dst.port(), Some(8080));
dst.set_host("/im-not a host! >:)").unwrap_err();
assert_eq!(dst.scheme(), "http", "error doesn't modify dst");
assert_eq!(dst.host(), "seanmonstar.com", "error doesn't modify dst");
assert_eq!(dst.port(), Some(8080), "error doesn't modify dst");
dst.set_host("seanmonstar.com:3030").expect_err("set_host sneaky port");
assert_eq!(dst.scheme(), "http", "error doesn't modify dst");
assert_eq!(dst.host(), "seanmonstar.com", "error doesn't modify dst");
assert_eq!(dst.port(), Some(8080), "error doesn't modify dst");
dst.set_host("sean@nope").expect_err("set_host sneaky userinfo");
assert_eq!(dst.scheme(), "http", "error doesn't modify dst");
assert_eq!(dst.host(), "seanmonstar.com", "error doesn't modify dst");
assert_eq!(dst.port(), Some(8080), "error doesn't modify dst");
dst.set_host("[::1]").expect("set_host with IPv6");
assert_eq!(dst.host(), "::1");
assert_eq!(dst.port(), Some(8080), "IPv6 didn't affect port");
dst.set_host("[::2]:1337").expect_err("set_host with IPv6 and sneaky port");
assert_eq!(dst.host(), "::1");
assert_eq!(dst.port(), Some(8080));
}
#[test]
fn test_destination_set_port() {
let mut dst = Destination {
uri: "http://hyper.rs".parse().expect("initial parse"),
};
assert_eq!(dst.scheme(), "http");
assert_eq!(dst.host(), "hyper.rs");
assert_eq!(dst.port(), None);
dst.set_port(None);
assert_eq!(dst.scheme(), "http");
assert_eq!(dst.host(), "hyper.rs");
assert_eq!(dst.port(), None);
dst.set_port(8080);
assert_eq!(dst.scheme(), "http");
assert_eq!(dst.host(), "hyper.rs");
assert_eq!(dst.port(), Some(8080));
let mut dst = Destination {
uri: "http://hyper.rs:8080".parse().expect("initial parse 2"),
};
assert_eq!(dst.scheme(), "http");
assert_eq!(dst.host(), "hyper.rs");
assert_eq!(dst.port(), Some(8080));
dst.set_port(3030);
assert_eq!(dst.scheme(), "http");
assert_eq!(dst.host(), "hyper.rs");
assert_eq!(dst.port(), Some(3030));
dst.set_port(None);
assert_eq!(dst.scheme(), "http");
assert_eq!(dst.host(), "hyper.rs");
assert_eq!(dst.port(), None);
}
}