Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

automatically detect and use system HTTP/HTTPS proxy #141

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ tabwriter = "1.2.1"
tar = "0.4.38"
tempfile = "3.3.0"
tokio = { version = "1.23.0", features = ["full"] }
hyper-proxy = "0.9.1"
http = "1.2.0"
headers = "0.3.9"
url = "2.5.4"
59 changes: 45 additions & 14 deletions src/hub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@ use std::io;
use std::ops::Deref;
use std::path::PathBuf;
use std::pin::Pin;
use hyper_proxy::{ProxyConnector, Intercept, Proxy};
use headers::Authorization;
use url::Url;
use hyper::Client;
use crate::proxy::EnvProxy;

pub struct HubConfig {
pub secret: oauth2::ApplicationSecret,
pub tokens_path: PathBuf,
}

pub struct Hub(DriveHub<HttpsConnector<HttpConnector>>);
pub struct Hub(DriveHub<ProxyConnector<HttpsConnector<HttpConnector>>>);

impl Deref for Hub {
type Target = DriveHub<HttpsConnector<HttpConnector>>;
type Target = DriveHub<ProxyConnector<HttpsConnector<HttpConnector>>>;

fn deref(&self) -> &Self::Target {
&self.0
Expand All @@ -30,23 +35,15 @@ impl Deref for Hub {

impl Hub {
pub async fn new(auth: Auth) -> Hub {
let connector = HttpsConnectorBuilder::new()
.with_native_roots()
.https_or_http()
.enable_http1()
.enable_http2()
.build();

let http_client = hyper::Client::builder().build(connector);

let http_client = create_client();
Hub(google_drive3::DriveHub::new(http_client, auth.0))
}
}

pub struct Auth(pub Authenticator<HttpsConnector<HttpConnector>>);
pub struct Auth(pub Authenticator<ProxyConnector<HttpsConnector<HttpConnector>>>);

impl Deref for Auth {
type Target = Authenticator<HttpsConnector<HttpConnector>>;
type Target = Authenticator<ProxyConnector<HttpsConnector<HttpConnector>>>;

fn deref(&self) -> &Self::Target {
&self.0
Expand All @@ -60,10 +57,12 @@ impl Auth {
) -> Result<Auth, io::Error> {
let secret = oauth2_secret(config);
let delegate = Box::new(AuthDelegate);
let client = create_client();

let auth = oauth2::InstalledFlowAuthenticator::builder(
let auth = oauth2::InstalledFlowAuthenticator::with_client::<hyper::Client<ProxyConnector<HttpsConnector<HttpConnector>>>>(
secret,
oauth2::InstalledFlowReturnMethod::HTTPPortRedirect(8085),
client,
)
.persist_tokens_to_disk(tokens_path)
.flow_delegate(delegate)
Expand Down Expand Up @@ -110,3 +109,35 @@ async fn present_user_url(url: &str) -> Result<String, String> {
println!("{}", url);
Ok(String::new())
}

fn create_client() -> Client<ProxyConnector<HttpsConnector<HttpConnector>>> {
let connector = HttpsConnectorBuilder::new()
.with_native_roots()
.https_or_http()
.enable_http1()
.enable_http2()
.build();
let env_proxy = EnvProxy::try_from_env();
let proxy_connector = match env_proxy {
Some(val) => {
let uri_str = val.uri_str();
let mut url = Url::parse(uri_str).unwrap();
let username = String::from(url.username()).clone();
let password = String::from(url.password().unwrap_or_default()).clone();
let _ = url.set_username("");
let _ = url.set_password(None);
let uri = url.as_str().parse();
let mut proxy = Proxy::new(Intercept::All, uri.unwrap());
if username != "" {
proxy.set_authorization(Authorization::basic(&username, &password));
}
println!("using system proxy {}", uri_str);
ProxyConnector::from_proxy(connector, proxy).unwrap()
},
None => {
// println!("not using proxy!");
ProxyConnector::new(connector).unwrap()
}
};
hyper::Client::builder().build(proxy_connector)
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod files;
pub mod hub;
pub mod permissions;
pub mod version;
pub mod proxy;

use clap::{Parser, Subcommand};
use common::delegate::ChunkSize;
Expand Down
137 changes: 137 additions & 0 deletions src/proxy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/// modified from https://github.com/algesten/ureq/

use http::Uri;
use std::io;

/// Proxy protocol
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub(crate) enum Proto {
Http,
Https,
}

/// Proxy server settings
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct EnvProxy {
proxy: String,
}

impl EnvProxy {
/// Create a proxy from a uri.
///
/// # Arguments:
///
/// * `proxy` - a str of format `<protocol>://<user>:<password>@<host>:port` . All parts
/// except host are optional.
///
/// ### Protocols
///
/// * `http`: HTTP CONNECT proxy
/// * `https`: HTTPS CONNECT proxy (requires a TLS provider)
///
/// # Examples proxy formats
///
/// * `http://127.0.0.1:8080`
/// * `localhost`

fn new_with_flag(proxy: &str) -> Result<Self, Error> {
let uri = proxy.parse::<Uri>().unwrap();

// The uri must have an authority part (with the host), or
// it is invalid.
let _ = uri.authority().ok_or(Error::InvalidProxyUrl)?;

// The default protocol is Proto::HTTP
let scheme = uri.scheme_str().unwrap_or("http");
let _proto = match scheme {
"http" => Proto::Http,
"https" => Proto::Https,
&_ => todo!("only support http or https protocol!"),
};

Ok(Self {
proxy: proxy.to_string(),
})
}

/// Read proxy settings from environment variables.
///
/// The environment variable is expected to contain a proxy URI. The following
/// environment variables are attempted:
///
/// * `ALL_PROXY`
/// * `HTTPS_PROXY`
/// * `HTTP_PROXY`
///
/// Returns `None` if no environment variable is set or the URI is invalid.
pub fn try_from_env() -> Option<Self> {
macro_rules! try_env {
($($env:literal),+) => {
$(
if let Ok(env) = std::env::var($env) {
if let Ok(proxy) = Self::new_with_flag(&env) {
return Some(proxy);
}
}
)+
};
}

try_env!(
"ALL_PROXY",
"all_proxy",
"HTTPS_PROXY",
"https_proxy",
"HTTP_PROXY",
"http_proxy"
);
None
}

pub fn uri_str(&self) -> &String {
&self.proxy
}

}

#[derive(Debug)]
#[non_exhaustive]
pub enum Error {

/// When [`http_status_as_error()`](crate::config::ConfigBuilder::http_status_as_error) is true,
/// 4xx and 5xx response status codes are translated to this error.
///
/// This is the default behavior.
StatusCode(u16),

/// Errors arising from the http-crate.
///
/// These errors happen for things like invalid characters in header names.
Http,

/// Error if the URI is missing scheme or host.
BadUri(String),

/// Error in io such as the TCP socket.
Io(io::Error),

/// Error when resolving a hostname fails.
HostNotFound,

/// A redirect failed.
///
/// This happens when ureq encounters a redirect when sending a request body
/// such as a POST request, and receives a 307/308 response. ureq refuses to
/// redirect the POST body and instead raises this error.
RedirectFailed,

/// Error when creating proxy settings.
InvalidProxyUrl,

/// A connection failed.
ConnectionFailed,

/// A send body (Such as `&str`) is larger than the `content-length` header.
BodyExceedsLimit(u64),
}