From eae366f130c7437f0de29803e7adbbdd09079b2a Mon Sep 17 00:00:00 2001 From: lifegpc Date: Mon, 20 Jun 2022 10:17:20 +0000 Subject: [PATCH] add Proxy settings --- src/opt/proxy.rs | 120 +++++++++++++++++++++++++++++++++++++++++++ src/opthelper.rs | 15 ++++++ src/settings_list.rs | 2 + src/webclient.rs | 14 +++-- 4 files changed, 147 insertions(+), 4 deletions(-) diff --git a/src/opt/proxy.rs b/src/opt/proxy.rs index 30d342f..73c84a2 100644 --- a/src/opt/proxy.rs +++ b/src/opt/proxy.rs @@ -2,20 +2,39 @@ use crate::ext::try_err::TryErr; use crate::gettext; use json::JsonValue; use std::convert::TryFrom; +use std::fmt::Display; +use std::ops::Deref; +use std::ops::DerefMut; use url::Url; +/// The error when parsing Proxy settings #[derive(Debug, derive_more::From)] pub enum ProxyError { + /// String error String(String), + /// Url parse error UrlParseError(url::ParseError), } +impl Display for ProxyError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::String(s) => f.write_str(s.as_str()), + Self::UrlParseError(s) => { + f.write_str(gettext("Failed to parse URL:"))?; + s.fmt(f) + } + } + } +} + impl From<&str> for ProxyError { fn from(v: &str) -> Self { Self::String(String::from(v)) } } +#[derive(Clone, Debug)] /// Proxy settings pub enum Proxy { /// Apply for all HTTP requests, [None] means do not proxy @@ -87,3 +106,104 @@ impl TryFrom<&JsonValue> for Proxy { ))) } } + +impl Proxy { + /// Match the url. + /// * `url` - Url + pub fn r#match(&self, url: &Url) -> Option> { + match self { + Self::All(d) => Some(d.clone()), + Self::HTTP(d) => { + let scheme = url.scheme(); + if scheme.is_empty() || scheme == "http" { + Some(d.clone()) + } else { + None + } + } + Self::HTTPS(d) => { + if url.scheme() == "https" { + Some(d.clone()) + } else { + None + } + } + } + } +} + +#[derive(Clone, Debug)] +/// A list of [Proxy] +pub struct ProxyChain { + /// Proxies + proxies: Vec, +} + +impl ProxyChain { + /// Match the url + /// * `url` - The url + pub fn r#match(&self, url: &Url) -> Option { + for i in self.proxies.iter() { + match i.r#match(url) { + Some(u) => { + return u; + } + None => {} + } + } + None + } +} + +impl Default for ProxyChain { + fn default() -> Self { + Self { + proxies: Vec::new(), + } + } +} + +impl Deref for ProxyChain { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.proxies + } +} + +impl DerefMut for ProxyChain { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.proxies + } +} + +impl TryFrom<&JsonValue> for ProxyChain { + type Error = ProxyError; + fn try_from(value: &JsonValue) -> Result { + let mut list = Vec::new(); + if value.is_array() { + for i in value.members() { + list.push(Proxy::try_from(i)?); + } + Ok(Self { proxies: list }) + } else { + Err(ProxyError::from(gettext("Failed to parse proxy list."))) + } + } +} + +impl TryFrom for ProxyChain { + type Error = ProxyError; + fn try_from(value: JsonValue) -> Result { + Self::try_from(&value) + } +} + +pub fn check_proxy(v: &JsonValue) -> bool { + match ProxyChain::try_from(v) { + Ok(_) => true, + Err(e) => { + println!("{}", e); + false + } + } +} diff --git a/src/opthelper.rs b/src/opthelper.rs index d3e677a..a2c9f60 100644 --- a/src/opthelper.rs +++ b/src/opthelper.rs @@ -5,11 +5,14 @@ use crate::ext::rw_lock::GetRwLock; use crate::ext::use_or_not::ToBool; use crate::ext::use_or_not::UseOrNot; use crate::list::NonTailList; +use crate::opt::proxy::ProxyChain; use crate::opt::size::parse_u32_size; use crate::opt::use_progress_bar::UseProgressBar; use crate::opts::CommandOpts; use crate::retry_interval::parse_retry_interval_from_json; use crate::settings::SettingStore; +use std::convert::TryFrom; +use std::ops::Deref; use std::sync::Arc; use std::sync::RwLock; use std::sync::RwLockReadGuard; @@ -25,6 +28,8 @@ pub struct OptHelper { default_retry_interval: NonTailList, _author_name_filters: RwLock>, _use_progress_bar: RwLock>, + /// Proxy settings + _proxy_chain: RwLock, } impl OptHelper { @@ -171,6 +176,10 @@ impl OptHelper { } else { None }); + if settings.have("proxy") { + self._proxy_chain + .replace_with2(ProxyChain::try_from(settings.get("proxy").unwrap()).unwrap()); + } self.opt.replace_with2(opt); self.settings.replace_with2(settings); } @@ -179,6 +188,11 @@ impl OptHelper { self.opt.get_ref().overwrite } + /// The proxy chain + pub fn proxy_chain(&self) -> ProxyChain { + self._proxy_chain.get_ref().deref().clone() + } + pub fn verbose(&self) -> bool { self.opt.get_ref().verbose } @@ -284,6 +298,7 @@ impl Default for OptHelper { default_retry_interval: l, _author_name_filters: RwLock::new(Vec::new()), _use_progress_bar: RwLock::new(None), + _proxy_chain: RwLock::new(ProxyChain::default()), } } } diff --git a/src/settings_list.rs b/src/settings_list.rs index fe924bd..3ce0536 100644 --- a/src/settings_list.rs +++ b/src/settings_list.rs @@ -5,6 +5,7 @@ use crate::gettext; use crate::retry_interval::check_retry_interval; use crate::settings::SettingDes; use crate::settings::JsonValueType; +use crate::opt::proxy::check_proxy; use crate::opt::size::parse_u32_size; use json::JsonValue; @@ -28,6 +29,7 @@ pub fn get_settings_list() -> Vec { SettingDes::new("download-part-retry", gettext("Max retry count of each part when downloading in multiple thread mode."), JsonValueType::Number, Some(check_i64)).unwrap(), SettingDes::new("max-threads", gettext("The maximun threads when downloading file."), JsonValueType::Number, Some(check_u64)).unwrap(), SettingDes::new("part-size", gettext("The size of the each part when downloading file."), JsonValueType::Number, Some(check_parse_size_u32)).unwrap(), + SettingDes::new("proxy", gettext("Proxy settings."), JsonValueType::Array, Some(check_proxy)).unwrap(), ] } diff --git a/src/webclient.rs b/src/webclient.rs index 10aedc0..636e79d 100644 --- a/src/webclient.rs +++ b/src/webclient.rs @@ -7,7 +7,7 @@ use crate::gettext; use crate::list::NonTailList; use crate::opthelper::get_helper; use json::JsonValue; -use reqwest::{Client, IntoUrl, RequestBuilder, Response}; +use reqwest::{Client, ClientBuilder, IntoUrl, RequestBuilder, Response}; use std::collections::HashMap; use std::convert::TryInto; use std::default::Default; @@ -94,9 +94,9 @@ impl WebClient { /// Create a new instance of client /// /// This function will not handle any basic options, please use [Self::default()] instead. - pub fn new() -> Self { + pub fn new(client: Client) -> Self { Self { - client: Client::new(), + client, headers: RwLock::new(HashMap::new()), cookies: RwLock::new(CookieJar::new()), verbose: Arc::new(AtomicBool::new(false)), @@ -374,8 +374,14 @@ impl WebClient { impl Default for WebClient { fn default() -> Self { - let c = Self::new(); let opt = get_helper(); + let mut c = ClientBuilder::new(); + let chain = opt.proxy_chain(); + if !chain.is_empty() { + c = c.proxy(reqwest::Proxy::custom(move |url| chain.r#match(url))); + } + let c = c.build().unwrap(); + let c = Self::new(c); c.set_verbose(opt.verbose()); match opt.retry() { Some(retry) => c.set_retry(retry),