From bbc24542afdd6ec97cefbb2d4800d8bcaf9334ea Mon Sep 17 00:00:00 2001 From: Ibrahim Rahhal Date: Sun, 12 Apr 2026 01:38:48 +0300 Subject: [PATCH] Supporting cookies --- Cargo.lock | 77 ++++++++++++++++++++++++++++++++++++- Cargo.toml | 2 + src/utils/api.rs | 98 +++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 167 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8920232..e62c9c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -293,6 +293,35 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -311,13 +340,14 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "corgea" -version = "1.8.4" +version = "1.8.5" dependencies = [ "chrono", "clap", "dirs", "git2", "globset", + "http", "http-body-util", "hyper", "hyper-util", @@ -478,6 +508,15 @@ dependencies = [ "syn", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "either" version = "1.15.0" @@ -1131,6 +1170,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -1421,6 +1466,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "publicsuffix" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" +dependencies = [ + "idna", + "psl-types", +] + [[package]] name = "quick-xml" version = "0.36.2" @@ -1502,6 +1563,8 @@ checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64", "bytes", + "cookie", + "cookie_store", "futures-channel", "futures-core", "futures-util", @@ -1890,10 +1953,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", "serde_core", "time-core", + "time-macros", ] [[package]] @@ -1902,6 +1967,16 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 24ae90d..1ebb9ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ clap = { version = "4.4.13", features = ["derive"] } dirs = "5.0.1" reqwest = { version = "0.12.23", default-features = false, features = [ "blocking", + "cookies", "json", "multipart", "native-tls", @@ -33,6 +34,7 @@ chrono = "0.4" tokio = { version = "1.0", features = ["full"] } hyper = { version = "1.0", features = ["full"] } hyper-util = { version = "0.1", features = ["full"] } +http = "1" http-body-util = "0.1" url = "2.5" open = "5.0" diff --git a/src/utils/api.rs b/src/utils/api.rs index e2b50db..e7623f2 100644 --- a/src/utils/api.rs +++ b/src/utils/api.rs @@ -19,20 +19,100 @@ fn get_source() -> String { std::env::var("CORGEA_SOURCE").unwrap_or_else(|_| "cli".to_string()) } -pub fn http_client() -> reqwest::blocking::Client { - let mut builder = - reqwest::blocking::Client::builder().timeout(std::time::Duration::from_secs(5 * 30)); +static COOKIE_JAR: std::sync::LazyLock> = + std::sync::LazyLock::new(|| std::sync::Arc::new(reqwest::cookie::Jar::default())); - if let Ok(https_proxy) = std::env::var("https_proxy") { - debug(&format!("https_proxy detected: {}", https_proxy)); +static SHARED_CLIENT: std::sync::LazyLock = + std::sync::LazyLock::new(|| { + let mut builder = reqwest::blocking::Client::builder() + .timeout(std::time::Duration::from_secs(5 * 30)) + .cookie_provider(COOKIE_JAR.clone()); - if std::env::var("CORGEA_ACCEPT_CERT").is_ok() { - debug(&format!("Skipping CA cert validation")); - builder = builder.danger_accept_invalid_certs(true); + if let Ok(https_proxy) = std::env::var("https_proxy") { + debug(&format!("https_proxy detected: {}", https_proxy)); + + if std::env::var("CORGEA_ACCEPT_CERT").is_ok() { + debug(&format!("Skipping CA cert validation")); + builder = builder.danger_accept_invalid_certs(true); + } } + + builder.build().expect("Failed to build http client") + }); + +pub struct HttpClient { + inner: reqwest::blocking::Client, +} + +pub struct DebugRequestBuilder { + client: reqwest::blocking::Client, + inner: reqwest::blocking::RequestBuilder, +} + +impl HttpClient { + pub fn get(&self, url: U) -> DebugRequestBuilder { + DebugRequestBuilder { client: self.inner.clone(), inner: self.inner.get(url) } } - builder.build().expect("Failed to build http client") + pub fn post(&self, url: U) -> DebugRequestBuilder { + DebugRequestBuilder { client: self.inner.clone(), inner: self.inner.post(url) } + } + + pub fn patch(&self, url: U) -> DebugRequestBuilder { + DebugRequestBuilder { client: self.inner.clone(), inner: self.inner.patch(url) } + } +} + +impl DebugRequestBuilder { + pub fn header(self, key: K, value: V) -> Self + where + reqwest::header::HeaderName: TryFrom, + >::Error: Into, + reqwest::header::HeaderValue: TryFrom, + >::Error: Into, + { + Self { inner: self.inner.header(key, value), client: self.client } + } + + pub fn headers(self, headers: reqwest::header::HeaderMap) -> Self { + Self { inner: self.inner.headers(headers), client: self.client } + } + + pub fn query(self, query: &T) -> Self { + Self { inner: self.inner.query(query), client: self.client } + } + + pub fn multipart(self, form: reqwest::blocking::multipart::Form) -> Self { + Self { inner: self.inner.multipart(form), client: self.client } + } + + pub fn body>(self, body: T) -> Self { + Self { inner: self.inner.body(body), client: self.client } + } + + pub fn send(self) -> reqwest::Result { + use reqwest::cookie::CookieStore; + + let request = self.inner.build()?; + + debug(&format!("→ {} {}", request.method(), request.url())); + debug(&format!(" Request headers: {:?}", request.headers())); + match COOKIE_JAR.cookies(request.url()) { + Some(cookies) => debug(&format!(" Cookie: {}", cookies.to_str().unwrap_or(""))), + None => debug(" Cookie: (none in jar for this URL)"), + } + + let response = self.client.execute(request)?; + + debug(&format!("← {} {}", response.status(), response.url())); + debug(&format!(" Response headers: {:?}", response.headers())); + + Ok(response) + } +} + +pub fn http_client() -> HttpClient { + HttpClient { inner: SHARED_CLIENT.clone() } } fn check_for_warnings(headers: &HeaderMap, status: StatusCode) {