Skip to content

Commit 182230d

Browse files
committed
Add redlib-org#458 into the mix
Honestly not expecting it will work
1 parent c7bea2d commit 182230d

7 files changed

Lines changed: 210 additions & 7 deletions

File tree

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ time = { version = "0.3.31", features = ["local-offset"] }
3636
url = "2.5.0"
3737
rust-embed = { version = "8.1.0", features = ["include-exclude"] }
3838
libflate = "2.0.0"
39+
3940
brotli = { version = "7.0.0", features = ["std"] }
4041
toml = "0.8.8"
4142
serde_yaml = "0.9.29"
@@ -51,7 +52,7 @@ arc-swap = "1.7.1"
5152
serde_json_path = "0.7.1"
5253
async-recursion = "1.1.1"
5354
pulldown-cmark = { version = "0.12.0", features = ["simd", "html"], default-features = false }
54-
hyper-tls = { version = "0.5.0" }
55+
hyper-tls = "0.5.0"
5556
openssl = { version = "0.10", features = ["vendored"] }
5657
tegen = "0.1.4"
5758
serde_urlencoded = "0.7.1"
@@ -60,6 +61,7 @@ htmlescape = "0.3.1"
6061
bincode = "1.3.3"
6162
base2048 = "2.0.2"
6263
revision = "0.10.0"
64+
tokio-socks = "0.5.2"
6365
fake_user_agent = "0.2.2"
6466
rustls = "0.21.12"
6567

src/client.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use arc_swap::ArcSwap;
22
use cached::proc_macro::cached;
33
use futures_lite::future::block_on;
44
use futures_lite::{future::Boxed, FutureExt};
5-
use hyper::client::HttpConnector;
65
use hyper::header::HeaderValue;
76
use hyper::{body, body::Buf, header, Body, Client, Method, Request, Response, Uri};
87
//use hyper_rustls::{ConfigBuilderExt, HttpsConnector};
@@ -31,7 +30,9 @@ const REDDIT_SHORT_URL_BASE_HOST: &str = "redd.it";
3130
const ALTERNATIVE_REDDIT_URL_BASE: &str = "https://www.reddit.com";
3231
const ALTERNATIVE_REDDIT_URL_BASE_HOST: &str = "www.reddit.com";
3332

34-
pub static HTTPS_CONNECTOR: LazyLock<HttpsConnector<HttpConnector>> = LazyLock::new(HttpsConnector::new);
33+
pub static HTTPS_CONNECTOR: LazyLock<HttpsConnector<ProxyConnector>> = LazyLock::new(|| {
34+
HttpsConnector::new_with_connector(ProxyConnector::new())
35+
});
3536
/*
3637
pub static HTTPS_CONNECTOR: LazyLock<HttpsConnector<HttpConnector>> = LazyLock::new(|| {
3738
hyper_rustls::HttpsConnectorBuilder::new()
@@ -53,6 +54,7 @@ pub static HTTPS_CONNECTOR: LazyLock<HttpsConnector<HttpConnector>> = LazyLock::
5354
rustls::cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
5455
])
5556
// .with_safe_default_cipher_suites()
57+
5658
.with_safe_default_kx_groups()
5759
.with_safe_default_protocol_versions()
5860
.unwrap()
@@ -65,7 +67,8 @@ pub static HTTPS_CONNECTOR: LazyLock<HttpsConnector<HttpConnector>> = LazyLock::
6567
});
6668
*/
6769

68-
pub static CLIENT: LazyLock<Client<HttpsConnector<HttpConnector>>> = LazyLock::new(|| Client::builder().build::<_, Body>(HTTPS_CONNECTOR.clone()));
70+
//pub static CLIENT: LazyLock<Client<HttpsConnector<HttpConnector>>> = LazyLock::new(|| Client::builder().build::<_, Body>(HTTPS_CONNECTOR.clone()));
71+
pub static CLIENT: LazyLock<Client<HttpsConnector<ProxyConnector>>> = LazyLock::new(|| Client::builder().build::<_, Body>(HTTPS_CONNECTOR.clone()));
6972

