1use regex::Regex;
4use std::sync::OnceLock;
5
6static URL_REGEX: OnceLock<Regex> = OnceLock::new();
7
8fn get_url_regex() -> &'static Regex {
9 URL_REGEX.get_or_init(|| {
10 Regex::new(r"^https?://[^\s/$.?#].[^\s]*$")
11 .expect("Failed to compile URL regex")
12 })
13}
14
15pub fn is_valid_url(url: &str) -> bool {
28 if url.is_empty() {
29 return false;
30 }
31
32 get_url_regex().is_match(url)
33}
34
35pub fn is_valid_https_url(url: &str) -> bool {
37 url.starts_with("https://") && is_valid_url(url)
38}
39
40pub fn is_url_from_domain(url: &str, domain: &str) -> bool {
42 if !is_valid_url(url) {
43 return false;
44 }
45
46 url.contains(&format!("://{}", domain)) || url.contains(&format!("://www.{}", domain))
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52
53 #[test]
54 fn test_valid_urls() {
55 assert!(is_valid_url("https://www.example.com"));
56 assert!(is_valid_url("http://example.com"));
57 assert!(is_valid_url("https://example.com/path/to/page"));
58 assert!(is_valid_url("https://example.com?query=1&other=2"));
59 }
60
61 #[test]
62 fn test_invalid_urls() {
63 assert!(!is_valid_url(""));
64 assert!(!is_valid_url("not a url"));
65 assert!(!is_valid_url("ftp://example.com"));
66 assert!(!is_valid_url("javascript:alert(1)"));
67 }
68
69 #[test]
70 fn test_https_only() {
71 assert!(is_valid_https_url("https://example.com"));
72 assert!(!is_valid_https_url("http://example.com"));
73 }
74
75 #[test]
76 fn test_url_domain() {
77 assert!(is_url_from_domain("https://example.com/path", "example.com"));
78 assert!(is_url_from_domain("https://www.example.com/path", "example.com"));
79 assert!(!is_url_from_domain("https://other.com/path", "example.com"));
80 }
81}
82