Skip to content

Commit fefdef2

Browse files
committed
fix(local-http): HttpClient removes Host from URI only for HTTP/1.x
1 parent 38855a3 commit fefdef2

File tree

5 files changed

+74
-30
lines changed

5 files changed

+74
-30
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "shadowsocks-rust"
3-
version = "1.21.1"
3+
version = "1.21.2"
44
authors = ["Shadowsocks Contributors"]
55
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
66
repository = "https://github.com/shadowsocks/shadowsocks-rust"
@@ -248,7 +248,7 @@ jemallocator = { version = "0.5", optional = true }
248248
snmalloc-rs = { version = "0.3", optional = true }
249249
rpmalloc = { version = "0.2", optional = true }
250250

251-
shadowsocks-service = { version = "1.21.1", path = "./crates/shadowsocks-service" }
251+
shadowsocks-service = { version = "1.21.2", path = "./crates/shadowsocks-service" }
252252

253253
windows-service = { version = "0.7", optional = true }
254254

crates/shadowsocks-service/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "shadowsocks-service"
3-
version = "1.21.1"
3+
version = "1.21.2"
44
authors = ["Shadowsocks Contributors"]
55
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
66
repository = "https://github.com/shadowsocks/shadowsocks-rust"

crates/shadowsocks-service/src/local/http/http_client.rs

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! HTTP Client
22
33
use std::{
4+
borrow::Cow,
45
collections::VecDeque,
56
fmt::Debug,
67
future::Future,
@@ -11,7 +12,8 @@ use std::{
1112
time::{Duration, Instant},
1213
};
1314

14-
use http::Uri;
15+
use bson::doc;
16+
use http::{header::InvalidHeaderValue, HeaderValue, Method as HttpMethod, Uri, Version as HttpVersion};
1517
use hyper::{
1618
body::{self, Body},
1719
client::conn::{http1, http2},
@@ -47,6 +49,9 @@ pub enum HttpClientError {
4749
/// Errors from http
4850
#[error("{0}")]
4951
Http(#[from] http::Error),
52+
/// Errors from http header
53+
#[error("{0}")]
54+
InvalidHeaderValue(#[from] InvalidHeaderValue),
5055
}
5156

5257
#[derive(Clone, Debug)]
@@ -137,20 +142,32 @@ where
137142
pub async fn send_request(
138143
&self,
139144
context: Arc<ServiceContext>,
140-
req: Request<B>,
145+
mut req: Request<B>,
141146
balancer: Option<&PingBalancer>,
142147
) -> Result<Response<body::Incoming>, HttpClientError> {
143148
let host = match host_addr(req.uri()) {
144149
Some(h) => h,
145150
None => panic!("URI missing host: {}", req.uri()),
146151
};
147152

153+
// Set Host header if it was missing in the Request
154+
{
155+
let headers = req.headers_mut();
156+
if !headers.contains_key("Host") {
157+
let host_value = match host {
158+
Address::DomainNameAddress(ref domain, _) => HeaderValue::from_str(domain)?,
159+
Address::SocketAddress(ref saddr) => HeaderValue::from_str(saddr.ip().to_string().as_str())?,
160+
};
161+
headers.insert("Host", host_value);
162+
}
163+
}
164+
148165
// 1. Check if there is an available client
149166
//
150167
// FIXME: If the cached connection is closed unexpectedly, this request will fail immediately.
151168
if let Some(c) = self.get_cached_connection(&host).await {
152169
trace!("HTTP client for host: {} taken from cache", host);
153-
return self.send_request_conn(host, c, req).await
170+
return self.send_request_conn(host, c, req).await;
154171
}
155172

156173
// 2. If no. Make a new connection
@@ -159,13 +176,12 @@ where
159176
None => &Scheme::HTTP,
160177
};
161178

162-
let domain = req
163-
.uri()
164-
.host()
165-
.unwrap()
166-
.trim_start_matches('[')
167-
.trim_start_matches(']');
168-
let c = match HttpConnection::connect(context.clone(), scheme, host.clone(), domain, balancer).await {
179+
let domain = match host {
180+
Address::DomainNameAddress(ref domain, _) => Cow::Borrowed(domain.as_str()),
181+
Address::SocketAddress(ref saddr) => Cow::Owned(saddr.ip().to_string()),
182+
};
183+
184+
let c = match HttpConnection::connect(context.clone(), scheme, host.clone(), &domain, balancer).await {
169185
Ok(c) => c,
170186
Err(err) => {
171187
error!("failed to connect to host: {}, error: {}", host, err);
@@ -196,19 +212,8 @@ where
196212
&self,
197213
host: Address,
198214
mut c: HttpConnection<B>,
199-
mut req: Request<B>,
215+
req: Request<B>,
200216
) -> Result<Response<body::Incoming>, HttpClientError> {
201-
// Remove Scheme, Host part from URI
202-
if req.uri().scheme().is_some() || req.uri().authority().is_some() {
203-
let mut builder = Uri::builder();
204-
if let Some(path_and_query) = req.uri().path_and_query() {
205-
builder = builder.path_and_query(path_and_query.as_str());
206-
} else {
207-
builder = builder.path_and_query("/");
208-
}
209-
*(req.uri_mut()) = builder.build()?;
210-
}
211-
212217
trace!("HTTP making request to host: {}, request: {:?}", host, req);
213218
let response = c.send_request(req).await?;
214219
trace!("HTTP received response from host: {}, response: {:?}", host, response);
@@ -351,10 +356,45 @@ where
351356
}
352357

353358
#[inline]
354-
pub async fn send_request(&mut self, req: Request<B>) -> hyper::Result<Response<body::Incoming>> {
359+
pub async fn send_request(&mut self, mut req: Request<B>) -> Result<Response<body::Incoming>, HttpClientError> {
355360
match self {
356-
HttpConnection::Http1(r) => r.send_request(req).await,
357-
HttpConnection::Http2(r) => r.send_request(req).await,
361+
HttpConnection::Http1(r) => {
362+
if !matches!(
363+
req.version(),
364+
HttpVersion::HTTP_09 | HttpVersion::HTTP_10 | HttpVersion::HTTP_11
365+
) {
366+
trace!(
367+
"HTTP client changed Request.version to HTTP/1.1 from {:?}",
368+
req.version()
369+
);
370+
371+
*req.version_mut() = HttpVersion::HTTP_11;
372+
}
373+
374+
// Remove Scheme, Host part from URI
375+
if req.method() != HttpMethod::CONNECT
376+
&& (req.uri().scheme().is_some() || req.uri().authority().is_some())
377+
{
378+
let mut builder = Uri::builder();
379+
if let Some(path_and_query) = req.uri().path_and_query() {
380+
builder = builder.path_and_query(path_and_query.as_str());
381+
} else {
382+
builder = builder.path_and_query("/");
383+
}
384+
*(req.uri_mut()) = builder.build()?;
385+
}
386+
387+
r.send_request(req).await.map_err(Into::into)
388+
}
389+
HttpConnection::Http2(r) => {
390+
if !matches!(req.version(), HttpVersion::HTTP_2) {
391+
trace!("HTTP client changed Request.version to HTTP/2 from {:?}", req.version());
392+
393+
*req.version_mut() = HttpVersion::HTTP_2;
394+
}
395+
396+
r.send_request(req).await.map_err(Into::into)
397+
}
358398
}
359399
}
360400

crates/shadowsocks-service/src/local/http/http_service.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ impl HttpService {
162162
error!("failed to make request to host: {}, error: {}", host, err);
163163
return make_bad_request();
164164
}
165+
Err(HttpClientError::InvalidHeaderValue(err)) => {
166+
error!("failed to make request to host: {}, error: {}", host, err);
167+
return make_bad_request();
168+
}
165169
};
166170

167171
trace!("received {} <- {} {:?}", self.peer_addr, host, res);

0 commit comments

Comments
 (0)