use std::collections::HashMap;
use std::net::SocketAddr;
use std::time::Duration;
#[cfg(feature = "request")]
pub use reqwest::{
Url,
StatusCode,
Error,
tls::Certificate,
header::{ HeaderMap, HeaderName, HeaderValue },
};
#[cfg(any(feature = "request", feature = "response", feature = "method"))]
use serde::{ Serialize, Deserialize };
#[cfg(any(feature = "request", feature = "response"))]
pub use http::{
Request, Response,
request::Parts,
Uri
};
#[cfg(any(feature = "request", feature = "response"))]
pub use hyper::{ Body, body::to_bytes, body::Bytes };
pub use http::Method as HttpMethod;
#[cfg(feature = "request")]
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum Protocol {
SOCKS5,
HTTP,
HTTPS,
ALL
}
#[cfg(feature = "request")]
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum RedirectPolicy {
None,
Limit(usize),
Default
}
#[cfg(feature = "method")]
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum Method {
GET,
POST,
PATCH,
PUT,
DELETE,
TRACE,
HEAD,
OPTIONS,
CONNECT,
ANY
}
#[cfg(feature = "method")]
impl Method {
pub fn to_http_method(&self) -> reqwest::Method {
match self {
Method::GET => reqwest::Method::GET,
Method::POST => reqwest::Method::POST,
Method::PATCH => reqwest::Method::PATCH,
Method::PUT => reqwest::Method::PUT,
Method::DELETE => reqwest::Method::DELETE,
Method::TRACE => reqwest::Method::TRACE,
Method::HEAD => reqwest::Method::HEAD,
Method::OPTIONS => reqwest::Method::OPTIONS,
Method::CONNECT => reqwest::Method::CONNECT,
Method::ANY => reqwest::Method::GET
}
}
pub fn from_http_method(method: reqwest::Method) -> Self {
match method {
reqwest::Method::GET => Method::GET,
reqwest::Method::POST => Method::POST,
reqwest::Method::PATCH => Method::PATCH,
reqwest::Method::PUT => Method::PUT,
reqwest::Method::DELETE => Method::DELETE,
reqwest::Method::TRACE => Method::TRACE,
reqwest::Method::HEAD => Method::HEAD,
reqwest::Method::OPTIONS => Method::OPTIONS,
reqwest::Method::CONNECT => Method::CONNECT,
_ => Method::ANY
}
}
pub fn from_str(method: &str) -> Self {
match method {
"GET" => Method::GET,
"POST" => Method::POST,
"PATCH" => Method::PATCH,
"PUT" => Method::PUT,
"DELETE" => Method::DELETE,
"TRACE" => Method::TRACE,
"HEAD" => Method::HEAD,
"OPTIONS" => Method::OPTIONS,
"CONNECT" => Method::CONNECT,
_ => Method::ANY
}
}
}
#[cfg(feature = "request")]
pub enum BodyType {
Json,
Form
}
#[cfg(feature = "request")]
#[derive(Debug)]
pub struct ProductOSRequestError {
pub error: ProductOSRequestErrorState,
pub generated_error: Option<reqwest::Error>
}
#[cfg(feature = "sync")]
#[derive(Debug)]
pub struct ProductOSRequestSyncError {
pub error: ProductOSRequestErrorState,
pub generated_error: Option<ureq::Error>
}
#[cfg(feature = "request")]
#[derive(Debug)]
pub enum ProductOSRequestErrorState {
Error(String),
None
}
#[cfg(feature = "request")]
impl std::error::Error for ProductOSRequestError {}
#[cfg(feature = "request")]
impl std::fmt::Display for ProductOSRequestError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match &self.error {
ProductOSRequestErrorState::Error(m) => write!(f, "{}", m),
ProductOSRequestErrorState::None => write!(f, "No error")
}
}
}
#[cfg(feature = "request")]
pub struct ProductOSRequest {
url: Url,
method: Method,
headers: HashMap<HeaderName, HeaderValue>,
bearer_auth: Option<String>,
query: HashMap<String, String>,
body: Option<reqwest::Body>,
content_type: Option<BodyType>,
}
#[cfg(feature = "request")]
impl ProductOSRequest {
fn new(method: Method, url: String) -> Self {
Self {
url: Url::parse(url.as_str()).unwrap(),
method,
headers: HashMap::new(),
bearer_auth: None,
query: HashMap::new(),
body: None,
content_type: None,
}
}
pub fn add_header(&mut self, name: String, value: String, is_sensitive: bool) {
let header_name = reqwest::header::HeaderName::try_from(name).unwrap();
let mut header_value = reqwest::header::HeaderValue::try_from(value).unwrap();
header_value.set_sensitive(is_sensitive);
self.headers.insert(header_name, header_value);
}
pub fn add_headers(&mut self, headers: HashMap<String, String>, are_sensitive: bool) {
for (name, value) in headers {
self.add_header(name, value, are_sensitive);
}
}
pub fn bearer_auth(&mut self, token: String) {
self.bearer_auth = Some(token);
}
pub fn add_param(&mut self, key: String, value: String) {
self.query.insert(key, value);
}
pub fn add_params(&mut self, params: HashMap<String, String>) {
for (name, value) in params {
self.add_param(name, value);
}
}
pub fn set_query(&mut self, query: HashMap<String, String>) {
self.query = query;
}
pub fn set_form<T: serde::ser::Serialize + ?Sized>(&mut self, data: &T) {
self.body = Some(reqwest::Body::from(serde_urlencoded::to_string(data).unwrap()));
self.content_type = Some(BodyType::Form);
}
pub fn set_json<T: serde::ser::Serialize + ?Sized>(&mut self, data: &T) {
self.body = Some(reqwest::Body::from(serde_json::to_string(data).unwrap()));
self.content_type = Some(BodyType::Json);
}
pub fn set_body<T: serde::ser::Serialize + ?Sized>(&mut self, body: BodyType, data: &T) {
match body {
BodyType::Form => self.set_form(data),
BodyType::Json => self.set_json(data)
};
}
fn build(self, requester: &ProductOSRequester) -> reqwest::Request {
let method = self.method.to_http_method();
let mut request = requester.client.request(method, self.url.to_string());
for (name, value) in self.headers {
request = request.header(name, value);
}
match self.bearer_auth.clone() {
Some(a) => request = request.bearer_auth(a),
None => ()
}
let mut vector = Vec::new();
for pair in self.query {
vector.push(pair);
}
request = request.query(vector.into_boxed_slice().as_ref());
match self.body {
Some(_) => {
match self.content_type {
Some(BodyType::Json) => { request = request.header("content-type", "application/json"); },
Some(BodyType::Form) => { request = request.header("content-type", "application/www-form-url-encoded"); },
_ => ()
};
request = request.body(self.body.unwrap());
},
None => ()
}
request.build().unwrap()
}
#[cfg(feature = "sync")]
fn build_sync(self, requester: &mut ProductOSRequester) -> ureq::Request {
let method = self.method.to_http_method();
let mut request = match &requester.client_sync {
None => {
requester.build_sync();
requester.client_sync.as_mut().unwrap().request(method.as_str(), self.url.as_str())
},
Some(cs) => cs.request(method.as_str(), self.url.as_str())
};
for (name, value) in self.headers {
request = request.set(name.as_str(), value.to_str().unwrap());
}
match self.bearer_auth.clone() {
Some(a) => {
let mut bearer = "Bearer ".to_string();
bearer.push_str(a.as_str());
request = request.set("authorization", bearer.as_str());
},
None => ()
}
for (key, value) in self.query {
request = request.query(key.as_str(), value.as_str());
}
request
}
}
#[cfg(feature = "response")]
#[derive(Debug)]
pub struct ProductOSResponse {
response_async: Option<reqwest::Response>,
#[cfg(feature = "sync")]
response_sync: Option<ureq::Response>
}
#[cfg(feature = "response")]
impl ProductOSResponse {
pub fn new_async(r: reqwest::Response) -> Self{
Self {
response_async: Some(r),
#[cfg(feature = "sync")]
response_sync: None
}
}
#[cfg(feature = "sync")]
pub fn new_sync(r: ureq::Response) -> Self{
Self {
response_async: None,
#[cfg(feature = "sync")]
response_sync: Some(r)
}
}
pub fn status(&self) -> StatusCode {
#[cfg(feature = "sync")]
{
if self.response_sync.is_some() { StatusCode::NOT_IMPLEMENTED }
else { self.response_async.as_ref().unwrap().status() }
}
#[cfg(not(feature = "sync"))]
self.response_async.as_ref().unwrap().status()
}
pub fn status_code(&self) -> u16 {
self.status().as_u16()
}
pub fn get_headers(&self) -> HashMap<String, String> {
let mut map = HashMap::new();
#[cfg(feature = "sync")]
{
if self.response_sync.is_some() {
let header_names = self.response_sync.as_ref().unwrap().headers_names();
for name in header_names {
map.insert(name.clone(), self.response_sync.as_ref().unwrap().header(name.as_str()).unwrap().to_string());
}
}
else {
let headers = self.response_async.as_ref().unwrap().headers();
for (name, value) in headers {
map.insert(name.to_string(), value.to_str().unwrap().to_string());
}
}
}
#[cfg(not(feature = "sync"))]
{
let headers = self.response_async.as_ref().unwrap().headers();
for (name, value) in headers {
map.insert(name.to_string(), value.to_str().unwrap().to_string());
}
}
map
}
pub fn response_url(&self) -> String {
#[cfg(feature = "sync")]
{
if self.response_sync.is_some() { self.response_sync.as_ref().unwrap().get_url().to_string() }
else { self.response_async.as_ref().unwrap().url().to_string() }
}
#[cfg(not(feature = "sync"))]
self.response_async.as_ref().unwrap().url().to_string()
}
pub async fn text(self) -> reqwest::Result<String> {
self.response_async.unwrap().text().await
}
pub async fn json(self) -> reqwest::Result<serde_json::Value> {
self.response_async.unwrap().json().await
}
pub async fn bytes(self) -> reqwest::Result<bytes::Bytes> {
self.response_async.unwrap().bytes().await
}
pub async fn stream(self) -> reqwest::Result<Option<bytes::Bytes>> {
self.response_async.unwrap().chunk().await
}
pub fn as_error(self) -> Option<reqwest::Error> {
self.response_async.unwrap().error_for_status().err()
}
pub fn cookie_strings(&self) -> Vec<String> {
let mut cookies = vec!();
match &self.response_async {
Some(res) => {
for cookie in res.cookies() {
let name = cookie.name();
let value = cookie.value();
let domain = match cookie.domain() {
Some(d) => {
let mut dom = String::from(d);
dom.push_str("; ");
dom
}
None => String::new()
};
let secure = match cookie.secure() {
true => "Secure; ",
false => ""
};
let mut cookie_string = String::from(name);
cookie_string.push_str("=");
cookie_string.push_str(value);
cookie_string.push_str("; ");
cookie_string.push_str(domain.as_str());
cookie_string.push_str(secure);
cookies.push(cookie_string);
}
}
None => {}
}
cookies
}
}
#[cfg(feature = "request")]
#[derive(Clone)]
pub struct ProductOSRequester {
headers: reqwest::header::HeaderMap,
secure: bool,
timeout: Duration,
connect_timeout: Duration,
certificates: Vec<Vec<u8>>,
client: reqwest::Client,
#[cfg(feature = "sync")]
client_sync: Option<ureq::Agent>,
trust_all_certificates: bool,
trust_any_certificate_for_hostname: bool,
proxy: Option<reqwest::Proxy>,
#[cfg(feature = "sync")]
proxy_sync: Option<ureq::Proxy>,
redirect_policy: RedirectPolicy
}
#[cfg(feature = "request")]
impl ProductOSRequester {
pub fn new() -> Self {
Self {
headers: reqwest::header::HeaderMap::new(),
secure: true,
timeout: Duration::from_millis(1000),
connect_timeout: Duration::from_millis(1000),
certificates: vec!(),
client: reqwest::Client::new(),
#[cfg(feature = "sync")]
client_sync: None,
trust_all_certificates: false,
trust_any_certificate_for_hostname: false,
proxy: None,
#[cfg(feature = "sync")]
proxy_sync: None,
redirect_policy: RedirectPolicy::Default
}
}
pub fn add_header(&mut self, name: String, value: String, is_sensitive: bool) {
let header_name = reqwest::header::HeaderName::try_from(name).unwrap();
let mut header_value = reqwest::header::HeaderValue::try_from(value).unwrap();
header_value.set_sensitive(is_sensitive);
self.headers.append(header_name, header_value);
self.build();
}
pub fn set_headers(&mut self, headers: HashMap<String, String>) {
let mut header_map = reqwest::header::HeaderMap::new();
for (name, value) in headers {
header_map.insert(reqwest::header::HeaderName::try_from(name).unwrap(),
reqwest::header::HeaderValue::try_from(value).unwrap());
}
self.headers = header_map;
self.build();
}
pub fn force_secure(&mut self, sec: bool) {
self.secure = sec;
}
pub fn trust_all_certificates(&mut self, trust: bool) {
self.trust_all_certificates = trust;
}
pub fn trust_any_certificate_for_hostname(&mut self, trust: bool) {
self.trust_any_certificate_for_hostname = trust;
self.build();
}
pub fn set_timeout(&mut self, time: u64) {
self.timeout = Duration::from_millis(time);
self.build();
}
pub fn set_connect_timeout(&mut self, time: u64) {
self.connect_timeout = Duration::from_millis(time);
self.build();
}
pub fn add_trusted_certificate_pem(&mut self, certificate: Vec<u8>) {
self.certificates.push(certificate);
self.build();
}
pub fn get_trusted_certificates(&self) -> &Vec<Vec<u8>> {
&self.certificates
}
pub fn set_redirect_policy(&mut self, policy: RedirectPolicy) {
self.redirect_policy = policy;
self.build();
}
pub fn set_proxy(&mut self, proxy: Option<(Protocol, SocketAddr)>) {
match proxy {
None => self.proxy = None,
Some((protocol, address)) => {
match protocol {
Protocol::SOCKS5 => {
let mut address_string = String::from("socks5://");
address_string.push_str(address.to_string().as_str());
match reqwest::Proxy::http(address_string.to_owned()) {
Ok(proxy) => {
#[cfg(feature = "sync")]
match ureq::Proxy::new(address_string.to_owned()) {
Ok(ureq_proxy) => {
tracing::trace!("Sync proxy set successfully: {:?}", address_string);
self.proxy_sync = Some(ureq_proxy)
},
Err(e) => {
tracing::error!("Failed to setup sync proxy: {:?}", e);
self.proxy_sync = None
}
}
tracing::trace!("Async proxy set successfully: {:?}", address_string);
self.proxy = Some(proxy);
},
Err(e) => {
tracing::error!("Failed to setup proxy: {:?}", e);
self.proxy = None
}
}
}
Protocol::HTTP => {
let mut address_string = String::from("http://");
address_string.push_str(address.to_string().as_str());
match reqwest::Proxy::http(address_string.to_owned()) {
Ok(proxy) => {
#[cfg(feature = "sync")]
match ureq::Proxy::new(address_string.to_owned()) {
Ok(ureq_proxy) => {
tracing::info!("Sync proxy set successfully: {:?}", address_string);
self.proxy_sync = Some(ureq_proxy)
},
Err(e) => {
tracing::error!("Failed to setup sync proxy: {:?}", e);
self.proxy_sync = None
}
}
tracing::info!("Async proxy set successfully: {:?}", address_string);
self.proxy = Some(proxy)
},
Err(e) => {
tracing::error!("Failed to setup proxy: {:?}", e);
self.proxy = None
}
}
}
Protocol::HTTPS => {
let mut address_string = String::from("https://");
address_string.push_str(address.to_string().as_str());
match reqwest::Proxy::https(address_string.to_owned()) {
Ok(proxy) => {
#[cfg(feature = "sync")]
match ureq::Proxy::new(address_string.to_owned()) {
Ok(ureq_proxy) => {
tracing::info!("Sync proxy set successfully: {:?}", address_string);
self.proxy_sync = Some(ureq_proxy)
},
Err(e) => {
tracing::error!("Failed to setup sync proxy: {:?}", e);
self.proxy_sync = None
}
}
tracing::info!("Async proxy set successfully: {:?}", address_string);
self.proxy = Some(proxy)
},
Err(e) => {
tracing::error!("Failed to setup proxy: {:?}", e);
self.proxy = None
}
}
},
Protocol::ALL => {
let mut address_string = String::from("http://");
address_string.push_str(address.to_string().as_str());
match reqwest::Proxy::all(address_string.to_owned()) {
Ok(proxy) => {
#[cfg(feature = "sync")]
match ureq::Proxy::new(address_string.to_owned()) {
Ok(ureq_proxy) => {
tracing::info!("Sync proxy set successfully: {:?}", address_string);
self.proxy_sync = Some(ureq_proxy)
},
Err(e) => {
tracing::error!("Failed to setup sync proxy: {:?}", e);
self.proxy_sync = None
}
}
tracing::info!("Async proxy set successfully: {:?}", address_string);
self.proxy = Some(proxy)
},
Err(e) => {
tracing::error!("Failed to setup proxy: {:?}", e);
self.proxy = None
}
}
}
}
}
}
self.build();
}
fn build(&mut self) {
let mut builder = reqwest::ClientBuilder::new()
.default_headers(self.headers.clone())
.https_only(self.secure)
.timeout(self.timeout)
.connect_timeout(self.connect_timeout)
.danger_accept_invalid_certs(self.trust_all_certificates)
.cookie_store(true)
.gzip(true)
.brotli(true);
for cert in &self.certificates {
let certificate = reqwest::Certificate::from_der(cert.as_slice()).unwrap();
builder = builder.add_root_certificate(certificate);
}
match &self.proxy {
None => {}
Some(proxy) => builder = builder.proxy(proxy.to_owned())
}
let redirect_policy = match self.redirect_policy {
RedirectPolicy::None => reqwest::redirect::Policy::none(),
RedirectPolicy::Limit(hops) => reqwest::redirect::Policy::limited(hops),
RedirectPolicy::Default => reqwest::redirect::Policy::default()
};
builder = builder.redirect(redirect_policy);
tracing::trace!("Updated async client with configuration: {:?}", builder);
self.client = builder.build().unwrap();
}
#[cfg(feature = "sync")]
fn build_sync(&mut self) {
let mut builder = ureq::AgentBuilder::new();
builder = builder.timeout(self.timeout)
.timeout_connect(self.connect_timeout);
let mut root_store = rustls::RootCertStore::empty();
for cert in &self.certificates {
let certificate = rustls::Certificate(cert.to_vec());
match root_store.add(&certificate) {
Ok(_) => {}
Err(e) => panic!("Error generating certificates: {:?}", e)
}
}
let tls_config = rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_store)
.with_no_client_auth();
builder = builder.tls_config(std::sync::Arc::new(tls_config));
match &self.proxy_sync {
None => {}
Some(proxy) => builder = builder.proxy(proxy.to_owned())
}
tracing::trace!("Updated blocking client with configuration: {:?}", builder);
self.client_sync = Some(builder.build());
}
pub fn new_request(&self, method: Method, url: String) -> ProductOSRequest {
ProductOSRequest::new(method, url)
}
pub async fn request(&self, r: ProductOSRequest) -> Result<ProductOSResponse, Error> {
let request = r.build(&self);
match self.client.execute(request).await {
Ok(response) => Ok(ProductOSResponse::new_async(response)),
Err(error) => Err(error)
}
}
pub async fn request_simple(&self, method: Method, url: &str) -> Result<ProductOSResponse, Error> {
let r = ProductOSRequest::new(method, url.to_string());
let request = r.build(&self);
match self.client.execute(request).await {
Ok(response) => Ok(ProductOSResponse::new_async(response)),
Err(error) => Err(error)
}
}
pub async fn request_raw(&self, r: Request<hyper::Body>) -> Result<ProductOSResponse, Error> {
match reqwest::Request::try_from(r) {
Ok(request) => {
match self.client.execute(request).await {
Ok(response) => Ok(ProductOSResponse::new_async(response)),
Err(error) => Err(error)
}
}
Err(e) => {
tracing::error!("Request error: {:?}", e);
Err(e)
}
}
}
#[cfg(feature = "sync")]
pub fn request_sync(&mut self, mut r: ProductOSRequest, body_type: BodyType, body_content: Option<serde_json::Value>) -> Result<ProductOSResponse, ureq::Error> {
for (name, value) in self.headers.clone() {
r.add_header(name.unwrap().to_string(), value.to_str().unwrap().to_string(), false);
}
let request = r.build_sync(self);
if body_content.is_some() {
match body_type {
BodyType::Json => {
match request.send_json(body_content) {
Ok(response) => Ok(ProductOSResponse::new_sync(response)),
Err(error) => Err(error)
}
}
_ => Err(ureq::Error::Status(400, ureq::Response::new(400, "error", "").unwrap()))
}
}
else {
match request.call() {
Ok(response) => Ok(ProductOSResponse::new_sync(response)),
Err(error) => Err(error)
}
}
}
}