@@ -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 } ;
1818use std:: fmt;
1919use 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