1use url::Url;
4
5const INVALID_URL_FOR_LOGS: &str = "[INVALID URL]";
6
7pub fn url_for_logs(value: &str) -> String {
13 let Ok(mut url) = Url::parse(value) else {
14 return INVALID_URL_FOR_LOGS.to_owned();
15 };
16
17 if url.set_password(None).is_err() || url.set_username("").is_err() {
18 return INVALID_URL_FOR_LOGS.to_owned();
19 }
20
21 url.set_query(None);
22 url.set_fragment(None);
23
24 url.to_string()
25}
26
27#[cfg(test)]
28mod tests {
29 use super::{url_for_logs, INVALID_URL_FOR_LOGS};
30
31 #[test]
32 fn removes_url_credentials() {
33 let url = "https://alice:s3cr3t@example.com:8443/api?network=main#tip";
34
35 let logged_url = url_for_logs(url);
36
37 assert_eq!(logged_url, "https://example.com:8443/api");
38 assert!(!logged_url.contains("alice"));
39 assert!(!logged_url.contains("s3cr3t"));
40 }
41
42 #[test]
43 fn removes_credentials_from_custom_scheme() {
44 let url = "ssl://alice:s3cr3t@example.com:50002";
45
46 assert_eq!(url_for_logs(url), "ssl://example.com:50002");
47 }
48
49 #[test]
50 fn preserves_ipv6_port_and_path() {
51 let url = "https://user:pass@[2001:db8::1]:3002/api";
52
53 assert_eq!(url_for_logs(url), "https://[2001:db8::1]:3002/api");
54 }
55
56 #[test]
57 fn preserves_url_without_credentials() {
58 let url = "https://example.com/api";
59
60 assert_eq!(url_for_logs(url), url);
61 }
62
63 #[test]
64 fn removes_query_parameters_and_fragments() {
65 let url = "https://example.com/api?token=query-secret#fragment-secret";
66
67 let logged_url = url_for_logs(url);
68
69 assert_eq!(logged_url, "https://example.com/api");
70 assert!(!logged_url.contains("query-secret"));
71 assert!(!logged_url.contains("fragment-secret"));
72 }
73
74 #[test]
75 fn malformed_url_is_not_logged() {
76 let url = "not a URL with user:secret@example.com";
77
78 let logged_url = url_for_logs(url);
79
80 assert_eq!(logged_url, INVALID_URL_FOR_LOGS);
81 assert!(!logged_url.contains("secret"));
82 }
83}