Skip to content

Commit ae39e65

Browse files
authored
correctly handle insecure request over http proxies (#656)
1 parent 47aa022 commit ae39e65

File tree

1 file changed

+162
-2
lines changed
  • rama-http-backend/src/client

1 file changed

+162
-2
lines changed

rama-http-backend/src/client/svc.rs

Lines changed: 162 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use rama_http_types::{
1414
dep::{http::uri::PathAndQuery, http_body},
1515
header::{CONNECTION, HOST, KEEP_ALIVE, PROXY_CONNECTION, TRANSFER_ENCODING, UPGRADE},
1616
};
17-
use rama_net::http::RequestContext;
17+
use rama_net::{address::ProxyAddress, http::RequestContext};
1818
use std::fmt;
1919
use tokio::sync::Mutex;
2020

@@ -166,16 +166,28 @@ fn sanitize_client_req_header<S, B>(
166166
return Err(OpaqueError::from_display("missing host in CONNECT request").into());
167167
}
168168

169+
let uses_http_proxy = ctx
170+
.get::<ProxyAddress>()
171+
.and_then(|proxy| proxy.protocol.as_ref())
172+
.map(|protocol| protocol.is_http())
173+
.unwrap_or_default();
174+
169175
let request_ctx = ctx
170176
.get_or_try_insert_with_ctx::<RequestContext, _>(|ctx| (ctx, &req).try_into())
171177
.context("fetch request context")?;
172178

179+
let is_insecure_request_over_http_proxy = !request_ctx.protocol.is_secure() && uses_http_proxy;
180+
173181
// logic specific to http versions
174182
Ok(match req.version() {
175183
Version::HTTP_09 | Version::HTTP_10 | Version::HTTP_11 => {
176184
// remove authority and scheme for non-connect requests
177185
// cfr: <https://datatracker.ietf.org/doc/html/rfc2616#section-5.1.2>
178-
if req.method() != Method::CONNECT && req.uri().host().is_some() {
186+
// Unless we are sending an insecure request over a http(s) proxy
187+
if req.method() != Method::CONNECT
188+
&& !is_insecure_request_over_http_proxy
189+
&& req.uri().host().is_some()
190+
{
179191
tracing::trace!(
180192
"remove authority and scheme from non-connect direct http(~1) request"
181193
);
@@ -326,3 +338,151 @@ fn sanitize_client_req_header<S, B>(
326338
}
327339
})
328340
}
341+
342+
#[cfg(test)]
343+
mod tests {
344+
use super::*;
345+
use rama_http::{Scheme, Uri, dep::http::uri::Authority};
346+
use rama_net::{
347+
Protocol,
348+
address::{Domain, Host},
349+
};
350+
351+
#[test]
352+
fn should_sanitize_http1_except_connect() {
353+
for method in [
354+
Method::DELETE,
355+
Method::GET,
356+
Method::HEAD,
357+
Method::OPTIONS,
358+
Method::PATCH,
359+
Method::POST,
360+
Method::PUT,
361+
Method::TRACE,
362+
]
363+
.into_iter()
364+
{
365+
let uri = Uri::builder()
366+
.authority("example.com")
367+
.scheme(Scheme::HTTPS)
368+
.path_and_query("/test")
369+
.build()
370+
.unwrap();
371+
372+
let req = Request::builder().uri(uri).method(method).body(()).unwrap();
373+
let mut ctx = Context::default();
374+
let req = sanitize_client_req_header(&mut ctx, req).unwrap();
375+
376+
let (parts, _) = req.into_parts();
377+
let uri = parts.uri.into_parts();
378+
379+
assert_eq!(uri.scheme, None);
380+
assert_eq!(uri.authority, None);
381+
}
382+
}
383+
384+
#[test]
385+
fn should_not_sanitize_http1_connect() {
386+
let uri = Uri::builder()
387+
.authority("example.com")
388+
.scheme("https")
389+
.path_and_query("/test")
390+
.build()
391+
.unwrap();
392+
393+
let req = Request::builder()
394+
.method(Method::CONNECT)
395+
.uri(uri)
396+
.body(())
397+
.unwrap();
398+
let mut ctx = Context::default();
399+
let req = sanitize_client_req_header(&mut ctx, req).unwrap();
400+
401+
let (parts, _) = req.into_parts();
402+
let uri = parts.uri.into_parts();
403+
404+
assert_eq!(uri.scheme, Some(Scheme::HTTPS));
405+
assert_eq!(uri.authority, Some(Authority::from_static("example.com")));
406+
}
407+
408+
#[test]
409+
fn should_not_sanitize_insecure_http1_request_over_http_proxy() {
410+
let uri = Uri::builder()
411+
.authority("example.com")
412+
.scheme(Scheme::HTTP)
413+
.path_and_query("/test")
414+
.build()
415+
.unwrap();
416+
417+
let req = Request::builder().uri(uri).body(()).unwrap();
418+
419+
let mut ctx = Context::default();
420+
ctx.insert(ProxyAddress {
421+
authority: rama_net::address::Authority::new(Host::Name(Domain::example()), 80),
422+
credential: None,
423+
protocol: Some(Protocol::HTTP),
424+
});
425+
426+
let req = sanitize_client_req_header(&mut ctx, req).unwrap();
427+
428+
let (parts, _) = req.into_parts();
429+
let uri = parts.uri.into_parts();
430+
431+
assert_eq!(uri.scheme, Some(Scheme::HTTP));
432+
assert_eq!(uri.authority, Some(Authority::from_static("example.com")));
433+
}
434+
435+
#[test]
436+
fn should_sanitize_secure_http1_request_over_http_proxy() {
437+
let uri = Uri::builder()
438+
.authority("example.com")
439+
.scheme(Scheme::HTTPS)
440+
.path_and_query("/test")
441+
.build()
442+
.unwrap();
443+
444+
let req = Request::builder().uri(uri).body(()).unwrap();
445+
446+
let mut ctx = Context::default();
447+
ctx.insert(ProxyAddress {
448+
authority: rama_net::address::Authority::new(Host::Name(Domain::example()), 80),
449+
credential: None,
450+
protocol: Some(Protocol::HTTP),
451+
});
452+
453+
let req = sanitize_client_req_header(&mut ctx, req).unwrap();
454+
455+
let (parts, _) = req.into_parts();
456+
let uri = parts.uri.into_parts();
457+
458+
assert_eq!(uri.scheme, None);
459+
assert_eq!(uri.authority, None);
460+
}
461+
462+
#[test]
463+
fn should_sanitize_insecure_http1_request_over_socks_proxy() {
464+
let uri = Uri::builder()
465+
.authority("example.com")
466+
.scheme(Scheme::HTTP)
467+
.path_and_query("/test")
468+
.build()
469+
.unwrap();
470+
471+
let req = Request::builder().uri(uri).body(()).unwrap();
472+
473+
let mut ctx = Context::default();
474+
ctx.insert(ProxyAddress {
475+
authority: rama_net::address::Authority::new(Host::Name(Domain::example()), 80),
476+
credential: None,
477+
protocol: Some(Protocol::SOCKS5),
478+
});
479+
480+
let req = sanitize_client_req_header(&mut ctx, req).unwrap();
481+
482+
let (parts, _) = req.into_parts();
483+
let uri = parts.uri.into_parts();
484+
485+
assert_eq!(uri.scheme, None);
486+
assert_eq!(uri.authority, None);
487+
}
488+
}

0 commit comments

Comments
 (0)