root/trunk/system/libraries/Input.php

Revision 2937, 12.0 kB (checked in by armen, 1 week ago)

Don't force $array as array, it could be any iterateable object.

  • Property svn:eol-style set to LF
  • Property copyright set to Copyright (c) 2007 Kohana Team
  • Property svn:keywords set to Id
Line 
1 <?php defined('SYSPATH') or die('No direct script access.');
2 /**
3  * Input library.
4  *
5  * $Id$
6  *
7  * @package    Core
8  * @author     Kohana Team
9  * @copyright  (c) 2007-2008 Kohana Team
10  * @license    http://kohanaphp.com/license.html
11  */
12 class Input_Core {
13
14     // Singleton instance
15     protected static $instance;
16
17     // Enable or disable automatic XSS cleaning
18     protected $use_xss_clean = FALSE;
19
20     // IP address of current user
21     public $ip_address = FALSE;
22
23     /**
24      * Retrieve a singleton instance of Input. This will always be the first
25      * created instance of this class.
26      *
27      * @return  object
28      */
29     public static function instance()
30     {
31         // Create an instance if none exists
32         empty(self::$instance) and new Input;
33
34         return self::$instance;
35     }
36
37     /**
38      * Sanitizes global GET, POST and COOKIE data. Also takes care of
39      * register_globals, if it has been enabled.
40      *
41      * @return  void
42      */
43     public function __construct()
44     {
45         // Use XSS clean?
46         $this->use_xss_clean = (bool) Config::item('core.global_xss_filtering');
47
48         if (self::$instance === NULL)
49         {
50             if (ini_get('register_globals'))
51             {
52                 // Prevent GLOBALS override attacks
53                 isset($_REQUEST['GLOBALS']) and exit('Global variable overload attack.');
54
55                 // Destroy the REQUEST global
56                 $_REQUEST = array();
57
58                 // These globals are standard and should not be removed
59                 $preserve = array('GLOBALS', '_REQUEST', '_GET', '_POST', '_FILES', '_COOKIE', '_SERVER', '_ENV', '_SESSION');
60
61                 // This loop has the same effect as disabling register_globals
62                 foreach ($GLOBALS as $key => $val)
63                 {
64                     if ( ! in_array($key, $preserve))
65                     {
66                         // NULL-ify the global variable
67                         global $$key;
68                         $$key = NULL;
69                         // Unset the global variable
70                         unset($GLOBALS[$key]);
71                         unset($$key);
72                     }
73                 }
74
75                 // Warn the developer about register globals
76                 Log::add('debug', 'Register globals is enabled. To save resources, disable register_globals in php.ini');
77             }
78
79             if (is_array($_GET) AND count($_GET) > 0)
80             {
81                 foreach ($_GET as $key => $val)
82                 {
83                     // Sanitize $_GET
84                     $_GET[$this->clean_input_keys($key)] = $this->clean_input_data($val);
85                 }
86             }
87             else
88             {
89                 $_GET = array();
90             }
91
92             if (is_array($_POST) AND count($_POST) > 0)
93             {
94                 foreach ($_POST as $key => $val)
95                 {
96                     // Sanitize $_POST
97                     $_POST[$this->clean_input_keys($key)] = $this->clean_input_data($val);
98                 }
99             }
100             else
101             {
102                 $_POST = array();
103             }
104
105             if (is_array($_COOKIE) AND count($_COOKIE) > 0)
106             {
107                 foreach ($_COOKIE as $key => $val)
108                 {
109                     // Sanitize $_COOKIE
110                     $_COOKIE[$this->clean_input_keys($key)] = $this->clean_input_data($val);
111                 }
112             }
113             else
114             {
115                 $_COOKIE = array();
116             }
117
118             // Create a singleton
119             self::$instance = $this;
120
121             Log::add('debug', 'Global GET, POST and COOKIE data sanitized');
122         }
123     }
124
125     /**
126      * Fetch an item from the $_GET array.
127      *
128      * @param   string   key to find
129      * @param   mixed    default value
130      * @param   boolean  XSS clean the value
131      * @return  mixed
132      */
133     public function get($key = array(), $default = NULL, $xss_clean = FALSE)
134     {
135         return $this->search_array($_GET, $key, $default, $xss_clean);
136     }
137
138     /**
139      * Fetch an item from the $_POST array.
140      *
141      * @param   string   key to find
142      * @param   mixed    default value
143      * @param   boolean  XSS clean the value
144      * @return  mixed
145      */
146     public function post($key = array(), $default = NULL, $xss_clean = FALSE)
147     {
148         return $this->search_array($_POST, $key, $default, $xss_clean);
149     }
150
151     /**
152      * Fetch an item from the $_COOKIE array.
153      *
154      * @param   string   key to find
155      * @param   mixed    default value
156      * @param   boolean  XSS clean the value
157      * @return  mixed
158      */
159     public function cookie($key = array(), $default = NULL, $xss_clean = FALSE)
160     {
161         return $this->search_array($_COOKIE, $key, $default, $xss_clean);
162     }
163
164     /**
165      * Fetch an item from the $_SERVER array.
166      *
167      * @param   string   key to find
168      * @param   mixed    default value
169      * @param   boolean  XSS clean the value
170      * @return  mixed
171      */
172     public function server($key = array(), $default = NULL, $xss_clean = FALSE)
173     {
174         return $this->search_array($_SERVER, $key, $default, $xss_clean);
175     }
176
177     /**
178      * Fetch an item from a global array.
179      *
180      * @param   array    array to search
181      * @param   string   key to find
182      * @param   mixed    default value
183      * @param   boolean  XSS clean the value
184      * @return  mixed
185      */
186     protected function search_array($array, $key, $default = NULL, $xss_clean = FALSE)
187     {
188         if ($key === array())
189             return $array;
190
191         // Get the value from the array
192         $value = isset($array[$key]) ? $array[$key] : $default;
193
194         if ($this->use_xss_clean === FALSE AND $xss_clean === TRUE)
195         {
196             // XSS clean the value
197             $value = $this->xss_clean($value);
198         }
199
200         return $value;
201     }
202
203     /**
204      * Fetch the IP Address.
205      *
206      * @return string
207      */
208     public function ip_address()
209     {
210         if ($this->ip_address !== FALSE)
211             return $this->ip_address;
212
213         if ($this->server('REMOTE_ADDR') AND $this->server('HTTP_CLIENT_IP'))
214         {
215              $this->ip_address = $_SERVER['HTTP_CLIENT_IP'];
216         }
217         elseif ($this->server('REMOTE_ADDR'))
218         {
219              $this->ip_address = $_SERVER['REMOTE_ADDR'];
220         }
221         elseif ($this->server('HTTP_CLIENT_IP'))
222         {
223              $this->ip_address = $_SERVER['HTTP_CLIENT_IP'];
224         }
225         elseif ($this->server('HTTP_X_FORWARDED_FOR'))
226         {
227              $this->ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
228         }
229
230         if (strpos($this->ip_address, ',') !== FALSE)
231         {
232             $x = explode(',', $this->ip_address);
233             $this->ip_address = end($x);
234         }
235
236         if ( ! valid::ip($this->ip_address))
237         {
238             $this->ip_address = '0.0.0.0';
239         }
240
241         return $this->ip_address;
242     }
243
244     /**
245      * Clean cross site scripting exploits from string.
246      * HTMLPurifier may be used if installed, otherwise defaults to built in method.
247      * Note - This function should only be used to deal with data upon submission.
248      * It's not something that should be used for general runtime processing
249      * since it requires a fair amount of processing overhead.
250      *
251      * @param   string  data to clean
252      * @param   string  xss_clean method to use ('htmlpurifier' or defaults to built in method)
253      * @return  string
254      */
255     public function xss_clean($data, $tool = NULL)
256     {
257         if (is_array($data))
258         {
259             foreach ($data as $key => $val)
260             {
261                 $data[$key] = $this->xss_clean($val, $tool);
262             }
263             return $data;
264         }
265
266         // It is a string
267         $string = $data;
268
269         // Do not clean empty strings
270         if (trim($string) === '')
271             return $string;
272
273         if ( ! is_string($tool))
274         {
275             // Fetch the configured tool
276             if (is_bool($tool = Config::item('core.global_xss_filtering')))
277             {
278                 // Make sure that the default tool is used
279                 $tool = 'default';
280             }
281         }
282
283         switch ($tool)
284         {
285             case 'htmlpurifier':
286                 /**
287                  * @todo License should go here, http://htmlpurifier.org/
288                  */
289                 require_once Kohana::find_file('vendor', 'htmlpurifier/HTMLPurifier.auto');
290                 require_once 'HTMLPurifier.func.php';
291
292                 // Set configuration
293                 $config = HTMLPurifier_Config::createDefault();
294                 $config->set('HTML', 'TidyLevel', 'none'); // Only XSS cleaning now
295
296                 // Run HTMLPurifier
297                 $string = HTMLPurifier($string, $config);
298             break;
299             default:
300                 // http://svn.bitflux.ch/repos/public/popoon/trunk/classes/externalinput.php
301                 // +----------------------------------------------------------------------+
302                 // | Copyright (c) 2001-2006 Bitflux GmbH                                 |
303                 // +----------------------------------------------------------------------+
304                 // | Licensed under the Apache License, Version 2.0 (the "License");      |
305                 // | you may not use this file except in compliance with the License.     |
306                 // | You may obtain a copy of the License at                              |
307                 // | http://www.apache.org/licenses/LICENSE-2.0                           |
308                 // | Unless required by applicable law or agreed to in writing, software  |
309                 // | distributed under the License is distributed on an "AS IS" BASIS,    |
310                 // | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or      |
311                 // | implied. See the License for the specific language governing         |
312                 // | permissions and limitations under the License.                       |
313                 // +----------------------------------------------------------------------+
314                 // | Author: Christian Stocker <chregu@bitflux.ch>                        |
315                 // +----------------------------------------------------------------------+
316                 //
317                 // Kohana Modifications:
318                 // * Changed double quotes to single quotes, changed indenting and spacing
319                 // * Removed magic_quotes stuff
320                 // * Increased regex readability:
321                 //   * Used delimeters that aren't found in the pattern
322                 //   * Removed all unneeded escapes
323                 //   * Deleted U modifiers and swapped greediness where needed
324                 // * Increased regex speed:
325                 //   * Made capturing parentheses non-capturing where possible
326                 //   * Removed parentheses where possible
327                 //   * Split up alternation alternatives
328                 //
329
330                 $string = str_replace(array('&amp;','&lt;','&gt;'), array('&amp;amp;','&amp;lt;','&amp;gt;'), $string);
331                 // fix &entitiy\n;
332
333                 $string = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $string);
334                 $string = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $string);
335                 $string = html_entity_decode($string, ENT_COMPAT, 'UTF-8');
336
337                 // remove any attribute starting with "on" or xmlns
338                 $string = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*>#iu', '$1>', $string);
339                 // remove javascript: and vbscript: protocol
340                 $string = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $string);
341                 $string = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $string);
342                 $string = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $string);
343                 //<span style="width: expression(alert('Ping!'));"></span>
344                 // only works in ie...
345                 $string = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*>#i', '$1>', $string);
346                 $string = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*>#i', '$1>', $string);
347                 $string = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*>#iu', '$1>', $string);
348                 //remove namespaced elements (we do not need them...)
349                 $string = preg_replace('#</*\w+:\w[^>]*>#i', '',$string);
350                 //remove really unwanted tags
351
352                 do {
353                     $oldstring = $string;
354                     $string = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*>#i', '', $string);
355                 }
356                 while ($oldstring !== $string);
357             break;
358         }
359
360         return $string;
361     }
362
363     /**
364      * This is a helper method. It enforces W3C specifications for allowed
365      * key name strings, to prevent malicious exploitation.
366      *
367      * @param   string  string to clean
368      * @return  string
369      */
370     public function clean_input_keys($str)
371     {
372         $chars = PCRE_UNICODE_PROPERTIES ? '\pL' : 'a-zA-Z';
373
374         if ( ! preg_match('#^['.$chars.'0-9:_.-]++$#uD', $str))
375         {
376             exit('Disallowed key characters in global data.');
377         }
378
379         return $str;
380     }
381
382     /**
383      * This is a helper method. It escapes data and forces all newline
384      * characters to "\n".
385      *
386      * @param   unknown_type  string to clean
387      * @return  string
388      */
389     public function clean_input_data($str)
390     {
391         if (is_array($str))
392         {
393             $new_array = array();
394             foreach ($str as $key => $val)
395             {
396                 // Recursion!
397                 $new_array[$this->clean_input_keys($key)] = $this->clean_input_data($val);
398             }
399             return $new_array;
400         }
401
402         if (get_magic_quotes_gpc())
403         {
404             // Remove annoying magic quotes
405             $str = stripslashes($str);
406         }
407
408         if ($this->use_xss_clean === TRUE)
409         {
410             $str = $this->xss_clean($str);
411         }
412
413         if (strpos($str, "\r") !== FALSE)
414         {
415             // Standardize newlines
416             $str = str_replace(array("\r\n", "\r"), "\n", $str);
417         }
418
419         return $str;
420     }
421
422 } // End Input Class
423
Note: See TracBrowser for help on using the browser.