Skip to content

Commit e72b082

Browse files
committed
fix(spammer): validate WebSocket targets
1 parent 97f8da0 commit e72b082

1 file changed

Lines changed: 43 additions & 11 deletions

File tree

‎crates/spammer/src/main.rs‎

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)