sanitize_html/rules/pattern.rs
1//! This module contains code dedicated to check validity of attribute's value.
2//!
3//! # Examples
4//! ```
5//! use sanitize_html::rules::pattern::Pattern;
6//! use regex::Regex;
7//!
8//! let href = Pattern::regex(Regex::new("^(ftp:|http:|https:|mailto:)").unwrap()) |
9//! !Pattern::regex(Regex::new("^[^/]+[[:space:]]*:").unwrap());
10//!
11//! assert!(href.matches("filename.xls"));
12//! assert!(href.matches("http://foo.com/"));
13//! assert!(href.matches(" filename with spaces .zip "));
14//! assert!(!href.matches(" javascript : window.location = '//example.com/'")); // Attempt to make XSS
15//! ```
16
17use regex::Regex;
18
19/// Value pattern
20pub struct Pattern(pub Box<dyn Fn(&str) -> bool + Sync + Send>);
21
22impl Pattern {
23 /// Creates pattern which accepts any value.
24 ///
25 /// # Example
26 /// ```
27 /// use sanitize_html::rules::pattern::Pattern;
28 /// use regex::Regex;
29 ///
30 /// let pattern = Pattern::any();
31 /// assert!(pattern.matches(""));
32 /// assert!(pattern.matches("pants"));
33 /// ```
34 pub fn any() -> Self {
35 Pattern(Box::new(move |_value| true))
36 }
37
38 /// Creates pattern which uses regular expression to check a value. Panics
39 ///
40 /// # Example
41 /// ```
42 /// use sanitize_html::rules::pattern::Pattern;
43 /// use regex::Regex;
44 ///
45 /// let pattern = Pattern::regex(Regex::new("ant").unwrap());
46 /// assert!(!pattern.matches(""));
47 /// assert!(pattern.matches("pants"));
48 /// ```
49 pub fn regex(re: Regex) -> Self {
50 Pattern(Box::new(move |value| re.is_match(value)))
51 }
52
53 /// Checks if a value matches to a pattern.
54 pub fn matches(&self, value: &str) -> bool {
55 (self.0)(value)
56 }
57}
58
59impl ::std::ops::Not for Pattern {
60 type Output = Pattern;
61
62 /// Negates pattern
63 ///
64 /// # Example
65 /// ```
66 /// use sanitize_html::rules::pattern::Pattern;
67 /// use regex::Regex;
68 ///
69 /// let pattern = !Pattern::any();
70 /// assert!(!pattern.matches(""));
71 /// assert!(!pattern.matches("pants"));
72 /// ```
73 fn not(self) -> Self::Output {
74 let cb = self.0;
75 Pattern(Box::new(move |value| !cb(value)))
76 }
77}
78
79impl ::std::ops::BitAnd for Pattern {
80 type Output = Pattern;
81
82 /// Combines two patterns into a pattern which matches a string iff both patterns match that string.
83 ///
84 /// # Example
85 /// ```
86 /// use sanitize_html::rules::pattern::Pattern;
87 /// use regex::Regex;
88 ///
89 /// let pan = Pattern::regex(Regex::new("pan").unwrap());
90 /// let ant = Pattern::regex(Regex::new("ant").unwrap());
91 /// let pattern = pan & ant;
92 ///
93 /// assert!(!pattern.matches("pan"));
94 /// assert!(!pattern.matches("ant"));
95 /// assert!(pattern.matches("pants"));
96 /// ```
97 fn bitand(self, rhs: Pattern) -> Self::Output {
98 let cb1 = self.0;
99 let cb2 = rhs.0;
100 Pattern(Box::new(move |value| cb1(value) && cb2(value)))
101 }
102}
103
104impl ::std::ops::BitOr for Pattern {
105 type Output = Pattern;
106
107 /// Combines two patterns into a pattern which matches a string if one of patterns matches that string.
108 ///
109 /// # Example
110 /// ```
111 /// use sanitize_html::rules::pattern::Pattern;
112 /// use regex::Regex;
113 ///
114 /// let pan = Pattern::regex(Regex::new("pan").unwrap());
115 /// let pot = Pattern::regex(Regex::new("pot").unwrap());
116 /// let pattern = pan | pot;
117 ///
118 /// assert!(pattern.matches("pants"));
119 /// assert!(pattern.matches("pot"));
120 /// assert!(!pattern.matches("jar"));
121 /// ```
122 fn bitor(self, rhs: Pattern) -> Self::Output {
123 let cb1 = self.0;
124 let cb2 = rhs.0;
125 Pattern(Box::new(move |value| cb1(value) || cb2(value)))
126 }
127}