@@ -385,7 +385,19 @@ impl Service<Uri> for HttpsCapableConnector {
385385 }
386386
387387 fn call ( & mut self , dst : Uri ) -> Self :: Future {
388- let is_https = dst. scheme_str ( ) == Some ( "https" ) ;
388+ let is_https = match dst. scheme_str ( ) {
389+ Some ( "https" ) => true ,
390+ Some ( "http" ) => false ,
391+ scheme => {
392+ let scheme = scheme. map ( str:: to_owned) ;
393+ return Box :: pin ( async move {
394+ Err ( Box :: new ( io:: Error :: new (
395+ io:: ErrorKind :: InvalidInput ,
396+ format ! ( "unsupported URI scheme: {scheme:?}" ) ,
397+ ) ) as BoxError )
398+ } ) ;
399+ }
400+ } ;
389401 let transport_fut = self . inner . call ( dst. clone ( ) ) ;
390402 let tls_config = Arc :: clone ( & self . tls_config ) ;
391403 let tls_handshake_timeout = self . tls_handshake_timeout ;
@@ -400,6 +412,7 @@ impl Service<Uri> for HttpsCapableConnector {
400412 let host = dst. host ( ) . ok_or_else ( || -> BoxError {
401413 Box :: new ( io:: Error :: new ( io:: ErrorKind :: InvalidInput , "URI has no host" ) )
402414 } ) ?;
415+ let host = strip_ipv6_brackets ( host) ;
403416 let server_name = ServerName :: try_from ( host)
404417 . map_err ( |error| -> BoxError { Box :: new ( error) } ) ?
405418 . to_owned ( ) ;
@@ -429,6 +442,14 @@ impl Service<Uri> for HttpsCapableConnector {
429442 }
430443}
431444
445+ /// Strips the surrounding brackets from a bracketed IPv6 host, as found in a URI authority.
446+ ///
447+ /// [`rustls::pki_types::ServerName`] accepts unbracketed IPv6 addresses but rejects the bracketed form that
448+ /// [`http::Uri::host`] returns (e.g. `[::1]`), so this normalizes the host before constructing the server name.
449+ fn strip_ipv6_brackets ( host : & str ) -> & str {
450+ host. strip_prefix ( '[' ) . and_then ( |h| h. strip_suffix ( ']' ) ) . unwrap_or ( host)
451+ }
452+
432453/// Awaits a TLS handshake future, bounding it by `timeout` unless `timeout` is zero.
433454///
434455/// A zero duration means the handshake deadline is disabled, matching the core Agent convention for this setting.
@@ -663,6 +684,62 @@ mod tests {
663684 assert_eq ! ( result. unwrap( ) , 42 ) ;
664685 }
665686
687+ #[ test]
688+ fn strip_ipv6_brackets_unwraps_bracketed_addresses ( ) {
689+ use super :: strip_ipv6_brackets;
690+
691+ assert_eq ! ( strip_ipv6_brackets( "[::1]" ) , "::1" ) ;
692+ assert_eq ! ( strip_ipv6_brackets( "[2001:db8::1]" ) , "2001:db8::1" ) ;
693+ }
694+
695+ #[ test]
696+ fn strip_ipv6_brackets_leaves_unbracketed_hosts_alone ( ) {
697+ use super :: strip_ipv6_brackets;
698+
699+ assert_eq ! ( strip_ipv6_brackets( "example.com" ) , "example.com" ) ;
700+ assert_eq ! ( strip_ipv6_brackets( "::1" ) , "::1" ) ;
701+ }
702+
703+ #[ cfg( unix) ]
704+ #[ tokio:: test]
705+ async fn call_rejects_unsupported_uri_scheme ( ) {
706+ use std:: sync:: Arc ;
707+
708+ use rustls:: { ClientConfig , RootCertStore } ;
709+ use tower:: Service as _;
710+
711+ use super :: { HttpsCapableConnector , InnerConnector } ;
712+ use crate :: net:: dns:: SystemResolver ;
713+
714+ let inner = InnerConnector {
715+ http : SystemResolver :: new ( ) . into_http_connector ( ) ,
716+ connect_timeout : Duration :: from_secs ( 1 ) ,
717+ error_telemetry : None ,
718+ unix_socket_path : None ,
719+ #[ cfg( target_os = "linux" ) ]
720+ vsock_addr : None ,
721+ } ;
722+
723+ let tls_config = Arc :: new (
724+ ClientConfig :: builder ( )
725+ . with_root_certificates ( RootCertStore :: empty ( ) )
726+ . with_no_client_auth ( ) ,
727+ ) ;
728+
729+ let mut connector = HttpsCapableConnector {
730+ inner,
731+ tls_config,
732+ tls_handshake_timeout : Duration :: from_secs ( 1 ) ,
733+ bytes_sent : None ,
734+ error_telemetry : None ,
735+ conn_age_limit : None ,
736+ } ;
737+
738+ let uri: http:: Uri = "ftp://example.com/" . parse ( ) . unwrap ( ) ;
739+ let error = connector. call ( uri) . await . err ( ) . expect ( "expected scheme to be rejected" ) ;
740+ assert ! ( error. to_string( ) . contains( "unsupported URI scheme" ) ) ;
741+ }
742+
666743 #[ test]
667744 fn auto_protocol_advertises_h2_and_http1_alpn ( ) {
668745 let alpn_protocols = configure_alpn_for_http_protocol ( HttpProtocol :: Auto ) ;
0 commit comments