blob: 98c2cbd555becddc96ad8ec7778f64b1cd2be8a6 [file] [log] [blame]
adminb0dd10f2006-08-25 17:25:49 +00001<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
2/**
3 * Code Igniter
4 *
5 * An open source application development framework for PHP 4.3.2 or newer
6 *
7 * @package CodeIgniter
8 * @author Rick Ellis
9 * @copyright Copyright (c) 2006, pMachine, Inc.
10 * @license http://www.codeignitor.com/user_guide/license.html
11 * @link http://www.codeigniter.com
12 * @since Version 1.0
13 * @filesource
14 */
15
16// ------------------------------------------------------------------------
17
18/**
19 * Input Class
20 *
21 * Pre-processes global input data for security
22 *
23 * @package CodeIgniter
24 * @subpackage Libraries
25 * @category Input
26 * @author Rick Ellis
27 * @link http://www.codeigniter.com/user_guide/libraries/input.html
28 */
29class CI_Input {
30 var $use_xss_clean = FALSE;
31 var $ip_address = FALSE;
32 var $user_agent = FALSE;
33 var $allow_get_array = FALSE;
34
35 /**
36 * Constructor
37 *
38 * Sets whether to globally enable the XSS processing
39 * and whether to allow the $_GET array
40 *
41 * @access public
42 */
43 function CI_Input()
44 {
admin04ea44e2006-10-03 19:17:59 +000045 log_message('debug', "Input Class Initialized");
46
admin33de9a12006-09-28 06:50:16 +000047 $CFG =& _load_class('Config');
adminb0dd10f2006-08-25 17:25:49 +000048 $this->use_xss_clean = ($CFG->item('global_xss_filtering') === TRUE) ? TRUE : FALSE;
admin04ea44e2006-10-03 19:17:59 +000049 $this->allow_get_array = ($CFG->item('enable_query_strings') === TRUE) ? TRUE : FALSE;
adminb0dd10f2006-08-25 17:25:49 +000050 $this->_sanitize_globals();
51 }
adminb0dd10f2006-08-25 17:25:49 +000052
53 // --------------------------------------------------------------------
54
55 /**
56 * Sanitize Globals
57 *
58 * This function does the folowing:
59 *
60 * Unsets $_GET data (if query strings are not enabled)
61 *
62 * Unsets all globals if register_globals is enabled
63 *
64 * Standardizes newline characters to \n
65 *
66 * @access private
67 * @return void
68 */
69 function _sanitize_globals()
70 {
71 // Unset globals. This is effectively the same as register_globals = off
72 foreach (array($_GET, $_POST, $_COOKIE) as $global)
73 {
74 if ( ! is_array($global))
75 {
76 unset($$global);
77 }
78 else
79 {
80 foreach ($global as $key => $val)
81 {
82 unset($$key);
admin04ea44e2006-10-03 19:17:59 +000083 }
adminb0dd10f2006-08-25 17:25:49 +000084 }
85 }
86
admin04ea44e2006-10-03 19:17:59 +000087 // Is $_GET data allowed? If not we'll set the $_GET to an empty array
adminb0dd10f2006-08-25 17:25:49 +000088 if ($this->allow_get_array == FALSE)
89 {
90 $_GET = array();
91 }
92
93 // Clean $_POST Data
94 if (is_array($_POST) AND count($_POST) > 0)
95 {
96 foreach($_POST as $key => $val)
admin04ea44e2006-10-03 19:17:59 +000097 {
98 $_POST[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
99 }
adminb0dd10f2006-08-25 17:25:49 +0000100 }
101
102 // Clean $_COOKIE Data
103 if (is_array($_COOKIE) AND count($_COOKIE) > 0)
104 {
105 foreach($_COOKIE as $key => $val)
admin04ea44e2006-10-03 19:17:59 +0000106 {
adminb0dd10f2006-08-25 17:25:49 +0000107 $_COOKIE[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
admin04ea44e2006-10-03 19:17:59 +0000108 }
adminb0dd10f2006-08-25 17:25:49 +0000109 }
110
111 log_message('debug', "Global POST and COOKIE data sanitized");
112 }
adminb0dd10f2006-08-25 17:25:49 +0000113
114 // --------------------------------------------------------------------
115
116 /**
117 * Clean Intput Data
118 *
119 * This is a helper function. It escapes data and
120 * standardizes newline characters to \n
121 *
122 * @access private
123 * @param string
124 * @return string
125 */
126 function _clean_input_data($str)
127 {
128 if (is_array($str))
129 {
130 $new_array = array();
131 foreach ($str as $key => $val)
132 {
admin04ea44e2006-10-03 19:17:59 +0000133 $new_array[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
adminb0dd10f2006-08-25 17:25:49 +0000134 }
135 return $new_array;
136 }
137
138 if ($this->use_xss_clean === TRUE)
139 {
140 $str = $this->xss_clean($str);
141 }
142
admin04ea44e2006-10-03 19:17:59 +0000143 // Standardize newlines
adminb0dd10f2006-08-25 17:25:49 +0000144 return preg_replace("/\015\012|\015|\012/", "\n", $str);
145 }
adminb0dd10f2006-08-25 17:25:49 +0000146
147 // --------------------------------------------------------------------
148
149 /**
150 * Clean Keys
151 *
152 * This is a helper function. To prevent malicious users
153 * from trying to exploit keys we make sure that keys are
154 * only named with alpha-numeric text and a few other items.
155 *
156 * @access private
157 * @param string
158 * @return string
159 */
160 function _clean_input_keys($str)
admin04ea44e2006-10-03 19:17:59 +0000161 {
adminb0dd10f2006-08-25 17:25:49 +0000162 if ( ! preg_match("/^[a-z0-9:_\/-]+$/i", $str))
163 {
164 exit('Disallowed Key Characters: '.$str);
165 }
166
167 if ( ! get_magic_quotes_gpc())
168 {
169 return addslashes($str);
170 }
171
172 return $str;
173 }
adminb0dd10f2006-08-25 17:25:49 +0000174
175 // --------------------------------------------------------------------
176
177 /**
178 * Fetch an item from the POST array
179 *
180 * @access public
181 * @param string
182 * @return string
183 */
184 function post($index = '', $xss_clean = FALSE)
185 {
186 if ( ! isset($_POST[$index]))
187 {
188 return FALSE;
189 }
admin04ea44e2006-10-03 19:17:59 +0000190
191 if ($xss_clean === TRUE)
adminb0dd10f2006-08-25 17:25:49 +0000192 {
admin04ea44e2006-10-03 19:17:59 +0000193 if (is_array($_POST[$index]))
adminb0dd10f2006-08-25 17:25:49 +0000194 {
admin04ea44e2006-10-03 19:17:59 +0000195 foreach($_POST[$index] as $key => $val)
196 {
197 $_POST[$index][$key] = $this->xss_clean($val);
198 }
adminb0dd10f2006-08-25 17:25:49 +0000199 }
200 else
201 {
admin04ea44e2006-10-03 19:17:59 +0000202 return $this->xss_clean($_POST[$index]);
adminb0dd10f2006-08-25 17:25:49 +0000203 }
204 }
admin04ea44e2006-10-03 19:17:59 +0000205
206 return $_POST[$index];
adminb0dd10f2006-08-25 17:25:49 +0000207 }
adminb0dd10f2006-08-25 17:25:49 +0000208
209 // --------------------------------------------------------------------
210
211 /**
212 * Fetch an item from the COOKIE array
213 *
214 * @access public
215 * @param string
216 * @return string
217 */
218 function cookie($index = '', $xss_clean = FALSE)
219 {
220 if ( ! isset($_COOKIE[$index]))
221 {
222 return FALSE;
223 }
admin04ea44e2006-10-03 19:17:59 +0000224
225 if ($xss_clean === TRUE)
226 {
227 if (is_array($_COOKIE[$index]))
adminb0dd10f2006-08-25 17:25:49 +0000228 {
admin04ea44e2006-10-03 19:17:59 +0000229 $cookie = array();
230 foreach($_COOKIE[$index] as $key => $val)
admin2fcd16b2006-10-03 16:41:54 +0000231 {
admin04ea44e2006-10-03 19:17:59 +0000232 $cookie[$key] = $this->xss_clean($val);
admin2fcd16b2006-10-03 16:41:54 +0000233 }
admin04ea44e2006-10-03 19:17:59 +0000234
235 return $cookie;
adminb0dd10f2006-08-25 17:25:49 +0000236 }
237 else
238 {
admin04ea44e2006-10-03 19:17:59 +0000239 return $this->xss_clean($_COOKIE[$index]);
adminb0dd10f2006-08-25 17:25:49 +0000240 }
241 }
admin04ea44e2006-10-03 19:17:59 +0000242 else
243 {
244 return $_COOKIE[$index];
245 }
adminb0dd10f2006-08-25 17:25:49 +0000246 }
adminb0dd10f2006-08-25 17:25:49 +0000247
248 // --------------------------------------------------------------------
249
250 /**
251 * Fetch the IP Address
252 *
253 * @access public
254 * @return string
255 */
256 function ip_address()
257 {
258 if ($this->ip_address !== FALSE)
259 {
260 return $this->ip_address;
261 }
262
263 $cip = (isset($_SERVER['HTTP_CLIENT_IP']) AND $_SERVER['HTTP_CLIENT_IP'] != "") ? $_SERVER['HTTP_CLIENT_IP'] : FALSE;
264 $rip = (isset($_SERVER['REMOTE_ADDR']) AND $_SERVER['REMOTE_ADDR'] != "") ? $_SERVER['REMOTE_ADDR'] : FALSE;
265 $fip = (isset($_SERVER['HTTP_X_FORWARDED_FOR']) AND $_SERVER['HTTP_X_FORWARDED_FOR'] != "") ? $_SERVER['HTTP_X_FORWARDED_FOR'] : FALSE;
266
267 if ($cip && $rip) $this->ip_address = $cip;
268 elseif ($rip) $this->ip_address = $rip;
269 elseif ($cip) $this->ip_address = $cip;
270 elseif ($fip) $this->ip_address = $fip;
271
272 if (strstr($this->ip_address, ','))
273 {
274 $x = explode(',', $this->ip_address);
275 $this->ip_address = end($x);
276 }
277
278 if ( ! $this->valid_ip($this->ip_address))
279 {
280 $this->ip_address = '0.0.0.0';
281 }
282
283 unset($cip);
284 unset($rip);
285 unset($fip);
286
287 return $this->ip_address;
288 }
adminb0dd10f2006-08-25 17:25:49 +0000289
290 // --------------------------------------------------------------------
291
292 /**
293 * Validate IP Address
294 *
295 * @access public
296 * @param string
297 * @return string
298 */
299 function valid_ip($ip)
300 {
301 return ( ! preg_match( "/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/", $ip)) ? FALSE : TRUE;
302 }
adminb0dd10f2006-08-25 17:25:49 +0000303
304 // --------------------------------------------------------------------
305
306 /**
307 * User Agent
308 *
309 * @access public
310 * @return string
311 */
312 function user_agent()
313 {
314 if ($this->user_agent !== FALSE)
315 {
316 return $this->user_agent;
317 }
318
319 $this->user_agent = ( ! isset($_SERVER['HTTP_USER_AGENT'])) ? FALSE : $_SERVER['HTTP_USER_AGENT'];
320
321 return $this->user_agent;
322 }
adminb0dd10f2006-08-25 17:25:49 +0000323
324 // --------------------------------------------------------------------
325
326 /**
327 * XSS Clean
328 *
329 * Sanitizes data so that Cross Site Scripting Hacks can be
330 * prevented.Ê This function does a fair amount of work but
331 * it is extremely thorough, designed to prevent even the
332 * most obscure XSS attempts.Ê Nothing is ever 100% foolproof,
333 * of course, but I haven't been able to get anything passed
334 * the filter.
335 *
336 * Note: This function should only be used to deal with data
337 * upon submission.Ê It's not something that should
338 * be used for general runtime processing.
339 *
340 * This function was based in part on some code and ideas I
341 * got from Bitflux: http://blog.bitflux.ch/wiki/XSS_Prevention
342 *
343 * To help develop this script I used this great list of
344 * vulnerabilities along with a few other hacks I've
345 * harvested from examining vulnerabilities in other programs:
346 * http://ha.ckers.org/xss.html
347 *
348 * @access public
349 * @param string
350 * @return string
351 */
352 function xss_clean($str, $charset = 'ISO-8859-1')
353 {
354 /*
355 * Remove Null Characters
356 *
357 * This prevents sandwiching null characters
358 * between ascii characters, like Java\0script.
359 *
360 */
361 $str = preg_replace('/\0+/', '', $str);
362 $str = preg_replace('/(\\\\0)+/', '', $str);
363
364 /*
365 * Validate standard character entites
366 *
367 * Add a semicolon if missing. We do this to enable
368 * the conversion of entities to ASCII later.
369 *
370 */
371 $str = preg_replace('#(&\#*\w+)[\x00-\x20]+;#u',"\\1;",$str);
372
373 /*
374 * Validate UTF16 two byte encodeing (x00)
375 *
376 * Just as above, adds a semicolon if missing.
377 *
378 */
379 $str = preg_replace('#(&\#x*)([0-9A-F]+);*#iu',"\\1\\2;",$str);
380
381 /*
382 * URL Decode
383 *
384 * Just in case stuff like this is submitted:
385 *
386 * <a href="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">Google</a>
387 *
388 * Note: Normally urldecode() would be easier but it removes plus signs
389 *
390 */
391 $str = preg_replace("/%u0([a-z0-9]{3})/i", "&#x\\1;", $str);
admin04ea44e2006-10-03 19:17:59 +0000392 $str = preg_replace("/%([a-z0-9]{2})/i", "&#x\\1;", $str);
393
adminb0dd10f2006-08-25 17:25:49 +0000394 /*
395 * Convert character entities to ASCII
396 *
397 * This permits our tests below to work reliably.
398 * We only convert entities that are within tags since
399 * these are the ones that will pose security problems.
400 *
401 */
402
admin04ea44e2006-10-03 19:17:59 +0000403 if (preg_match_all("/<(.+?)>/si", $str, $matches))
404 {
adminb0dd10f2006-08-25 17:25:49 +0000405 for ($i = 0; $i < count($matches['0']); $i++)
406 {
407 $str = str_replace($matches['1'][$i],
408 $this->_html_entity_decode($matches['1'][$i], $charset),
409 $str);
410 }
411 }
412
413 /*
414 * Convert all tabs to spaces
415 *
416 * This prevents strings like this: ja vascript
417 * Note: we deal with spaces between characters later.
418 *
419 */
420 $str = preg_replace("#\t+#", " ", $str);
421
422 /*
423 * Makes PHP tags safe
424 *
425 * Note: XML tags are inadvertently replaced too:
426 *
427 * <?xml
428 *
429 * But it doesn't seem to pose a problem.
430 *
431 */
adminbc042dd2006-09-21 02:46:59 +0000432 $str = str_replace(array('<?php', '<?PHP', '<?', '?>'), array('&lt;?php', '&lt;?PHP', '&lt;?', '?&gt;'), $str);
adminb0dd10f2006-08-25 17:25:49 +0000433
434 /*
435 * Compact any exploded words
436 *
437 * This corrects words like: j a v a s c r i p t
438 * These words are compacted back to their correct state.
439 *
440 */
441 $words = array('javascript', 'vbscript', 'script', 'applet', 'alert', 'document', 'write', 'cookie', 'window');
442 foreach ($words as $word)
443 {
444 $temp = '';
445 for ($i = 0; $i < strlen($word); $i++)
446 {
447 $temp .= substr($word, $i, 1)."\s*";
448 }
449
450 $temp = substr($temp, 0, -3);
451 $str = preg_replace('#'.$temp.'#s', $word, $str);
452 $str = preg_replace('#'.ucfirst($temp).'#s', ucfirst($word), $str);
453 }
454
455 /*
456 * Remove disallowed Javascript in links or img tags
457 */
458 $str = preg_replace("#<a.+?href=.*?(alert\(|alert&\#40;|javascript\:|window\.|document\.|\.cookie|<script|<xss).*?\>.*?</a>#si", "", $str);
459 $str = preg_replace("#<img.+?src=.*?(alert\(|alert&\#40;|javascript\:|window\.|document\.|\.cookie|<script|<xss).*?\>#si", "", $str);
460 $str = preg_replace("#<(script|xss).*?\>#si", "", $str);
461
462 /*
463 * Remove JavaScript Event Handlers
464 *
465 * Note: This code is a little blunt. It removes
466 * the event handler and anything up to the closing >,
467 * but it's unlkely to be a problem.
468 *
469 */
470 $str = preg_replace('#(<[^>]+.*?)(onblur|onchange|onclick|onfocus|onload|onmouseover|onmouseup|onmousedown|onselect|onsubmit|onunload|onkeypress|onkeydown|onkeyup|onresize)[^>]*>#iU',"\\1>",$str);
471
472 /*
473 * Sanitize naughty HTML elements
474 *
475 * If a tag containing any of the words in the list
476 * below is found, the tag gets converted to entities.
477 *
478 * So this: <blink>
479 * Becomes: &lt;blink&gt;
480 *
481 */
482 $str = preg_replace('#<(/*\s*)(alert|applet|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|layer|link|meta|object|plaintext|style|script|textarea|title|xml|xss)([^>]*)>#is', "&lt;\\1\\2\\3&gt;", $str);
483
484 /*
485 * Sanitize naughty scripting elements
486 *
487 * Similar to above, only instead of looking for
488 * tags it looks for PHP and JavaScript commands
489 * that are disallowed. Rather than removing the
490 * code, it simply converts the parenthesis to entities
491 * rendering the code unexecutable.
492 *
493 * For example: eval('some code')
494 * Becomes: eval&#40;'some code'&#41;
495 *
496 */
497 $str = preg_replace('#(alert|cmd|passthru|eval|exec|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si', "\\1\\2&#40;\\3&#41;", $str);
498
499 /*
500 * Final clean up
501 *
502 * This adds a bit of extra precaution in case
503 * something got through the above filters
504 *
505 */
506 $bad = array(
507 'document.cookie' => '',
508 'document.write' => '',
509 'window.location' => '',
510 "javascript\s*:" => '',
511 "Redirect\s+302" => '',
512 '<!--' => '&lt;!--',
513 '-->' => '--&gt;'
514 );
515
516 foreach ($bad as $key => $val)
517 {
518 $str = preg_replace("#".$key."#i", $val, $str);
519 }
520
admin04ea44e2006-10-03 19:17:59 +0000521
adminb0dd10f2006-08-25 17:25:49 +0000522 log_message('debug', "XSS Filtering completed");
523 return $str;
524 }
adminb0dd10f2006-08-25 17:25:49 +0000525
admin04ea44e2006-10-03 19:17:59 +0000526 // --------------------------------------------------------------------
adminb0dd10f2006-08-25 17:25:49 +0000527
528 /**
529 * HTML Entities Decode
530 *
531 * This function is a replacement for html_entity_decode()
532 *
533 * In some versions of PHP the native function does not work
534 * when UTF-8 is the specified character set, so this gives us
535 * a work-around. More info here:
536 * http://bugs.php.net/bug.php?id=25670
537 *
538 * @access private
539 * @param string
540 * @param string
541 * @return string
542 */
543 /* -------------------------------------------------
admin04ea44e2006-10-03 19:17:59 +0000544 /* Replacement for html_entity_decode()
545 /* -------------------------------------------------*/
546
547 /*
548 NOTE: html_entity_decode() has a bug in some PHP versions when UTF-8 is the
549 character set, and the PHP developers said they were not back porting the
550 fix to versions other than PHP 5.x.
551 */
adminb0dd10f2006-08-25 17:25:49 +0000552 function _html_entity_decode($str, $charset='ISO-8859-1')
553 {
554 if (stristr($str, '&') === FALSE) return $str;
555
556 // The reason we are not using html_entity_decode() by itself is because
557 // while it is not technically correct to leave out the semicolon
558 // at the end of an entity most browsers will still interpret the entity
559 // correctly. html_entity_decode() does not convert entities without
560 // semicolons, so we are left with our own little solution here. Bummer.
561
562 if (function_exists('html_entity_decode') && (strtolower($charset) != 'utf-8' OR version_compare(phpversion(), '5.0.0', '>=')))
563 {
564 $str = html_entity_decode($str, ENT_COMPAT, $charset);
565 $str = preg_replace('~&#x([0-9a-f]{2,5})~ei', 'chr(hexdec("\\1"))', $str);
566 return preg_replace('~&#([0-9]{2,4})~e', 'chr(\\1)', $str);
567 }
568
569 // Numeric Entities
570 $str = preg_replace('~&#x([0-9a-f]{2,5});{0,1}~ei', 'chr(hexdec("\\1"))', $str);
571 $str = preg_replace('~&#([0-9]{2,4});{0,1}~e', 'chr(\\1)', $str);
572
573 // Literal Entities - Slightly slow so we do another check
574 if (stristr($str, '&') === FALSE)
575 {
576 $str = strtr($str, array_flip(get_html_translation_table(HTML_ENTITIES)));
577 }
578
579 return $str;
580 }
581
582}
583// END Input class
584?>