diff --git a/src/error.rs b/src/error.rs index 0934914..d4d57c8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -23,6 +23,12 @@ pub enum Error { BadHeader, /// Some unspecified `std::io::Error`. Synthetic error `500`. Io(IoError), + /// Proxy information was not properly formatted + BadProxy, + /// Proxy credentials were not properly formatted + BadProxyCreds, + /// Proxy could not connect + ProxyConnect, } impl Error { @@ -47,6 +53,9 @@ impl Error { Error::BadStatus => 500, Error::BadHeader => 500, Error::Io(_) => 500, + Error::BadProxy => 500, + Error::BadProxyCreds => 500, + Error::ProxyConnect => 500, } } @@ -62,6 +71,9 @@ impl Error { Error::BadStatus => "Bad Status", Error::BadHeader => "Bad Header", Error::Io(_) => "Network Error", + Error::BadProxy => "Malformed proxy", + Error::BadProxyCreds => "Failed to parse proxy credentials", + Error::ProxyConnect => "Proxy failed to connect", } } @@ -77,6 +89,9 @@ impl Error { Error::BadStatus => "Bad Status".to_string(), Error::BadHeader => "Bad Header".to_string(), Error::Io(ioe) => format!("Network Error: {}", ioe), + Error::BadProxy => "Malformed proxy".to_string(), + Error::BadProxyCreds => "Failed to parse proxy credentials".to_string(), + Error::ProxyConnect => "Proxy failed to connect".to_string(), } } } diff --git a/src/lib.rs b/src/lib.rs index 2978d21..274ec3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,6 +94,7 @@ mod body; mod error; mod header; mod pool; +mod proxy; mod request; mod response; mod stream; @@ -108,6 +109,7 @@ mod test; pub use crate::agent::Agent; pub use crate::error::Error; pub use crate::header::Header; +pub use crate::proxy::Proxy; pub use crate::request::Request; pub use crate::response::Response; diff --git a/src/proxy.rs b/src/proxy.rs new file mode 100644 index 0000000..71c8fed --- /dev/null +++ b/src/proxy.rs @@ -0,0 +1,127 @@ +use crate::error::Error; +use std::fmt; + +/// Kind of proxy connection (Basic, Digest, etc) +#[derive(Clone, Debug)] +pub(crate) enum ProxyKind { + Basic, +} + +/// Proxy server definition +#[derive(Clone, Debug)] +pub struct Proxy { + pub(crate) server: String, + pub(crate) port: u32, + pub(crate) user: Option, + pub(crate) password: Option, + pub(crate) kind: ProxyKind, +} + +impl Proxy { + fn parse_creds>( + creds: &Option, + ) -> Result<(Option, Option), Error> { + match creds { + Some(creds) => { + let mut parts = creds + .as_ref() + .splitn(2, ':') + .collect::>() + .into_iter(); + + if parts.len() != 2 { + Err(Error::BadProxyCreds) + } else { + Ok(( + parts.next().map(String::from), + parts.next().map(String::from), + )) + } + } + None => Ok((None, None)), + } + } + + fn parse_address>(host: &Option) -> Result<(String, Option), Error> { + match host { + Some(host) => { + let mut parts = host.as_ref().split(':').collect::>().into_iter(); + let host = parts.next().ok_or(Error::BadProxy)?; + let port = parts.next(); + Ok(( + String::from(host), + port.and_then(|port| port.parse::().ok()), + )) + } + None => Err(Error::BadProxy), + } + } + + fn use_authorization(&self) -> bool { + self.user.is_some() && self.password.is_some() + } + + pub fn new>(proxy: S) -> Result { + let mut parts = proxy + .as_ref() + .rsplitn(2, '@') + .collect::>() + .into_iter() + .rev(); + + let (user, password) = if parts.len() == 2 { + Proxy::parse_creds(&parts.next())? + } else { + (None, None) + }; + + let (server, port) = Proxy::parse_address(&parts.next())?; + + Ok(Self { + server, + user, + password, + port: port.unwrap_or(8080), + kind: ProxyKind::Basic, + }) + } + + pub(crate) fn kind(mut self, kind: ProxyKind) -> Self { + self.kind = kind; + self + } + + pub fn connect>(&self, host: S, port: u16) -> String { + format!( + "CONNECT {}:{} HTTP/1.1\r\n\ +Host: {}:{}\r\n\ +User-Agent: something/1.0.0\r\n\ +Proxy-Connection: Keep-Alive\r\n\r\n", + host.as_ref(), + port, + host.as_ref(), + port + ) + } + + pub(crate) fn verify_response(response: &[u8]) -> Result<(), Error> { + let response_string = String::from_utf8_lossy(response); + let top_line = response_string.lines().next().ok_or(Error::ProxyConnect)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::Proxy; + + #[test] + fn parse_proxy() { + let proxy = Proxy::new("user:p@ssw0rd@localhost:9999").unwrap(); + assert_eq!(proxy.user, Some(String::from("user"))); + assert_eq!(proxy.password, Some(String::from("p@ssw0rd"))); + assert_eq!(proxy.server, String::from("localhost")); + assert_eq!(proxy.port, 9999); + } +} diff --git a/src/request.rs b/src/request.rs index 09e8ee8..8422b15 100644 --- a/src/request.rs +++ b/src/request.rs @@ -45,6 +45,7 @@ pub struct Request { pub(crate) timeout_read: u64, pub(crate) timeout_write: u64, pub(crate) redirects: u32, + pub(crate) proxy: Option, } impl ::std::fmt::Debug for Request { @@ -514,4 +515,18 @@ impl Request { .join(&self.path) .map_err(|e| Error::BadUrl(format!("{}", e))) } + + /// Set the proxy server to use for the connection. + /// + /// Example: + /// ``` + /// let proxy = ureq::Proxy::new("user:password@cool.proxy:9090").unwrap(); + /// let req = ureq::post("https://cool.server") + /// .set_proxy(proxy) + /// .build(); + /// ``` + pub fn set_proxy(&mut self, proxy: crate::proxy::Proxy) -> &mut Request { + self.proxy = Some(proxy); + self + } } diff --git a/src/stream.rs b/src/stream.rs index 74cf7b2..d6ca2be 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -159,10 +159,13 @@ pub(crate) fn connect_https(unit: &Unit) -> Result { pub(crate) fn connect_host(unit: &Unit, hostname: &str, port: u16) -> Result { // - let ips: Vec = format!("{}:{}", hostname, port) - .to_socket_addrs() - .map_err(|e| Error::DnsFailed(format!("{}", e)))? - .collect(); + let ips: Vec = match unit.proxy { + Some(ref proxy) => format!("{}:{}", proxy.server, proxy.port), + None => format!("{}:{}", hostname, port), + } + .to_socket_addrs() + .map_err(|e| Error::DnsFailed(format!("{}", e)))? + .collect(); if ips.is_empty() { return Err(Error::DnsFailed(format!("No ip address for {}", hostname))); @@ -172,7 +175,7 @@ pub(crate) fn connect_host(unit: &Unit, hostname: &str, port: u16) -> Result TcpStream::connect(&sock_addr), _ => TcpStream::connect_timeout( &sock_addr, @@ -188,9 +191,7 @@ pub(crate) fn connect_host(unit: &Unit, hostname: &str, port: u16) -> Result 0 { @@ -198,9 +199,25 @@ pub(crate) fn connect_host(unit: &Unit, hostname: &str, port: u16) -> Result, } impl Unit { @@ -86,6 +88,7 @@ impl Unit { timeout_read: req.timeout_read, timeout_write: req.timeout_write, method: req.method.clone(), + proxy: req.proxy.clone(), } }