1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//! This module contains code dedicated to check validity of attribute's value.
//! 
//! # Examples
//! ```
//! # extern crate sanitize_html;
//! # extern crate regex;
//! # fn main() {
//! use sanitize_html::rules::pattern::Pattern;
//! use regex::Regex;
//!
//! let href = Pattern::regex(Regex::new("^(ftp:|http:|https:|mailto:)").unwrap()) |
//!     !Pattern::regex(Regex::new("^[^/]+[[:space:]]*:").unwrap());
//!
//! assert!(href.matches("filename.xls"));
//! assert!(href.matches("http://foo.com/"));
//! assert!(href.matches(" filename with spaces .zip "));
//! assert!(!href.matches(" javascript  : window.location = '//example.com/'")); // Attempt to make XSS
//! # }
//! ```
//!

use regex::Regex;

/// Value pattern
pub struct Pattern(pub Box<dyn Fn(&str) -> bool + Sync + Send>);

impl Pattern {
    /// Creates pattern which accepts any value.
    /// 
    /// # Example
    /// ```
    /// # extern crate sanitize_html;
    /// # extern crate regex;
    /// use sanitize_html::rules::pattern::Pattern;
    /// # fn main() {
    /// use regex::Regex;
    ///
    /// let pattern = Pattern::any();
    /// assert!(pattern.matches(""));
    /// assert!(pattern.matches("pants"));
    /// # }
    /// ```
    pub fn any() -> Self {
        Pattern(Box::new(move |_value| true))
    }

    /// Creates pattern which uses regular expression to check a value. Panics
    /// 
    /// # Example
    /// ```
    /// # extern crate sanitize_html;
    /// # extern crate regex;
    /// use sanitize_html::rules::pattern::Pattern;
    /// # fn main() {
    /// use regex::Regex;
    ///
    /// let pattern = Pattern::regex(Regex::new("ant").unwrap());
    /// assert!(!pattern.matches(""));
    /// assert!(pattern.matches("pants"));
    /// # }
    /// ```
    pub fn regex(re: Regex) -> Self {
        Pattern(Box::new(move |value| re.is_match(value)))
    }

    /// Checks if a value matches to a pattern.
    pub fn matches(&self, value: &str) -> bool {
        (self.0)(value)
    }
}

impl ::std::ops::Not for Pattern {
    type Output = Pattern;

    /// Negates pattern
    /// 
    /// # Example
    /// ```
    /// # extern crate sanitize_html;
    /// # extern crate regex;
    /// use sanitize_html::rules::pattern::Pattern;
    /// # fn main() {
    /// use regex::Regex;
    ///
    /// let pattern = !Pattern::any();
    /// assert!(!pattern.matches(""));
    /// assert!(!pattern.matches("pants"));
    /// # }
    /// ```
    fn not(self) -> Self::Output {
        let cb = self.0;
        Pattern(Box::new(move |value| !cb(value)))
    }
}

impl ::std::ops::BitAnd for Pattern {
    type Output = Pattern;

    /// Combines two patterns into a pattern which matches a string iff both patterns match that string.
    /// 
    /// # Example
    /// ```
    /// # extern crate sanitize_html;
    /// # extern crate regex;
    /// use sanitize_html::rules::pattern::Pattern;
    /// # fn main() {
    /// use regex::Regex;
    ///
    /// let pan = Pattern::regex(Regex::new("pan").unwrap());
    /// let ant = Pattern::regex(Regex::new("ant").unwrap());
    /// let pattern = pan & ant;
    /// 
    /// assert!(!pattern.matches("pan"));
    /// assert!(!pattern.matches("ant"));
    /// assert!(pattern.matches("pants"));
    /// # }
    /// ```
    fn bitand(self, rhs: Pattern) -> Self::Output {
        let cb1 = self.0;
        let cb2 = rhs.0;
        Pattern(Box::new(move |value| cb1(value) && cb2(value)))
    }
}

impl ::std::ops::BitOr for Pattern {
    type Output = Pattern;

    /// Combines two patterns into a pattern which matches a string if one of patterns matches that string.
    /// 
    /// # Example
    /// ```
    /// # extern crate sanitize_html;
    /// # extern crate regex;
    /// use sanitize_html::rules::pattern::Pattern;
    /// # fn main() {
    /// use regex::Regex;
    ///
    /// let pan = Pattern::regex(Regex::new("pan").unwrap());
    /// let pot = Pattern::regex(Regex::new("pot").unwrap());
    /// let pattern = pan | pot;
    /// 
    /// assert!(pattern.matches("pants"));
    /// assert!(pattern.matches("pot"));
    /// assert!(!pattern.matches("jar"));
    /// # }
    /// ```
    fn bitor(self, rhs: Pattern) -> Self::Output {
        let cb1 = self.0;
        let cb2 = rhs.0;
        Pattern(Box::new(move |value| cb1(value) || cb2(value)))
    }
}