@@ -1031,6 +1031,95 @@ async fn test_server_validates_host_header_port_for_dns_rebinding_protection() {
10311031 assert_eq ! ( response. status( ) , http:: StatusCode :: FORBIDDEN ) ;
10321032}
10331033
1034+ /// Integration test: Verify the validator falls back to the URI authority when
1035+ /// the Host header is absent (HTTP/2 :authority pseudo-header scenario).
1036+ #[ tokio:: test]
1037+ #[ cfg( all( feature = "transport-streamable-http-server" , feature = "server" , ) ) ]
1038+ async fn test_server_falls_back_to_uri_authority_when_host_header_missing ( ) {
1039+ use std:: sync:: Arc ;
1040+
1041+ use bytes:: Bytes ;
1042+ use http:: { Method , Request , header:: CONTENT_TYPE } ;
1043+ use http_body_util:: Full ;
1044+ use rmcp:: {
1045+ handler:: server:: ServerHandler ,
1046+ model:: { ServerCapabilities , ServerInfo } ,
1047+ transport:: streamable_http_server:: {
1048+ StreamableHttpServerConfig , StreamableHttpService , session:: local:: LocalSessionManager ,
1049+ } ,
1050+ } ;
1051+ use serde_json:: json;
1052+
1053+ #[ derive( Clone ) ]
1054+ struct TestHandler ;
1055+
1056+ impl ServerHandler for TestHandler {
1057+ fn get_info ( & self ) -> ServerInfo {
1058+ ServerInfo :: new ( ServerCapabilities :: builder ( ) . build ( ) )
1059+ }
1060+ }
1061+
1062+ let service = StreamableHttpService :: new (
1063+ || Ok ( TestHandler ) ,
1064+ Arc :: new ( LocalSessionManager :: default ( ) ) ,
1065+ StreamableHttpServerConfig :: default ( ) ,
1066+ ) ;
1067+
1068+ let init_body = json ! ( {
1069+ "jsonrpc" : "2.0" ,
1070+ "id" : 1 ,
1071+ "method" : "initialize" ,
1072+ "params" : {
1073+ "protocolVersion" : "2025-03-26" ,
1074+ "capabilities" : { } ,
1075+ "clientInfo" : {
1076+ "name" : "test-client" ,
1077+ "version" : "1.0.0"
1078+ }
1079+ }
1080+ } ) ;
1081+
1082+ // Allowed authority via URI only — no Host header.
1083+ let allowed_request = Request :: builder ( )
1084+ . method ( Method :: POST )
1085+ . uri ( "http://localhost:8080/" )
1086+ . header ( "Accept" , "application/json, text/event-stream" )
1087+ . header ( CONTENT_TYPE , "application/json" )
1088+ . body ( Full :: new ( Bytes :: from ( init_body. to_string ( ) ) ) )
1089+ . unwrap ( ) ;
1090+ assert ! ( allowed_request. headers( ) . get( "Host" ) . is_none( ) ) ;
1091+
1092+ let response = service. handle ( allowed_request) . await ;
1093+ assert_eq ! ( response. status( ) , http:: StatusCode :: OK ) ;
1094+
1095+ // Disallowed authority via URI only — no Host header.
1096+ let bad_request = Request :: builder ( )
1097+ . method ( Method :: POST )
1098+ . uri ( "http://attacker.example/" )
1099+ . header ( "Accept" , "application/json, text/event-stream" )
1100+ . header ( CONTENT_TYPE , "application/json" )
1101+ . body ( Full :: new ( Bytes :: from ( init_body. to_string ( ) ) ) )
1102+ . unwrap ( ) ;
1103+ assert ! ( bad_request. headers( ) . get( "Host" ) . is_none( ) ) ;
1104+
1105+ let response = service. handle ( bad_request) . await ;
1106+ assert_eq ! ( response. status( ) , http:: StatusCode :: FORBIDDEN ) ;
1107+
1108+ // Neither Host header nor URI authority — still a 400.
1109+ let missing_request = Request :: builder ( )
1110+ . method ( Method :: POST )
1111+ . uri ( "/" )
1112+ . header ( "Accept" , "application/json, text/event-stream" )
1113+ . header ( CONTENT_TYPE , "application/json" )
1114+ . body ( Full :: new ( Bytes :: from ( init_body. to_string ( ) ) ) )
1115+ . unwrap ( ) ;
1116+ assert ! ( missing_request. headers( ) . get( "Host" ) . is_none( ) ) ;
1117+ assert ! ( missing_request. uri( ) . authority( ) . is_none( ) ) ;
1118+
1119+ let response = service. handle ( missing_request) . await ;
1120+ assert_eq ! ( response. status( ) , http:: StatusCode :: BAD_REQUEST ) ;
1121+ }
1122+
10341123#[ cfg( all( feature = "transport-streamable-http-server" , feature = "server" ) ) ]
10351124mod origin_validation {
10361125 use std:: sync:: Arc ;
0 commit comments