Move busui to seperate repository
[bus.git] / class.inputfilter.php
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
<?php
 
/** @class: InputFilter (PHP4 & PHP5, with comments)
  * @project: PHP Input Filter
  * @date: 10-05-2005
  * @version: 1.2.2_php4/php5
  * @author: Daniel Morris
  * @contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, Chris Tobin and Andrew Eddie.
  * @copyright: Daniel Morris
  * @email: dan@rootcube.com
  * @license: GNU General Public License (GPL)
  */
class owa_InputFilter {
        var $tagsArray;                 // default = empty array
        var $attrArray;                 // default = empty array
 
        var $tagsMethod;                // default = 0
        var $attrMethod;                // default = 0
 
        var $xssAuto;           // default = 1
        var $tagBlacklist = array('applet', 'body', 'bgsound', 'base', 'basefont', 'embed', 'frame', 'frameset', 'head', 'html', 'id', 'iframe', 'ilayer', 'layer', 'link', 'meta', 'name', 'object', 'script', 'style', 'title', 'xml');
        var $attrBlacklist = array('action', 'background', 'codebase', 'dynsrc', 'lowsrc');  // also will strip ALL event handlers
                
        /** 
          * Constructor for inputFilter class. Only first parameter is required.
          * @access constructor
          * @param Array $tagsArray - list of user-defined tags
          * @param Array $attrArray - list of user-defined attributes
          * @param int $tagsMethod - 0= allow just user-defined, 1= allow all but user-defined
          * @param int $attrMethod - 0= allow just user-defined, 1= allow all but user-defined
          * @param int $xssAuto - 0= only auto clean essentials, 1= allow clean blacklisted tags/attr
          */
        function inputFilter($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1) {              
                // make sure user defined arrays are in lowercase
                for ($i = 0; $i < count($tagsArray); $i++) $tagsArray[$i] = strtolower($tagsArray[$i]);
                for ($i = 0; $i < count($attrArray); $i++) $attrArray[$i] = strtolower($attrArray[$i]);
                // assign to member vars
                $this->tagsArray = (array) $tagsArray;
                $this->attrArray = (array) $attrArray;
                $this->tagsMethod = $tagsMethod;
                $this->attrMethod = $attrMethod;
                $this->xssAuto = $xssAuto;
        }
        
        /** 
          * Method to be called by another php script. Processes for XSS and specified bad code.
          * @access public
          * @param Mixed $source - input string/array-of-string to be 'cleaned'
          * @return String $source - 'cleaned' version of input parameter
          */
        function process($source) {
                // clean all elements in this array
                if (is_array($source)) {
                        foreach($source as $key => $value)
                                // filter element for XSS and other 'bad' code etc.
                                if (is_string($value)) $source[$key] = $this->remove($this->decode($value));
                        return $source;
                // clean this string
                } else if (is_string($source)) {
                        // filter source for XSS and other 'bad' code etc.
                        return $this->remove($this->decode($source));
                // return parameter as given
                } else return $source;  
        }
 
        /** 
          * Internal method to iteratively remove all unwanted tags and attributes
          * @access protected
          * @param String $source - input string to be 'cleaned'
          * @return String $source - 'cleaned' version of input parameter
          */
        function remove($source) {
                $loopCounter=0;
                // provides nested-tag protection
                while($source != $this->filterTags($source)) {
                        $source = $this->filterTags($source);
                        $loopCounter++;
                }
                return $source;
        }       
        
