use crate::connector::Connector;
use crate::errors::{new_io_error, Error, ReplyError, Result};
use crate::response::ResponseBuilder;
use crate::socket::Socket;
use crate::{Request, Response};
use bytes::Bytes;
use http::uri::Authority;
use http::HeaderValue;
use percent_encoding::percent_decode;
use std::io::{BufReader, Read, Write};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
use std::str::FromStr;
impl Proxy {
fn http(uri: &http::Uri) -> Result<Self> {
let (host, addr) = uri_to_host_addr(uri)?;
Ok(Proxy::HTTP(HttpProxy {
uri: uri.clone(),
https: false,
auth: None,
addr,
host,
}))
}
fn https(uri: &http::Uri) -> Result<Self> {
let (host, addr) = uri_to_host_addr(uri)?;
Ok(Proxy::HTTP(HttpProxy {
uri: uri.clone(),
https: true,
auth: None,
addr,
host: host.to_string(),
}))
}
fn socks5(uri: &http::Uri) -> Result<Self> {
let (host, addr) = uri_to_host_addr(uri)?;
Ok(Proxy::Socket(Socket5Proxy::new(
uri.clone(),
host.to_string(),
addr,
false,
)))
}
fn socks5h(uri: &http::Uri) -> Result<Self> {
let (host, addr) = uri_to_host_addr(uri)?;
Ok(Proxy::Socket(Socket5Proxy::new(
uri.clone(),
host.to_string(),
addr,
true,
)))
}
fn with_basic_auth<T: Into<String>, U: Into<String>>(mut self, username: T, password: U) -> Self {
self.set_basic_auth(username, password);
self
}
fn set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U) {
match *self {
Proxy::HTTP(HttpProxy { ref mut auth, .. }) => {
let header = encode_basic_auth(username.into(), Some(&password.into()));
*auth = Some(header);
}
Proxy::Socket(ref mut s) => {
s.set_auth(username.into(), password.into());
}
}
}
pub fn uri(&self) -> http::Uri {
match self {
Proxy::HTTP(http) => http.uri.clone(),
Proxy::Socket(socket) => socket.uri.clone(),
}
}
pub fn parse<U>(url: U) -> Result<Self>
where
http::Uri: TryFrom<U>,
<http::Uri as TryFrom<U>>::Error: Into<http::Error>,
{
let url: http::Uri = TryFrom::try_from(url).map_err(Into::into)?;
let mut scheme = match url.scheme_str() {
Some("http") => Self::http(&url)?,
Some("https") => Self::https(&url)?,
Some("socks5") => Self::socks5(&url)?,
Some("socks5h") => Self::socks5h(&url)?,
_ => {
return Err(new_io_error(
std::io::ErrorKind::NotConnected,
"unknown proxy scheme",
));
}
};
if let Some((username, Some(password))) = get_auth_from_authority(url.authority()) {
let decoded_username = percent_decode(username.as_bytes()).decode_utf8_lossy();
let decoded_password = percent_decode(password.as_bytes()).decode_utf8_lossy();
scheme = scheme.with_basic_auth(decoded_username, decoded_password);
}
Ok(scheme)
}
fn to_addr(&self) -> Result<SocketAddr> {
match self.clone() {
Proxy::HTTP(HttpProxy { addr, .. }) => Ok(addr),
Proxy::Socket(s) => Ok(s.addr()),
}
}
#[cfg(feature = "tls")]
fn domain(&self) -> Result<&str> {
match self {
Proxy::HTTP(HttpProxy { host, .. }) => Ok(host.as_str()),
Proxy::Socket(s) => Ok(s.host()),
}
}
}
fn uri_to_host_addr(uri: &http::Uri) -> Result<(String, SocketAddr)> {
let host = uri.host().ok_or(new_io_error(
std::io::ErrorKind::InvalidData,
"url not host",
))?;
let to_addr = || {
let port = match uri.port_u16() {
None => match uri.scheme_str() {
Some("socks5") | Some("socks5h") => Some(1080),
Some("http") => Some(80),
Some("https") => Some(443),
_ => None,
},
Some(p) => Some(p),
}
.ok_or(new_io_error(
std::io::ErrorKind::InvalidData,
"no port in url",
))?;
(host, port).to_socket_addrs()?.next().ok_or(new_io_error(
std::io::ErrorKind::InvalidData,
"no addr in url",
))
};
Ok((host.to_string(), to_addr()?))
}
fn get_auth_from_authority(authority: Option<&Authority>) -> Option<(String, Option<String>)> {
match authority {
None => None,
Some(authority) => {
let mut full = authority.to_string();
if full.contains('@') {
if let Some(port) = authority.port() {
if let Some(remove) = full.strip_suffix(&format!(":{}", port.as_str())) {
full = remove.to_string();
}
}
if let Some(remove) = full.strip_suffix(authority.host()) {
full = remove.to_string();
}
let at = full.pop();
if at.is_none() {
return None;
} else if let Some((username, password)) = full.split_once(':') {
let password = if password.is_empty() {
None
} else {
Some(password.to_string())
};
return Some((username.to_string(), password));
}
}
None
}
}
}
pub fn encode_basic_auth<U, P>(username: U, password: Option<P>) -> HeaderValue
where
U: std::fmt::Display,
P: std::fmt::Display,
{
use base64::prelude::BASE64_STANDARD;
use base64::write::EncoderWriter;
let mut buf = b"Basic ".to_vec();
{
let mut encoder = EncoderWriter::new(&mut buf, &BASE64_STANDARD);
let _ = write!(encoder, "{username}:");
if let Some(password) = password {
let _ = write!(encoder, "{password}");
}
}
let mut header = HeaderValue::from_bytes(&buf).expect("base64 is always valid HeaderValue");
header.set_sensitive(true);
header
}
#[derive(Debug, Clone, PartialEq)]
pub enum Proxy {
HTTP(HttpProxy),
Socket(Socket5Proxy),
}
#[derive(Clone, Debug, PartialEq)]
pub struct HttpProxy {
uri: http::Uri,
https: bool,
auth: Option<HeaderValue>,
addr: SocketAddr,
host: String,
}
impl HttpProxy {
fn raw(&self, host_port: &str) -> Result<Bytes> {
let mut br = Request::builder()
.version(http::version::Version::HTTP_11)
.uri(http::Uri::builder().path_and_query(host_port).build()?)
.method(http::method::Method::CONNECT)
.header("Host", host_port)
.header("Proxy-Connection", "Keep-Alive");
if let Some(auth) = &self.auth {
br = br.header("Proxy-Authorization", auth);
}
let br: Request = br.body(None)?.into();
Ok(br.to_raw())
}
#[allow(clippy::unused_io_amount)]
fn read_resp(&self, proxy_socket: &mut Socket) -> Result<Response> {
let mut buffer = [0; 128];
proxy_socket.read(&mut buffer[..])?;
let reader = BufReader::new(buffer.as_slice());
let proxy_response = ResponseBuilder::new(reader, Default::default()).build()?;
if proxy_response.status_code() != http::StatusCode::OK {
return Err(new_io_error(
std::io::ErrorKind::NotConnected,
"not connect proxy",
));
}
Ok(proxy_response)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ProxySocket {
target: http::Uri,
proxy: Option<Proxy>,
}
impl ProxySocket {
pub fn new(target: &http::Uri, proxy: &Option<Proxy>) -> Self {
Self {
target: target.clone(),
proxy: proxy.clone(),
}
}
pub fn conn_with_connector(self, connector: &Connector) -> Result<Socket> {
let addr = self.get_conn_addr()?;
let mut socket = connector.connect_with_addr(addr)?;
match &self.proxy {
None => {
let _target_host = self.target.host().ok_or(new_io_error(
std::io::ErrorKind::InvalidData,
"no host in url",
))?;
#[cfg(feature = "tls")]
if self.target.scheme() == Some(&http::uri::Scheme::HTTPS) {
socket = connector.upgrade_to_tls(socket, _target_host)?;
}
Ok(socket)
}
Some(proxy) => {
let target_host = self.target.host().ok_or(new_io_error(
std::io::ErrorKind::InvalidData,
"no host in url",
))?;
let port = match self.target.port() {
Some(p) => p.as_u16(),
None => {
if self.target.scheme() == Some(&http::uri::Scheme::HTTPS) {
443u16
} else {
80u16
}
}
};
match proxy {
Proxy::HTTP(h) => {
#[cfg(feature = "tls")]
if h.https {
socket = connector.upgrade_to_tls(socket, proxy.domain()?)?;
}
socket.write_all(&h.raw(&format!("{}:{}", target_host, port))?)?;
socket.flush()?;
h.read_resp(&mut socket)?;
#[cfg(feature = "tls")]
if self.target.scheme() == Some(&http::uri::Scheme::HTTPS) {
socket = connector.upgrade_to_tls(socket, target_host)?;
}
Ok(socket)
}
Proxy::Socket(s) => {
s.conn(&mut socket, &self.target)?;
Ok(socket)
}
}
}
}
}
fn get_conn_addr(&self) -> Result<SocketAddr> {
match &self.proxy {
None => {
let original_host = self.target.host().ok_or(new_io_error(
std::io::ErrorKind::InvalidData,
"no host in url",
))?;
let port = default_port(&self.target).ok_or(new_io_error(
std::io::ErrorKind::InvalidData,
"no port in url",
))?;
let target_addr = (original_host, port)
.to_socket_addrs()?
.next()
.ok_or(new_io_error(
std::io::ErrorKind::InvalidData,
"no addr in url",
))?;
Ok(target_addr)
}
Some(proxy) => {
let proxy_addr = proxy.to_addr()?;
Ok(proxy_addr)
}
}
}
}
fn default_port(uri: &http::Uri) -> Option<u16> {
match uri.port_u16() {
Some(p) => Some(p),
None => match uri.scheme_str() {
Some("https") => Some(443u16),
Some("http") => Some(80u16),
Some("socks5") | Some("socks5h") => Some(1080u16),
_ => None,
},
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Socket5Proxy {
uri: http::Uri,
host: String,
addr: SocketAddr,
auth: Option<AuthenticationMethod>,
remote_dns: bool,
}
impl Socket5Proxy {
fn new(uri: http::Uri, host: String, addr: SocketAddr, remote_dns: bool) -> Self {
Socket5Proxy {
uri,
host,
addr,
auth: None,
remote_dns,
}
}
fn set_auth(&mut self, username: String, password: String) {
self.auth = Some(AuthenticationMethod::Password { username, password });
}
#[cfg(feature = "tls")]
pub(crate) fn host(&self) -> &str {
&self.host
}
pub(crate) fn addr(&self) -> SocketAddr {
self.addr
}
pub(crate) fn conn(&self, socket: &mut Socket, target: &http::Uri) -> Result<()> {
let method = self.version_methods(socket)?;
let auth_methods = self.which_method_accepted(socket, method)?;
self.use_password_auth(socket, auth_methods)?;
self.request_header(socket, target, Socks5Command::TCPConnect)?;
self.read_request_reply(socket)?;
Ok(())
}
fn version_methods(&self, proxy_socket: &mut Socket) -> Result<&AuthenticationMethod> {
let mut main_method = &AuthenticationMethod::None;
let mut methods = vec![main_method];
if let Some(method) = &self.auth {
methods.push(method);
main_method = method;
}
let mut packet = vec![consts::SOCKS5_VERSION, methods.len() as u8];
let auth: Vec<u8> = methods.into_iter().map(|l| l.into()).collect::<Vec<_>>();
packet.extend(auth);
proxy_socket.write_all(&packet)?;
Ok(main_method)
}
fn use_password_auth(
&self,
proxy_socket: &mut Socket,
method: AuthenticationMethod,
) -> Result<bool> {
if let AuthenticationMethod::Password { username, password } = method {
let user_bytes = username.as_bytes();
let pass_bytes = password.as_bytes();
let mut packet: Vec<u8> = vec![1, user_bytes.len() as u8];
packet.extend(user_bytes);
packet.push(pass_bytes.len() as u8);
packet.extend(pass_bytes);
proxy_socket.write_all(&packet)?;
let mut buf = [0u8, 2];
proxy_socket.read_exact(&mut buf)?;
let [_version, is_success] = buf;
if is_success != consts::SOCKS5_REPLY_SUCCEEDED {
return Err(Error::Other(format!(
"Authentication with username `{}`, rejected.",
username
)));
}
return Ok(is_success != 0);
}
Ok(true)
}
fn which_method_accepted(
&self,
proxy_socket: &mut Socket,
auth_method: &AuthenticationMethod,
) -> Result<AuthenticationMethod> {
let mut buf = [0u8; 2];
proxy_socket.read_exact(&mut buf)?;
let [version, method] = buf;
if version != consts::SOCKS5_VERSION {
return Err(new_io_error(
std::io::ErrorKind::InvalidData,
"unsupported SOCKS version",
));
}
match method {
consts::SOCKS5_AUTH_METHOD_NONE => Ok(AuthenticationMethod::None),
consts::SOCKS5_AUTH_METHOD_PASSWORD => Ok(auth_method.clone()),
_ => {
proxy_socket.write_all(&[
consts::SOCKS5_VERSION,
consts::SOCKS5_AUTH_METHOD_NOT_ACCEPTABLE,
])?;
Err(Error::Other("no acceptable auth methods".to_string()))
}
}
}
fn request_header(
&self,
proxy_socket: &mut Socket,
target: &http::Uri,
cmd: Socks5Command,
) -> Result<TargetAddr> {
let target = TargetAddr::from_uri(target, self.remote_dns)?;
let (padding, packet) = target.to_be_bytes(cmd)?;
proxy_socket.write_all(&packet[..padding])?;
proxy_socket.flush()?;
Ok(target)
}
fn read_request_reply(&self, proxy_socket: &mut Socket) -> Result<TargetAddr> {
let mut buf = [0u8; 4];
proxy_socket.read_exact(&mut buf)?;
let [version, reply, _rsv, address_type] = buf;
if version != consts::SOCKS5_VERSION {
return Err(Error::Other(format!("version {:?}", version)));
}
if reply != consts::SOCKS5_REPLY_SUCCEEDED {
return Err(Error::ReplyError(ReplyError::from(reply))); }
let address = read_address(proxy_socket, address_type)?;
Ok(address)
}
}
fn read_port(proxy_socket: &mut Socket) -> Result<u16> {
let mut port = [0u8; 2];
proxy_socket.read_exact(&mut port)?;
let port = (port[0] as u16) << 8 | port[1] as u16;
Ok(port)
}
fn read_address(proxy_socket: &mut Socket, addr_type: u8) -> Result<TargetAddr> {
let addr = match addr_type {
consts::SOCKS5_ADDR_TYPE_IPV4 => {
let mut buf = [0u8; 4];
proxy_socket.read_exact(&mut buf)?;
let [a, b, c, d] = buf;
TargetAddr::IP(SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::new(a, b, c, d),
read_port(proxy_socket)?,
)))
}
consts::SOCKS5_ADDR_TYPE_IPV6 => {
let mut buf = [0u8; 16];
proxy_socket.read_exact(&mut buf)?;
TargetAddr::IP(SocketAddr::V6(SocketAddrV6::new(
Ipv6Addr::from(buf),
read_port(proxy_socket)?,
0,
0,
)))
}
consts::SOCKS5_ADDR_TYPE_DOMAIN_NAME => {
let mut len = [0u8];
proxy_socket.read_exact(&mut len)?;
let mut domain = vec![0u8; len[0] as usize];
proxy_socket.read_exact(&mut domain)?;
TargetAddr::Domain(
String::from_utf8_lossy(&domain).to_string(),
read_port(proxy_socket)?,
)
}
_ => return Err(Error::Other("IncorrectAddressType".to_string())),
};
Ok(addr)
}
#[derive(Debug, PartialEq)]
enum Socks5Command {
TCPConnect,
}
impl Socks5Command {
#[inline]
pub fn as_u8(&self) -> u8 {
match self {
Socks5Command::TCPConnect => consts::SOCKS5_CMD_TCP_CONNECT,
}
}
}
#[derive(Debug, Clone, PartialEq)]
enum TargetAddr {
IP(SocketAddr),
Domain(String, u16),
}
#[derive(Debug, PartialEq, Clone)]
enum AuthenticationMethod {
None,
Password { username: String, password: String },
}
impl From<&AuthenticationMethod> for u8 {
fn from(val: &AuthenticationMethod) -> Self {
match val {
AuthenticationMethod::None => consts::SOCKS5_AUTH_METHOD_NONE,
AuthenticationMethod::Password { .. } => consts::SOCKS5_AUTH_METHOD_PASSWORD,
}
}
}
impl TargetAddr {
pub fn from_uri(value: &http::Uri, remote_dns: bool) -> Result<Self> {
let port = default_port(value).ok_or(new_io_error(
std::io::ErrorKind::InvalidData,
"not found port",
))?;
let host = value.host().ok_or(new_io_error(
std::io::ErrorKind::InvalidData,
"not found host",
))?;
if let Ok(ip) = IpAddr::from_str(host) {
Ok(TargetAddr::IP(SocketAddr::new(ip, port)))
} else {
if !remote_dns {
Ok(TargetAddr::IP(
(host, port).to_socket_addrs()?.next().ok_or(new_io_error(
std::io::ErrorKind::InvalidData,
"url not addr",
))?,
))
} else {
Ok(TargetAddr::Domain(host.to_string(), port))
}
}
}
pub fn to_be_bytes(&self, cmd: Socks5Command) -> Result<(usize, Vec<u8>)> {
let mut packet = [0u8; consts::MAX_ADDR_LEN + 3];
let padding;
packet[..3].copy_from_slice(&[consts::SOCKS5_VERSION, cmd.as_u8(), 0x00]);
match self {
TargetAddr::IP(SocketAddr::V4(addr)) => {
padding = 10;
packet[3] = 0x01;
packet[4..8].copy_from_slice(&addr.ip().octets()); packet[8..padding].copy_from_slice(&addr.port().to_be_bytes());
}
TargetAddr::IP(SocketAddr::V6(addr)) => {
padding = 22;
packet[3] = 0x04;
packet[4..20].copy_from_slice(&addr.ip().octets()); packet[20..padding].copy_from_slice(&addr.port().to_be_bytes());
}
TargetAddr::Domain(ref domain, port) => {
if domain.len() > u8::MAX as usize {
return Err(new_io_error(
std::io::ErrorKind::InvalidData,
"domain name too long",
));
}
padding = 5 + domain.len() + 2;
packet[3] = 0x03; packet[4] = domain.len() as u8; packet[5..(5 + domain.len())].copy_from_slice(domain.as_bytes()); packet[(5 + domain.len())..padding].copy_from_slice(&port.to_be_bytes());
}
}
Ok((padding, packet.to_vec()))
}
}
impl From<u8> for ReplyError {
fn from(value: u8) -> Self {
match value {
consts::SOCKS5_REPLY_SUCCEEDED => ReplyError::Succeeded,
consts::SOCKS5_REPLY_GENERAL_FAILURE => ReplyError::GeneralFailure,
consts::SOCKS5_REPLY_CONNECTION_NOT_ALLOWED => ReplyError::ConnectionNotAllowed,
consts::SOCKS5_REPLY_NETWORK_UNREACHABLE => ReplyError::NetworkUnreachable,
consts::SOCKS5_REPLY_HOST_UNREACHABLE => ReplyError::HostUnreachable,
consts::SOCKS5_REPLY_CONNECTION_REFUSED => ReplyError::ConnectionRefused,
consts::SOCKS5_REPLY_TTL_EXPIRED => ReplyError::TtlExpired,
consts::SOCKS5_REPLY_COMMAND_NOT_SUPPORTED => ReplyError::CommandNotSupported,
consts::SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED => ReplyError::AddressTypeNotSupported,
_ => unreachable!("ReplyError code unsupported."),
}
}
}
#[rustfmt::skip]
pub mod consts {
pub const MAX_ADDR_LEN: usize = 260;
pub const SOCKS5_VERSION: u8 = 0x05;
pub const SOCKS5_AUTH_METHOD_NONE: u8 = 0x00;
pub const SOCKS5_AUTH_METHOD_PASSWORD: u8 = 0x02;
pub const SOCKS5_AUTH_METHOD_NOT_ACCEPTABLE: u8 = 0xff;
pub const SOCKS5_CMD_TCP_CONNECT: u8 = 0x01;
pub const SOCKS5_ADDR_TYPE_IPV4: u8 = 0x01;
pub const SOCKS5_ADDR_TYPE_DOMAIN_NAME: u8 = 0x03;
pub const SOCKS5_ADDR_TYPE_IPV6: u8 = 0x04;
pub const SOCKS5_REPLY_SUCCEEDED: u8 = 0x00;
pub const SOCKS5_REPLY_GENERAL_FAILURE: u8 = 0x01;
pub const SOCKS5_REPLY_CONNECTION_NOT_ALLOWED: u8 = 0x02;
pub const SOCKS5_REPLY_NETWORK_UNREACHABLE: u8 = 0x03;
pub const SOCKS5_REPLY_HOST_UNREACHABLE: u8 = 0x04;
pub const SOCKS5_REPLY_CONNECTION_REFUSED: u8 = 0x05;
pub const SOCKS5_REPLY_TTL_EXPIRED: u8 = 0x06;
pub const SOCKS5_REPLY_COMMAND_NOT_SUPPORTED: u8 = 0x07;
pub const SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED: u8 = 0x08;
}