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