diff --git a/Cargo.toml b/Cargo.toml index e6688aa..2598e86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" \ No newline at end of file diff --git a/src/hub.rs b/src/hub.rs index 51e008b..5e5aa1b 100644 --- a/src/hub.rs +++ b/src/hub.rs @@ -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>); +pub struct Hub(DriveHub>>); impl Deref for Hub { - type Target = DriveHub>; + type Target = DriveHub>>; fn deref(&self) -> &Self::Target { &self.0 @@ -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>); +pub struct Auth(pub Authenticator>>); impl Deref for Auth { - type Target = Authenticator>; + type Target = Authenticator>>; fn deref(&self) -> &Self::Target { &self.0 @@ -60,10 +57,12 @@ impl Auth { ) -> Result { 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::>>>( secret, oauth2::InstalledFlowReturnMethod::HTTPPortRedirect(8085), + client, ) .persist_tokens_to_disk(tokens_path) .flow_delegate(delegate) @@ -110,3 +109,35 @@ async fn present_user_url(url: &str) -> Result { println!("{}", url); Ok(String::new()) } + +fn create_client() -> Client>> { + 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) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index b309066..1f2c7b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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; diff --git a/src/proxy.rs b/src/proxy.rs new file mode 100644 index 0000000..5509872 --- /dev/null +++ b/src/proxy.rs @@ -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 `://:@: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 { + let uri = proxy.parse::().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 { + 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), +} \ No newline at end of file