initial proxy impl

This commit is contained in:
rustysec
2020-03-13 11:28:50 -07:00
committed by Martin Algesten
parent 2956683870
commit 3b0df412ef
6 changed files with 190 additions and 11 deletions

View File

@@ -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(),
}
}
}

View File

@@ -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;

127
src/proxy.rs Normal file
View File

@@ -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<String>,
pub(crate) password: Option<String>,
pub(crate) kind: ProxyKind,
}
impl Proxy {
fn parse_creds<S: AsRef<str>>(
creds: &Option<S>,
) -> Result<(Option<String>, Option<String>), Error> {
match creds {
Some(creds) => {
let mut parts = creds
.as_ref()
.splitn(2, ':')
.collect::<Vec<&str>>()
.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<S: AsRef<str>>(host: &Option<S>) -> Result<(String, Option<u32>), Error> {
match host {
Some(host) => {
let mut parts = host.as_ref().split(':').collect::<Vec<&str>>().into_iter();
let host = parts.next().ok_or(Error::BadProxy)?;
let port = parts.next();
Ok((
String::from(host),
port.and_then(|port| port.parse::<u32>().ok()),
))
}
None => Err(Error::BadProxy),
}
}
fn use_authorization(&self) -> bool {
self.user.is_some() && self.password.is_some()
}
pub fn new<S: AsRef<str>>(proxy: S) -> Result<Self, Error> {
let mut parts = proxy
.as_ref()
.rsplitn(2, '@')
.collect::<Vec<&str>>()
.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<S: AsRef<str>>(&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);
}
}

View File

@@ -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<crate::proxy::Proxy>,
}
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
}
}

View File

@@ -159,10 +159,13 @@ pub(crate) fn connect_https(unit: &Unit) -> Result<Stream, Error> {
pub(crate) fn connect_host(unit: &Unit, hostname: &str, port: u16) -> Result<TcpStream, Error> {
//
let ips: Vec<SocketAddr> = format!("{}:{}", hostname, port)
.to_socket_addrs()
.map_err(|e| Error::DnsFailed(format!("{}", e)))?
.collect();
let ips: Vec<SocketAddr> = 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<Tcp
let sock_addr = ips[0];
// connect with a configured timeout.
let stream = match unit.timeout_connect {
let mut stream = match unit.timeout_connect {
0 => 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<Tcp
.set_read_timeout(Some(Duration::from_millis(unit.timeout_read as u64)))
.ok();
} else {
stream
.set_read_timeout(None)
.ok();
stream.set_read_timeout(None).ok();
}
if unit.timeout_write > 0 {
@@ -198,9 +199,25 @@ pub(crate) fn connect_host(unit: &Unit, hostname: &str, port: u16) -> Result<Tcp
.set_write_timeout(Some(Duration::from_millis(unit.timeout_write as u64)))
.ok();
} else {
stream
.set_write_timeout(None)
.ok();
stream.set_write_timeout(None).ok();
}
if let Some(ref proxy) = unit.proxy {
write!(stream, "{}", proxy.connect(hostname, port)).unwrap();
stream.flush()?;
let mut proxy_response = Vec::new();
loop {
let mut buf = vec![0; 256];
let total = stream.read(&mut buf)?;
proxy_response.append(&mut buf);
if total < 256 {
break;
}
}
crate::Proxy::verify_response(&proxy_response)?;
}
Ok(stream)

View File

@@ -11,6 +11,7 @@ use crate::agent::AgentState;
use crate::body::{self, Payload, SizedReader};
use crate::header;
use crate::stream::{self, connect_https, connect_test, Stream};
use crate::Proxy;
use crate::{Error, Header, Request, Response};
use crate::pool::DEFAULT_HOST;
@@ -29,6 +30,7 @@ pub(crate) struct Unit {
pub timeout_read: u64,
pub timeout_write: u64,
pub method: String,
pub proxy: Option<Proxy>,
}
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(),
}
}