7073
pub static OAUTH_CLIENT: LazyLock<ArcSwap<Oauth>> = LazyLock::new(|| {
7174
let client = block_on(Oauth::new());
@@ -553,6 +556,7 @@ pub async fn rate_limit_check() -> Result<(), String> {
553556

554557
#[cfg(test)]
555558
use {crate::config::get_setting, sealed_test::prelude::*};
559+
use crate::proxy::ProxyConnector;
556560

557561
#[tokio::test(flavor = "multi_thread")]
558562
async fn test_rate_limit_check() {

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ pub mod settings;
1111
pub mod subreddit;
1212
pub mod user;
1313
pub mod utils;
14+
mod proxy;

src/oauth.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ impl Oauth {
7575
pub(crate) async fn new() -> Self {
7676
// Try MobileSpoofAuth first, then fall back to GenericWebAuth
7777
let mut failure_count = 0;
78-
let mut _backend_not_used = OauthBackendImpl::MobileSpoof(MobileSpoofAuth::new());
78+
//let mut backend = OauthBackendImpl::MobileSpoof(MobileSpoofAuth::new());
7979
let mut backend = OauthBackendImpl::GenericWeb(GenericWebAuth::new()); // Use generic web auth
8080

8181
loop {

src/proxy.rs

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
use base64::engine::general_purpose;
2+
use base64::Engine;
3+
use hyper::client::HttpConnector;
4+
use hyper::service::Service;
5+
use hyper::Uri;
6+
use log::debug;
7+
use std::env;
8+
use std::error::Error;
9+
use std::fmt;
10+
use std::future::Future;
11+
use std::pin::Pin;
12+
use std::task::{Context, Poll};
13+
use tokio::net::TcpStream;
14+
use tokio_socks::tcp::Socks5Stream;
15+
16+
type BoxError = Box<dyn Error + Send + Sync>;
17+
type BoxFuture<T> = Pin<Box<dyn Future<Output = Result<T, BoxError>> + Send>>;
18+
type Credentials = (String, String);
19+
20+
#[derive(Clone)]
21+
pub enum ProxyConnector {
22+
NoProxy(HttpConnector),
23+
Socks(String),
24+
Http(String),
25+
}
26+
27+
#[derive(Debug)]
28+
pub struct ProxyError(String);
29+
30+
impl fmt::Display for ProxyError {
31+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32+
write!(f, "Proxy error: {}", self.0)
33+
}
34+
}
35+
36+
impl Error for ProxyError {}
37+
38+
impl Service<Uri> for ProxyConnector {
39+
type Response = TcpStream;
40+
type Error = BoxError;
41+
type Future = BoxFuture<Self::Response>;
42+
43+
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
44+
match self {
45+
ProxyConnector::NoProxy(connector) => connector.poll_ready(cx).map_err(Into::into),
46+
_ => Poll::Ready(Ok(())),
47+
}
48+
}
49+
50+
fn call(&mut self, uri: Uri) -> Self::Future {
51+
let this = self.clone();
52+
Box::pin(async move {
53+
match this {
54+
ProxyConnector::NoProxy(mut connector) => {
55+
connector.call(uri).await.map_err(Into::into)
56+
}
57+
ProxyConnector::Socks(proxy_addr) => handle_socks_connection(&proxy_addr, &uri).await,
58+
ProxyConnector::Http(proxy_addr) => handle_http_connection(&proxy_addr, &uri).await,
59+
}
60+
})
61+
}
62+
}
63+
64+
impl ProxyConnector {
65+
pub fn new() -> Self {
66+
if let Ok(socks_proxy) = env::var("SOCKS_PROXY") {
67+
debug!("Using SOCKS proxy: {}", socks_proxy);
68+
return ProxyConnector::Socks(socks_proxy);
69+
}
70+
71+
if let Ok(http_proxy) = env::var("HTTP_PROXY").or_else(|_| env::var("HTTPS_PROXY")) {
72+
debug!("Using HTTP proxy: {}", http_proxy);
73+
return ProxyConnector::Http(http_proxy);
74+
}
75+
76+
let mut connector = HttpConnector::new();
77+
connector.enforce_http(false);
78+
ProxyConnector::NoProxy(connector)
79+
}
80+
}
81+
82+
async fn handle_socks_connection(proxy_addr: &str, uri: &Uri) -> Result<TcpStream, BoxError> {
83+
let (host, port, credentials) = parse_proxy_addr(proxy_addr)?;
84+
let target_addr = get_target_addr(uri)?;
85+
86+
let stream = match credentials {
87+
Some((username, password)) => {
88+
Socks5Stream::connect_with_password((host.as_str(), port), target_addr, &username, &password).await
89+
}
90+
None => Socks5Stream::connect((host.as_str(), port), target_addr).await,
91+
}?;
92+
93+
Ok(stream.into_inner())
94+
}
95+
96+
async fn handle_http_connection(proxy_addr: &str, uri: &Uri) -> Result<TcpStream, BoxError> {
97+
let (host, port, credentials) = parse_proxy_addr(proxy_addr)?;
98+
let proxy_stream = TcpStream::connect((host.as_str(), port)).await?;
99+
let target_addr = get_target_addr(uri)?;
100+
101+
let connect_req = build_connect_request(&target_addr, credentials)?;
102+
write_and_verify_connection(&proxy_stream, &connect_req).await?;
103+
104+
Ok(proxy_stream)
105+
}
106+
107+
fn build_connect_request(target_addr: &str, credentials: Option<Credentials>) -> Result<String, BoxError> {
108+
let mut req = format!(
109+
"CONNECT {target_addr} HTTP/1.1\r\n\
110+
Host: {target_addr}\r\n\
111+
Connection: keep-alive\r\n"
112+
);
113+
114+
if let Some((username, password)) = credentials {
115+
let auth = general_purpose::STANDARD.encode(format!("{}:{}", username, password));
116+
req.push_str(&format!("Proxy-Authorization: Basic {}\r\n", auth));
117+
}
118+
119+
req.push_str("\r\n");
120+
Ok(req)
121+
}
122+
123+
async fn write_and_verify_connection(proxy_stream: &TcpStream, connect_req: &str) -> Result<(), BoxError> {
124+
proxy_stream.writable().await?;
125+
proxy_stream.try_write(connect_req.as_bytes())?;
126+
127+
let mut response = [0u8; 1024];
128+
proxy_stream.readable().await?;
129+
let n = proxy_stream.try_read(&mut response)?;
130+
131+
let response = String::from_utf8_lossy(&response[..n]);
132+
if !response.starts_with("HTTP/1.1 200") {
133+
return Err(Box::new(ProxyError(format!("Proxy CONNECT failed: {}", response))));
134+
}
135+
136+
Ok(())
137+
}
138+
139+
fn parse_proxy_addr(addr: &str) -> Result<(String, u16, Option<Credentials>), BoxError> {
140+
let uri: Uri = addr.parse()?;
141+
let host = uri.host().ok_or("Missing proxy host")?.to_string();
142+
let port = uri.port_u16().unwrap_or_else(|| {
143+
if uri.scheme_str() == Some("https") { 443 } else { 80 }
144+
});
145+
146+
let credentials = extract_credentials(uri.authority())?;
147+
Ok((host, port, credentials))
148+
}
149+
150+
fn extract_credentials(authority: Option<&hyper::http::uri::Authority>) -> Result<Option<Credentials>, BoxError> {
151+
let Some(authority) = authority else {
152+
return Ok(None);
153+
};
154+
155+
let Some(credentials) = authority.as_str().split('@').next() else {
156+
return Ok(None);
157+
};
158+
159+
if credentials == authority.as_str() {
160+
return Ok(None);
161+
}
162+
163+
let creds: Vec<&str> = credentials.split(':').collect();
164+
if creds.len() == 2 {
165+
Ok(Some((creds[0].to_string(), creds[1].to_string())))
166+
} else {
167+
Ok(None)
168+
}
169+
}
170+
171+
fn get_target_addr(uri: &Uri) -> Result<String, BoxError> {
172+
let host = uri.host().ok_or("Missing target host")?;
173+
let port = uri.port_u16().unwrap_or_else(|| {
174+
if uri.scheme_str() == Some("https") { 443 } else { 80 }
175+
});
176+
Ok(format!("{}:{}", host, port))
177+
}

static/hls.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)