@@ -129,7 +129,7 @@ async fn main() -> Result<()> {
129129 let target_ws_urls = match cli. command {
130130 TargetCommand :: Ws { targets } => {
131131 let target_nodes = targets. unwrap_or_else ( || vec ! [ DEFAULT_WS_TARGET . to_string( ) ] ) ;
132- ws_urls_from_strings ( target_nodes)
132+ ws_urls_from_strings ( target_nodes) ?
133133 }
134134 TargetCommand :: Nodes {
135135 nodes_path,
@@ -230,20 +230,31 @@ fn write_atomic(contents: &str, path: &Path, label: &str) -> Result<()> {
230230}
231231
232232// Build the WebSocket URLs of the target nodes from the list of IP addresses and ports
233- fn ws_urls_from_strings ( target_nodes : Vec < String > ) -> Vec < ( String , Url ) > {
233+ fn ws_urls_from_strings ( target_nodes : Vec < String > ) -> Result < Vec < ( String , Url ) > > {
234234 target_nodes
235235 . into_iter ( )
236- . map ( |s| ( s. clone ( ) , ws_url_from_str ( s) ) )
236+ . map ( |target| {
237+ let url = ws_url_from_str ( & target) ?;
238+ Ok ( ( target, url) )
239+ } )
237240 . collect ( )
238241}
239242
240- fn ws_url_from_str ( ip_port : String ) -> Url {
241- let url_str = if !ip_port . starts_with ( "ws ://") {
242- format ! ( "ws://{ip_port}" )
243+ fn ws_url_from_str ( target : & str ) -> Result < Url > {
244+ let url_str = if target . contains ( " ://") {
245+ target . to_string ( )
243246 } else {
244- ip_port
247+ format ! ( "ws://{target}" )
245248 } ;
246- Url :: parse ( & url_str) . unwrap ( )
249+ let url =
250+ Url :: parse ( & url_str) . wrap_err_with ( || format ! ( "Invalid --targets entry {target:?}" ) ) ?;
251+ if !matches ! ( url. scheme( ) , "ws" | "wss" ) {
252+ eyre:: bail!(
253+ "Invalid --targets entry {target:?}: unsupported scheme {:?}; expected ws:// or wss://" ,
254+ url. scheme( )
255+ ) ;
256+ }
257+ Ok ( url)
247258}
248259
249260// Read the file with node metadata and obtain the WebSocket URLs of the target nodes
@@ -313,22 +324,43 @@ mod tests {
313324
314325 #[ test]
315326 fn ws_url_from_str_adds_ws_scheme_if_missing ( ) {
316- let url = ws_url_from_str ( "127.0.0.1:8546" . to_string ( ) ) ;
327+ let url = ws_url_from_str ( "127.0.0.1:8546" ) . unwrap ( ) ;
317328 assert_eq ! ( url. as_str( ) , "ws://127.0.0.1:8546/" ) ;
318329 }
319330
320331 #[ test]
321332 fn ws_url_from_str_parses_endpoint ( ) {
322- let url = ws_url_from_str ( "ws://127.0.0.1:8546" . to_string ( ) ) ;
333+ let url = ws_url_from_str ( "ws://127.0.0.1:8546" ) . unwrap ( ) ;
323334 assert_eq ! ( url. as_str( ) , "ws://127.0.0.1:8546/" ) ;
324335 }
325336
337+ #[ test]
338+ fn ws_url_from_str_preserves_secure_websocket_scheme ( ) {
339+ let url = ws_url_from_str ( "wss://rpc.example:8546" ) . unwrap ( ) ;
340+ assert_eq ! ( url. as_str( ) , "wss://rpc.example:8546/" ) ;
341+ }
342+
343+ #[ test]
344+ fn ws_url_from_str_rejects_invalid_target_without_panicking ( ) {
345+ let err = ws_url_from_str ( "\u{200d} .example:8546" ) . unwrap_err ( ) ;
346+ assert ! ( err. to_string( ) . contains( "Invalid --targets entry" ) ) ;
347+ }
348+
349+ #[ test]
350+ fn ws_url_from_str_rejects_unsupported_scheme ( ) {
351+ let err = ws_url_from_str ( "http://rpc.example:8545" ) . unwrap_err ( ) ;
352+ let message = err. to_string ( ) ;
353+ assert ! ( message. contains( "unsupported scheme \" http\" " ) ) ;
354+ assert ! ( message. contains( "expected ws:// or wss://" ) ) ;
355+ }
356+
326357 #[ test]
327358 fn ws_urls_from_strings_parses_endpoints ( ) {
328359 let urls = ws_urls_from_strings ( vec ! [
329360 "127.0.0.1:8546" . to_string( ) ,
330361 "ws://127.0.0.1:9546" . to_string( ) ,
331- ] ) ;
362+ ] )
363+ . unwrap ( ) ;
332364
333365 assert_eq ! ( urls. len( ) , 2 ) ;
334366 assert_eq ! ( urls[ 0 ] . 0 , "127.0.0.1:8546" ) ;
0 commit comments