]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/Request.php
Patch from Matti Airas:
[SourceForge/phpwiki.git] / lib / Request.php
1 <?php rcs_id('$Id: Request.php,v 1.24 2002-12-14 16:21:46 dairiki Exp $');
2
3 // FIXME: write log entry.
4
5 class Request {
6         
7     function Request() {
8         $this->_fix_magic_quotes_gpc();
9         $this->_fix_multipart_form_data();
10         
11         switch($this->get('REQUEST_METHOD')) {
12         case 'GET':
13         case 'HEAD':
14             // $this->sanify_input_array(&$GLOBALS['HTTP_GET_VARS']);
15             $this->args = &$GLOBALS['HTTP_GET_VARS'];
16             break;
17         case 'POST':
18             // $this->sanify_input_array(&$GLOBALS['HTTP_POST_VARS']);
19             $this->args = &$GLOBALS['HTTP_POST_VARS'];
20             break;
21         default:
22             $this->args = array();
23             break;
24         }
25         
26         $this->session = new Request_SessionVars; 
27         $this->cookies = new Request_CookieVars;
28         
29         if (ACCESS_LOG)
30             $this->_log_entry = & new Request_AccessLogEntry($this,
31                                                              ACCESS_LOG);
32         
33         $GLOBALS['request'] = $this;
34     }
35
36     function get($key) {
37         $vars = &$GLOBALS['HTTP_SERVER_VARS'];
38
39         if (isset($vars[$key]))
40             return $vars[$key];
41
42         switch ($key) {
43         case 'REMOTE_HOST':
44             $addr = $vars['REMOTE_ADDR'];
45             if (defined('ENABLE_REVERSE_DNS') && ENABLE_REVERSE_DNS)
46                 return $vars[$key] = gethostbyaddr($addr);
47             else
48                 return $addr;
49         default:
50             return false;
51         }
52     }
53
54     function getArg($key) {
55         if (isset($this->args[$key]))
56             return $this->args[$key];
57         return false;
58     }
59
60     function getArgs () {
61         return $this->args;
62     }
63     
64     function setArg($key, $val) {
65         if ($val === false)
66             unset($this->args[$key]);
67         else
68             $this->args[$key] = $val;
69     }
70     
71     function debugVars() {
72         $array = array();
73         foreach (array('start_debug','debug_port','debug_no_cache') as $key) {
74             if (isset($GLOBALS['HTTP_GET_VARS'][$key]))
75                 $array[$key] = $GLOBALS['HTTP_GET_VARS'][$key];
76         }
77         return $array;
78     }
79
80
81     // Well oh well. Do we really want to pass POST params back as GET?
82     function getURLtoSelf($args = false, $exclude = array()) {
83         $get_args = $this->args;
84         if ($args)
85             $get_args = array_merge($get_args, $args);
86         if (defined('DEBUG'))
87             $get_args = array_merge($get_args, $this->debugVars());
88
89         foreach ($exclude as $ex) {
90             if (!empty($get_args[$ex])) unset($get_args[$ex]);
91         }
92
93         $pagename = $get_args['pagename'];
94         unset ($get_args['pagename']);
95         if ($get_args['action'] == 'browse')
96             unset($get_args['action']);
97
98         return WikiURL($pagename, $get_args);
99     }
100
101     function isPost () {
102         return $this->get("REQUEST_METHOD") == "POST";
103     }
104     
105     function redirect($url) {
106         header("Location: $url");
107         if (isset($this->_log_entry))
108             $this->_log_entry->setStatus(302);
109     }
110
111     function setStatus($status) {
112         if (preg_match('|^HTTP/.*?\s(\d+)|i', $status, $m)) {
113             header($status);
114             $status = $m[1];
115         }
116         else {
117             $status = (integer) $status;
118             $reasons = array('200' => 'OK',
119                              '302' => 'Found',
120                              '400' => 'Bad Request',
121                              '401' => 'Unauthorized',
122                              '403' => 'Forbidden',
123                              '404' => 'Not Found');
124             header(sprintf("HTTP/1.0 %d %s", $status, $reason[$status]));
125         }
126
127         if (isset($this->_log_entry))
128             $this->_log_entry->setStatus($status);
129     }
130
131     function compress_output() {
132         if ( function_exists('ob_gzhandler')
133              && function_exists('version_compare') /* (only in php >= 4.1.0) */
134              && version_compare(phpversion(), '4.2.3', ">=")
135              ){
136             ob_start('ob_gzhandler');
137             $this->_is_compressing_output = true;
138         }
139     }
140
141     function finish() {
142         if (!empty($this->_is_compressing_output))
143             ob_end_flush();
144     }
145
146     function getSessionVar($key) {
147         return $this->session->get($key);
148     }
149     function setSessionVar($key, $val) {
150         return $this->session->set($key, $val);
151     }
152     function deleteSessionVar($key) {
153         return $this->session->delete($key);
154     }
155
156     function getCookieVar($key) {
157         return $this->cookies->get($key);
158     }
159     function setCookieVar($key, $val, $lifetime_in_days = false) {
160         return $this->cookies->set($key, $val, $lifetime_in_days);
161     }
162     function deleteCookieVar($key) {
163         return $this->cookies->delete($key);
164     }
165     
166     function getUploadedFile($key) {
167         return Request_UploadedFile::getUploadedFile($key);
168     }
169     
170
171     function _fix_magic_quotes_gpc() {
172         $needs_fix = array('HTTP_POST_VARS',
173                            'HTTP_GET_VARS',
174                            'HTTP_COOKIE_VARS',
175                            'HTTP_SERVER_VARS',
176                            'HTTP_POST_FILES');
177         
178         // Fix magic quotes.
179         if (get_magic_quotes_gpc()) {
180             foreach ($needs_fix as $vars)
181                 $this->_stripslashes($GLOBALS[$vars]);
182         }
183     }
184
185     function _stripslashes(&$var) {
186         if (is_array($var)) {
187             foreach ($var as $key => $val)
188                 $this->_stripslashes($var[$key]);
189         }
190         elseif (is_string($var))
191             $var = stripslashes($var);
192     }
193     
194     function _fix_multipart_form_data () {
195         if (preg_match('|^multipart/form-data|', $this->get('CONTENT_TYPE')))
196             $this->_strip_leading_nl($GLOBALS['HTTP_POST_VARS']);
197     }
198     
199     function _strip_leading_nl(&$var) {
200         if (is_array($var)) {
201             foreach ($var as $key => $val)
202                 $this->_strip_leading_nl($var[$key]);
203         }
204         elseif (is_string($var))
205             $var = preg_replace('|^\r?\n?|', '', $var);
206     }
207
208     // Fixme: Seperate into fields of type: displayed, db, internal, password, ...
209     // and define for each type the allowed characters.
210     function sanify_input_array (&$arr) {
211         if (!empty($arr)) {
212             foreach (array_keys($arr) as $key) {
213                 if (!in_array($key,array('edit','password')))
214                     $arr[$key] = $this->sanify_userinput($arr[$key]);
215             }
216         }
217     }
218
219     function sanify_userinput ($var) {
220         // Prevent possible XSS attacks (cross site scripting attacks)
221         // See http://www.cert.org/advisories/CA-2000-02.html, http://www.perl.com/pub/a/2002/02/20/css.html
222         // <script> tags, ...
223         // /wiki/?pagename=<script>alert(document.cookie)</script>
224         if (is_string($var)) {
225             return strip_tags($var);
226         } elseif (is_array($var)) {
227             $this->sanify_input_array($var);
228             return $var;
229         } else {
230             return $var;
231         }
232     }
233 }
234
235 class Request_SessionVars {
236     function Request_SessionVars() {
237         // Prevent cacheing problems with IE 5
238         session_cache_limiter('none');
239                                         
240         session_start();
241     }
242     
243     function get($key) {
244         $vars = &$GLOBALS['HTTP_SESSION_VARS'];
245         if (isset($vars[$key]))
246             return $vars[$key];
247         return false;
248     }
249     
250     function set($key, $val) {
251         $vars = &$GLOBALS['HTTP_SESSION_VARS'];
252         if (ini_get('register_globals')) {
253             // This is funky but necessary, at least in some PHP's
254             $GLOBALS[$key] = $val;
255         }
256         $vars[$key] = $val;
257         session_register($key);
258     }
259     
260     function delete($key) {
261         $vars = &$GLOBALS['HTTP_SESSION_VARS'];
262         if (ini_get('register_globals'))
263             unset($GLOBALS[$key]);
264         unset($vars[$key]);
265         session_unregister($key);
266     }
267 }
268
269 class Request_CookieVars {
270     
271     function get($key) {
272         $vars = &$GLOBALS['HTTP_COOKIE_VARS'];
273         if (isset($vars[$key])) {
274             @$val = unserialize($vars[$key]);
275             if (!empty($val))
276                 return $val;
277         }
278         return false;
279     }
280         
281     function set($key, $val, $persist_days = false) {
282         $vars = &$GLOBALS['HTTP_COOKIE_VARS'];
283         
284         if (is_numeric($persist_days)) {
285             $expires = time() + (24 * 3600) * $persist_days;
286         }
287         else {
288             $expires = 0;
289         }
290         
291         $packedval = serialize($val);
292         $vars[$key] = $packedval;
293         setcookie($key, $packedval, $expires, '/');
294     }
295     
296     function delete($key) {
297         $vars = &$GLOBALS['HTTP_COOKIE_VARS'];
298         setcookie($key);
299         unset($vars[$key]);
300     }
301 }
302
303 class Request_UploadedFile {
304     function getUploadedFile($postname) {
305         global $HTTP_POST_FILES;
306         
307         if (!isset($HTTP_POST_FILES[$postname]))
308             return false;
309         
310         $fileinfo = &$HTTP_POST_FILES[$postname];
311         if (!is_uploaded_file($fileinfo['tmp_name']))
312             return false;       // possible malicious attack.
313
314         return new Request_UploadedFile($fileinfo);
315     }
316     
317     function Request_UploadedFile($fileinfo) {
318         $this->_info = $fileinfo;
319     }
320
321     function getSize() {
322         return $this->_info['size'];
323     }
324
325     function getName() {
326         return $this->_info['name'];
327     }
328
329     function getType() {
330         return $this->_info['type'];
331     }
332
333     function open() {
334         if ( ($fd = fopen($this->_info['tmp_name'], "rb")) ) {
335             if ($this->getSize() < filesize($this->_info['tmp_name'])) {
336                 // FIXME: Some PHP's (or is it some browsers?) put
337                 //    HTTP/MIME headers in the file body, some don't.
338                 //
339                 // At least, I think that's the case.  I know I used
340                 // to need this code, now I don't.
341                 //
342                 // This code is more-or-less untested currently.
343                 //
344                 // Dump HTTP headers.
345                 while ( ($header = fgets($fd, 4096)) ) {
346                     if (trim($header) == '') {
347                         break;
348                     }
349                     else if (!preg_match('/^content-(length|type):/i', $header)) {
350                         rewind($fd);
351                         break;
352                     }
353                 }
354             }
355         }
356         return $fd;
357     }
358
359     function getContents() {
360         $fd = $this->open();
361         $data = fread($fd, $this->getSize());
362         fclose($fd);
363         return $data;
364     }
365 }
366
367 /**
368  * Create NCSA "combined" log entry for current request.
369  */
370 class Request_AccessLogEntry
371 {
372     /**
373      * Constructor.
374      *
375      * The log entry will be automatically appended to the log file
376      * when the current request terminates.
377      *
378      * If you want to modify a Request_AccessLogEntry before it gets
379      * written (e.g. via the setStatus and setSize methods) you should
380      * use an '&' on the constructor, so that you're working with the
381      * original (rather than a copy) object.
382      *
383      * <pre>
384      *    $log_entry = & new Request_AccessLogEntry($req, "/tmp/wiki_access_log");
385      *    $log_entry->setStatus(401);
386      * </pre>
387      *
388      *
389      * @param $request object  Request object for current request.
390      * @param $logfile string  Log file name.
391      */
392     function Request_AccessLogEntry (&$request, $logfile) {
393         $this->logfile = $logfile;
394         
395         $this->host  = $request->get('REMOTE_HOST');
396         $this->ident = $request->get('REMOTE_IDENT');
397         if (!$this->ident)
398             $this->ident = '-';
399         $this->user = '-';        // FIXME: get logged-in user name
400         $this->time = time();
401         $this->request = join(' ', array($request->get('REQUEST_METHOD'),
402                                          $request->get('REQUEST_URI'),
403                                          $request->get('SERVER_PROTOCOL')));
404         $this->status = 200;
405         $this->size = 0;
406         $this->referer = (string) $request->get('HTTP_REFERER');
407         $this->user_agent = (string) $request->get('HTTP_USER_AGENT');
408
409         global $Request_AccessLogEntry_entries;
410         if (!isset($Request_AccessLogEntry_entries)) {
411             register_shutdown_function("Request_AccessLogEntry_shutdown_function");
412         }
413         $Request_AccessLogEntry_entries[] = &$this;
414     }
415
416     /**
417      * Set result status code.
418      *
419      * @param $status integer  HTTP status code.
420      */
421     function setStatus ($status) {
422         $this->status = $status;
423     }
424     
425     /**
426      * Set response size.
427      *
428      * @param $size integer
429      */
430     function setSize ($size) {
431         $this->size = $size;
432     }
433     
434     /**
435      * Get time zone offset.
436      *
437      * This is a static member function.
438      *
439      * @param $time integer Unix timestamp (defaults to current time).
440      * @return string Zone offset, e.g. "-0800" for PST.
441      */
442     function _zone_offset ($time = false) {
443         if (!$time)
444             $time = time();
445         $offset = date("Z", $time);
446         if ($offset < 0) {
447             $negoffset = "-";
448             $offset = -$offset;
449         }
450         $offhours = floor($offset / 3600);
451         $offmins  = $offset / 60 - $offhours * 60;
452         return sprintf("%s%02d%02d", $negoffset, $offhours, $offmins);
453     }
454
455     /**
456      * Format time in NCSA format.
457      *
458      * This is a static member function.
459      *
460      * @param $time integer Unix timestamp (defaults to current time).
461      * @return string Formatted date & time.
462      */
463     function _ncsa_time($time = false) {
464         if (!$time)
465             $time = time();
466
467         return date("d/M/Y:H:i:s", $time) .
468             " " . $this->_zone_offset();
469     }
470
471     /**
472      * Write entry to log file.
473      */
474     function write() {
475         $entry = sprintf('%s %s %s [%s] "%s" %d %d "%s" "%s"',
476                          $this->host, $this->ident, $this->user,
477                          $this->_ncsa_time($this->time),
478                          $this->request, $this->status, $this->size,
479                          $this->referer, $this->user_agent);
480
481         //Error log doesn't provide locking.
482         //error_log("$entry\n", 3, $this->logfile);
483
484         // Alternate method 
485         if (($fp = fopen($this->logfile, "a"))) {
486             flock($fp, LOCK_EX);
487             fputs($fp, "$entry\n");
488             fclose($fp);
489         }
490     }
491 }
492
493 /**
494  * Shutdown callback.
495  *
496  * @access private
497  * @see Request_AccessLogEntry
498  */
499 function Request_AccessLogEntry_shutdown_function ()
500 {
501     global $Request_AccessLogEntry_entries;
502     
503     foreach ($Request_AccessLogEntry_entries as $entry) {
504         $entry->write();
505     }
506     unset($Request_AccessLogEntry_entries);
507 }
508
509 // Local Variables:
510 // mode: php
511 // tab-width: 8
512 // c-basic-offset: 4
513 // c-hanging-comment-ender-p: nil
514 // indent-tabs-mode: nil
515 // End:   
516 ?>