        /** 
          * Internal method to strip a string of certain tags
          * @access protected
          * @param String $source - input string to be 'cleaned'
          * @return String $source - 'cleaned' version of input parameter
          */
        function filterTags($source) {
                // filter pass setup
                $preTag = NULL;
                $postTag = $source;
                // find initial tag's position
                $tagOpen_start = strpos($source, '<');
                // interate through string until no tags left
                while($tagOpen_start !== FALSE) {
                        // process tag interatively
                        $preTag .= substr($postTag, 0, $tagOpen_start);
                        $postTag = substr($postTag, $tagOpen_start);
                        $fromTagOpen = substr($postTag, 1);
                        // end of tag
                        $tagOpen_end = strpos($fromTagOpen, '>');
                        if ($tagOpen_end === false) break;
                        // next start of tag (for nested tag assessment)
                        $tagOpen_nested = strpos($fromTagOpen, '<');
                        if (($tagOpen_nested !== false) && ($tagOpen_nested < $tagOpen_end)) {
                                $preTag .= substr($postTag, 0, ($tagOpen_nested+1));
                                $postTag = substr($postTag, ($tagOpen_nested+1));
                                $tagOpen_start = strpos($postTag, '<');
                                continue;
                        } 
                        $tagOpen_nested = (strpos($fromTagOpen, '<') + $tagOpen_start + 1);
                        $currentTag = substr($fromTagOpen, 0, $tagOpen_end);
                        $tagLength = strlen($currentTag);
                        if (!$tagOpen_end) {
                                $preTag .= $postTag;
                                $tagOpen_start = strpos($postTag, '<');                 
                        }
                        // iterate through tag finding attribute pairs - setup
                        $tagLeft = $currentTag;
                        $attrSet = array();
                        $currentSpace = strpos($tagLeft, ' ');
                        // is end tag
                        if (substr($currentTag, 0, 1) == "/") {
                                $isCloseTag = TRUE;
                                list($tagName) = explode(' ', $currentTag);
                                $tagName = substr($tagName, 1);
                        // is start tag
                        } else {
                                $isCloseTag = FALSE;
                                list($tagName) = explode(' ', $currentTag);
                        }               
                        // excludes all "non-regular" tagnames OR no tagname OR remove if xssauto is on and tag is blacklisted
                        if ((!preg_match("/^[a-z][a-z0-9]*$/i",$tagName)) || (!$tagName) || ((in_array(strtolower($tagName), $this->tagBlacklist)) && ($this->xssAuto))) {                              
                                $postTag = substr($postTag, ($tagLength + 2));
                                $tagOpen_start = strpos($postTag, '<');
                                // don't append this tag
                                continue;
                        }
                        // this while is needed to support attribute values with spaces in!
                        while ($currentSpace !== FALSE) {
                                $fromSpace = substr($tagLeft, ($currentSpace+1));
                                $nextSpace = strpos($fromSpace, ' ');
                                $openQuotes = strpos($fromSpace, '"');
                                $closeQuotes = strpos(substr($fromSpace, ($openQuotes+1)), '"') + $openQuotes + 1;
                                // another equals exists
                                if (strpos($fromSpace, '=') !== FALSE) {
                                        // opening and closing quotes exists
                                        if (($openQuotes !== FALSE) && (strpos(substr($fromSpace, ($openQuotes+1)), '"') !== FALSE))
                                                $attr = substr($fromSpace, 0, ($closeQuotes+1));
                                        // one or neither exist
                                        else $attr = substr($fromSpace, 0, $nextSpace);
                                // no more equals exist
                                } else $attr = substr($fromSpace, 0, $nextSpace);
                                // last attr pair
                                if (!$attr) $attr = $fromSpace;
                                // add to attribute pairs array
                                $attrSet[] = $attr;
                                // next inc
                                $tagLeft = substr($fromSpace, strlen($attr));
                                $currentSpace = strpos($tagLeft, ' ');
                        }
                        // appears in array specified by user
                        $tagFound = in_array(strtolower($tagName), $this->tagsArray);                   
                        // remove this tag on condition
                        if ((!$tagFound && $this->tagsMethod) || ($tagFound && !$this->tagsMethod)) {
                                // reconstruct tag with allowed attributes
                                if (!$isCloseTag) {
                                        $attrSet = $this->filterAttr($attrSet);
                                        $preTag .= '<' . $tagName;
                                        for ($i = 0; $i < count($attrSet); $i++)
                                                $preTag .= ' ' . $attrSet[$i];
                                        // reformat single tags to XHTML
                                        if (strpos($fromTagOpen, "</" . $tagName)) $preTag .= '>';
                                        else $preTag .= ' />';
                                // just the tagname
                            } else $preTag .= '</' . $tagName . '>';
                        }
                        // find next tag's start
                        $postTag = substr($postTag, ($tagLength + 2));
                        $tagOpen_start = strpos($postTag, '<');                 
                }
                // append any code after end of tags
                $preTag .= $postTag;
                return $preTag;
        }
 
        /** 
          * Internal method to strip a tag of certain attributes
          * @access protected
          * @param Array $attrSet
          * @return Array $newSet
          */
        function filterAttr($attrSet) { 
                $newSet = array();
                // process attributes
                for ($i = 0; $i <count($attrSet); $i++) {
                        // skip blank spaces in tag
                        if (!$attrSet[$i]) continue;
                        // split into attr name and value
                        $attrSubSet = explode('=', trim($attrSet[$i]));
                        list($attrSubSet[0]) = explode(' ', $attrSubSet[0]);
                        // removes all "non-regular" attr names AND also attr blacklisted
                        if ((!eregi("^[a-z]*$",$attrSubSet[0])) || (($this->xssAuto) && ((in_array(strtolower($attrSubSet[0]), $this->attrBlacklist)) || (substr($attrSubSet[0], 0, 2) == 'on')))) 
                                continue;
                        // xss attr value filtering
                        if (