/framework/web/CHttpRequest.php
PHP | 1191 lines | 782 code | 54 blank | 355 comment | 73 complexity | 266e1f1243cab8d6ea6ba8e5560fd500 MD5 | raw file
1<?php
2/**
3 * CHttpRequest and CCookieCollection class file.
4 *
5 * @author Qiang Xue <qiang.xue@gmail.com>
6 * @link http://www.yiiframework.com/
7 * @copyright Copyright © 2008-2011 Yii Software LLC
8 * @license http://www.yiiframework.com/license/
9 */
10
11
12/**
13 * CHttpRequest encapsulates the $_SERVER variable and resolves its inconsistency among different Web servers.
14 *
15 * CHttpRequest also manages the cookies sent from and sent to the user.
16 * By setting {@link enableCookieValidation} to true,
17 * cookies sent from the user will be validated to see if they are tampered.
18 * The property {@link getCookies cookies} returns the collection of cookies.
19 * For more details, see {@link CCookieCollection}.
20 *
21 * CHttpRequest is a default application component loaded by {@link CWebApplication}. It can be
22 * accessed via {@link CWebApplication::getRequest()}.
23 *
24 * @property string $url Part of the request URL after the host info.
25 * @property string $hostInfo Schema and hostname part (with port number if needed) of the request URL (e.g. http://www.yiiframework.com).
26 * @property string $baseUrl The relative URL for the application.
27 * @property string $scriptUrl The relative URL of the entry script.
28 * @property string $pathInfo Part of the request URL that is after the entry script and before the question mark.
29 * Note, the returned pathinfo is decoded starting from 1.1.4.
30 * Prior to 1.1.4, whether it is decoded or not depends on the server configuration
31 * (in most cases it is not decoded).
32 * @property string $requestUri The request URI portion for the currently requested URL.
33 * @property string $queryString Part of the request URL that is after the question mark.
34 * @property boolean $isSecureConnection If the request is sent via secure channel (https).
35 * @property string $requestType Request type, such as GET, POST, HEAD, PUT, DELETE.
36 * @property boolean $isPostRequest Whether this is a POST request.
37 * @property boolean $isDeleteRequest Whether this is a DELETE request.
38 * @property boolean $isPutRequest Whether this is a PUT request.
39 * @property boolean $isAjaxRequest Whether this is an AJAX (XMLHttpRequest) request.
40 * @property boolean $isFlashRequest Whether this is an Adobe Flash or Adobe Flex request.
41 * @property string $serverName Server name.
42 * @property integer $serverPort Server port number.
43 * @property string $urlReferrer URL referrer, null if not present.
44 * @property string $userAgent User agent, null if not present.
45 * @property string $userHostAddress User IP address.
46 * @property string $userHost User host name, null if cannot be determined.
47 * @property string $scriptFile Entry script file path (processed w/ realpath()).
48 * @property array $browser User browser capabilities.
49 * @property string $acceptTypes User browser accept types, null if not present.
50 * @property integer $port Port number for insecure requests.
51 * @property integer $securePort Port number for secure requests.
52 * @property CCookieCollection|CHttpCookie[] $cookies The cookie collection.
53 * @property string $preferredLanguage The user preferred language.
54 * @property array $preferredLanguages An array of all user accepted languages in order of preference.
55 * @property string $csrfToken The random token for CSRF validation.
56 *
57 * @author Qiang Xue <qiang.xue@gmail.com>
58 * @package system.web
59 * @since 1.0
60 */
61class CHttpRequest extends CApplicationComponent
62{
63 /**
64 * @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to false.
65 */
66 public $enableCookieValidation=false;
67 /**
68 * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to false.
69 * By setting this property to true, forms submitted to an Yii Web application must be originated
70 * from the same application. If not, a 400 HTTP exception will be raised.
71 * Note, this feature requires that the user client accepts cookie.
72 * You also need to use {@link CHtml::form} or {@link CHtml::statefulForm} to generate
73 * the needed HTML forms in your pages.
74 * @see http://seclab.stanford.edu/websec/csrf/csrf.pdf
75 */
76 public $enableCsrfValidation=false;
77 /**
78 * @var string the name of the token used to prevent CSRF. Defaults to 'YII_CSRF_TOKEN'.
79 * This property is effectively only when {@link enableCsrfValidation} is true.
80 */
81 public $csrfTokenName='YII_CSRF_TOKEN';
82 /**
83 * @var array the property values (in name-value pairs) used to initialize the CSRF cookie.
84 * Any property of {@link CHttpCookie} may be initialized.
85 * This property is effective only when {@link enableCsrfValidation} is true.
86 */
87 public $csrfCookie;
88
89 private $_requestUri;
90 private $_pathInfo;
91 private $_scriptFile;
92 private $_scriptUrl;
93 private $_hostInfo;
94 private $_baseUrl;
95 private $_cookies;
96 private $_preferredLanguages;
97 private $_csrfToken;
98 private $_restParams;
99
100 /**
101 * Initializes the application component.
102 * This method overrides the parent implementation by preprocessing
103 * the user request data.
104 */
105 public function init()
106 {
107 parent::init();
108 $this->normalizeRequest();
109 }
110
111 /**
112 * Normalizes the request data.
113 * This method strips off slashes in request data if get_magic_quotes_gpc() returns true.
114 * It also performs CSRF validation if {@link enableCsrfValidation} is true.
115 */
116 protected function normalizeRequest()
117 {
118 // normalize request
119 if(function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc())
120 {
121 if(isset($_GET))
122 $_GET=$this->stripSlashes($_GET);
123 if(isset($_POST))
124 $_POST=$this->stripSlashes($_POST);
125 if(isset($_REQUEST))
126 $_REQUEST=$this->stripSlashes($_REQUEST);
127 if(isset($_COOKIE))
128 $_COOKIE=$this->stripSlashes($_COOKIE);
129 }
130
131 if($this->enableCsrfValidation)
132 Yii::app()->attachEventHandler('onBeginRequest',array($this,'validateCsrfToken'));
133 }
134
135
136 /**
137 * Strips slashes from input data.
138 * This method is applied when magic quotes is enabled.
139 * @param mixed $data input data to be processed
140 * @return mixed processed data
141 */
142 public function stripSlashes(&$data)
143 {
144 return is_array($data)?array_map(array($this,'stripSlashes'),$data):stripslashes($data);
145 }
146
147 /**
148 * Returns the named GET or POST parameter value.
149 * If the GET or POST parameter does not exist, the second parameter to this method will be returned.
150 * If both GET and POST contains such a named parameter, the GET parameter takes precedence.
151 * @param string $name the GET parameter name
152 * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
153 * @return mixed the GET parameter value
154 * @see getQuery
155 * @see getPost
156 */
157 public function getParam($name,$defaultValue=null)
158 {
159 return isset($_GET[$name]) ? $_GET[$name] : (isset($_POST[$name]) ? $_POST[$name] : $defaultValue);
160 }
161
162 /**
163 * Returns the named GET parameter value.
164 * If the GET parameter does not exist, the second parameter to this method will be returned.
165 * @param string $name the GET parameter name
166 * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
167 * @return mixed the GET parameter value
168 * @see getPost
169 * @see getParam
170 */
171 public function getQuery($name,$defaultValue=null)
172 {
173 return isset($_GET[$name]) ? $_GET[$name] : $defaultValue;
174 }
175
176 /**
177 * Returns the named POST parameter value.
178 * If the POST parameter does not exist, the second parameter to this method will be returned.
179 * @param string $name the POST parameter name
180 * @param mixed $defaultValue the default parameter value if the POST parameter does not exist.
181 * @return mixed the POST parameter value
182 * @see getParam
183 * @see getQuery
184 */
185 public function getPost($name,$defaultValue=null)
186 {
187 return isset($_POST[$name]) ? $_POST[$name] : $defaultValue;
188 }
189
190 /**
191 * Returns the named DELETE parameter value.
192 * If the DELETE parameter does not exist or if the current request is not a DELETE request,
193 * the second parameter to this method will be returned.
194 * If the DELETE request was tunneled through POST via _method parameter, the POST parameter
195 * will be returned instead (available since version 1.1.11).
196 * @param string $name the DELETE parameter name
197 * @param mixed $defaultValue the default parameter value if the DELETE parameter does not exist.
198 * @return mixed the DELETE parameter value
199 * @since 1.1.7
200 */
201 public function getDelete($name,$defaultValue=null)
202 {
203 if($this->getIsDeleteViaPostRequest())
204 return $this->getPost($name, $defaultValue);
205
206 if($this->getIsDeleteRequest())
207 {
208 $this->getRestParams();
209 return isset($this->_restParams[$name]) ? $this->_restParams[$name] : $defaultValue;
210 }
211 else
212 return $defaultValue;
213 }
214
215 /**
216 * Returns the named PUT parameter value.
217 * If the PUT parameter does not exist or if the current request is not a PUT request,
218 * the second parameter to this method will be returned.
219 * If the PUT request was tunneled through POST via _method parameter, the POST parameter
220 * will be returned instead (available since version 1.1.11).
221 * @param string $name the PUT parameter name
222 * @param mixed $defaultValue the default parameter value if the PUT parameter does not exist.
223 * @return mixed the PUT parameter value
224 * @since 1.1.7
225 */
226 public function getPut($name,$defaultValue=null)
227 {
228 if($this->getIsPutViaPostRequest())
229 return $this->getPost($name, $defaultValue);
230
231 if($this->getIsPutRequest())
232 {
233 $this->getRestParams();
234 return isset($this->_restParams[$name]) ? $this->_restParams[$name] : $defaultValue;
235 }
236 else
237 return $defaultValue;
238 }
239
240 /**
241 * Returns request parameters. Typically PUT or DELETE.
242 * @return array the request parameters
243 * @since 1.1.7
244 * @since 1.1.13 method became public
245 */
246 public function getRestParams()
247 {
248 if($this->_restParams===null)
249 {
250 $result=array();
251 if(function_exists('mb_parse_str'))
252 mb_parse_str($this->getRawBody(), $result);
253 else
254 parse_str($this->getRawBody(), $result);
255 $this->_restParams=$result;
256 }
257
258 return $this->_restParams;
259 }
260
261 /**
262 * Returns the raw HTTP request body.
263 * @return string the request body
264 * @since 1.1.13
265 */
266 public function getRawBody()
267 {
268 static $rawBody;
269 if($rawBody===null)
270 $rawBody=file_get_contents('php://input');
271 return $rawBody;
272 }
273
274 /**
275 * Returns the currently requested URL.
276 * This is the same as {@link getRequestUri}.
277 * @return string part of the request URL after the host info.
278 */
279 public function getUrl()
280 {
281 return $this->getRequestUri();
282 }
283
284 /**
285 * Returns the schema and host part of the application URL.
286 * The returned URL does not have an ending slash.
287 * By default this is determined based on the user request information.
288 * You may explicitly specify it by setting the {@link setHostInfo hostInfo} property.
289 * @param string $schema schema to use (e.g. http, https). If empty, the schema used for the current request will be used.
290 * @return string schema and hostname part (with port number if needed) of the request URL (e.g. http://www.yiiframework.com)
291 * @see setHostInfo
292 */
293 public function getHostInfo($schema='')
294 {
295 if($this->_hostInfo===null)
296 {
297 if($secure=$this->getIsSecureConnection())
298 $http='https';
299 else
300 $http='http';
301 if(isset($_SERVER['HTTP_HOST']))
302 $this->_hostInfo=$http.'://'.$_SERVER['HTTP_HOST'];
303 else
304 {
305 $this->_hostInfo=$http.'://'.$_SERVER['SERVER_NAME'];
306 $port=$secure ? $this->getSecurePort() : $this->getPort();
307 if(($port!==80 && !$secure) || ($port!==443 && $secure))
308 $this->_hostInfo.=':'.$port;
309 }
310 }
311 if($schema!=='')
312 {
313 $secure=$this->getIsSecureConnection();
314 if($secure && $schema==='https' || !$secure && $schema==='http')
315 return $this->_hostInfo;
316
317 $port=$schema==='https' ? $this->getSecurePort() : $this->getPort();
318 if($port!==80 && $schema==='http' || $port!==443 && $schema==='https')
319 $port=':'.$port;
320 else
321 $port='';
322
323 $pos=strpos($this->_hostInfo,':');
324 return $schema.substr($this->_hostInfo,$pos,strcspn($this->_hostInfo,':',$pos+1)+1).$port;
325 }
326 else
327 return $this->_hostInfo;
328 }
329
330 /**
331 * Sets the schema and host part of the application URL.
332 * This setter is provided in case the schema and hostname cannot be determined
333 * on certain Web servers.
334 * @param string $value the schema and host part of the application URL.
335 */
336 public function setHostInfo($value)
337 {
338 $this->_hostInfo=rtrim($value,'/');
339 }
340
341 /**
342 * Returns the relative URL for the application.
343 * This is similar to {@link getScriptUrl scriptUrl} except that
344 * it does not have the script file name, and the ending slashes are stripped off.
345 * @param boolean $absolute whether to return an absolute URL. Defaults to false, meaning returning a relative one.
346 * @return string the relative URL for the application
347 * @see setScriptUrl
348 */
349 public function getBaseUrl($absolute=false)
350 {
351 if($this->_baseUrl===null)
352 $this->_baseUrl=rtrim(dirname($this->getScriptUrl()),'\\/');
353 return $absolute ? $this->getHostInfo() . $this->_baseUrl : $this->_baseUrl;
354 }
355
356 /**
357 * Sets the relative URL for the application.
358 * By default the URL is determined based on the entry script URL.
359 * This setter is provided in case you want to change this behavior.
360 * @param string $value the relative URL for the application
361 */
362 public function setBaseUrl($value)
363 {
364 $this->_baseUrl=$value;
365 }
366
367 /**
368 * Returns the relative URL of the entry script.
369 * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
370 * @return string the relative URL of the entry script.
371 */
372 public function getScriptUrl()
373 {
374 if($this->_scriptUrl===null)
375 {
376 $scriptName=basename($_SERVER['SCRIPT_FILENAME']);
377 if(basename($_SERVER['SCRIPT_NAME'])===$scriptName)
378 $this->_scriptUrl=$_SERVER['SCRIPT_NAME'];
379 elseif(basename($_SERVER['PHP_SELF'])===$scriptName)
380 $this->_scriptUrl=$_SERVER['PHP_SELF'];
381 elseif(isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME'])===$scriptName)
382 $this->_scriptUrl=$_SERVER['ORIG_SCRIPT_NAME'];
383 elseif(($pos=strpos($_SERVER['PHP_SELF'],'/'.$scriptName))!==false)
384 $this->_scriptUrl=substr($_SERVER['SCRIPT_NAME'],0,$pos).'/'.$scriptName;
385 elseif(isset($_SERVER['DOCUMENT_ROOT']) && strpos($_SERVER['SCRIPT_FILENAME'],$_SERVER['DOCUMENT_ROOT'])===0)
386 $this->_scriptUrl=str_replace('\\','/',str_replace($_SERVER['DOCUMENT_ROOT'],'',$_SERVER['SCRIPT_FILENAME']));
387 else
388 throw new CException(Yii::t('yii','CHttpRequest is unable to determine the entry script URL.'));
389 }
390 return $this->_scriptUrl;
391 }
392
393 /**
394 * Sets the relative URL for the application entry script.
395 * This setter is provided in case the entry script URL cannot be determined
396 * on certain Web servers.
397 * @param string $value the relative URL for the application entry script.
398 */
399 public function setScriptUrl($value)
400 {
401 $this->_scriptUrl='/'.trim($value,'/');
402 }
403
404 /**
405 * Returns the path info of the currently requested URL.
406 * This refers to the part that is after the entry script and before the question mark.
407 * The starting and ending slashes are stripped off.
408 * @return string part of the request URL that is after the entry script and before the question mark.
409 * Note, the returned pathinfo is decoded starting from 1.1.4.
410 * Prior to 1.1.4, whether it is decoded or not depends on the server configuration
411 * (in most cases it is not decoded).
412 * @throws CException if the request URI cannot be determined due to improper server configuration
413 */
414 public function getPathInfo()
415 {
416 if($this->_pathInfo===null)
417 {
418 $pathInfo=$this->getRequestUri();
419
420 if(($pos=strpos($pathInfo,'?'))!==false)
421 $pathInfo=substr($pathInfo,0,$pos);
422
423 $pathInfo=$this->decodePathInfo($pathInfo);
424
425 $scriptUrl=$this->getScriptUrl();
426 $baseUrl=$this->getBaseUrl();
427 if(strpos($pathInfo,$scriptUrl)===0)
428 $pathInfo=substr($pathInfo,strlen($scriptUrl));
429 elseif($baseUrl==='' || strpos($pathInfo,$baseUrl)===0)
430 $pathInfo=substr($pathInfo,strlen($baseUrl));
431 elseif(strpos($_SERVER['PHP_SELF'],$scriptUrl)===0)
432 $pathInfo=substr($_SERVER['PHP_SELF'],strlen($scriptUrl));
433 else
434 throw new CException(Yii::t('yii','CHttpRequest is unable to determine the path info of the request.'));
435
436 $this->_pathInfo=trim($pathInfo,'/');
437 }
438 return $this->_pathInfo;
439 }
440
441 /**
442 * Decodes the path info.
443 * This method is an improved variant of the native urldecode() function and used in {@link getPathInfo getPathInfo()} to
444 * decode the path part of the request URI. You may override this method to change the way the path info is being decoded.
445 * @param string $pathInfo encoded path info
446 * @return string decoded path info
447 * @since 1.1.10
448 */
449 protected function decodePathInfo($pathInfo)
450 {
451 $pathInfo = urldecode($pathInfo);
452
453 // is it UTF-8?
454 // http://w3.org/International/questions/qa-forms-utf-8.html
455 if(preg_match('%^(?:
456 [\x09\x0A\x0D\x20-\x7E] # ASCII
457 | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
458 | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
459 | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
460 | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
461 | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
462 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
463 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
464 )*$%xs', $pathInfo))
465 {
466 return $pathInfo;
467 }
468 else
469 {
470 return utf8_encode($pathInfo);
471 }
472 }
473
474 /**
475 * Returns the request URI portion for the currently requested URL.
476 * This refers to the portion that is after the {@link hostInfo host info} part.
477 * It includes the {@link queryString query string} part if any.
478 * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
479 * @return string the request URI portion for the currently requested URL.
480 * @throws CException if the request URI cannot be determined due to improper server configuration
481 */
482 public function getRequestUri()
483 {
484 if($this->_requestUri===null)
485 {
486 if(isset($_SERVER['HTTP_X_REWRITE_URL'])) // IIS
487 $this->_requestUri=$_SERVER['HTTP_X_REWRITE_URL'];
488 elseif(isset($_SERVER['REQUEST_URI']))
489 {
490 $this->_requestUri=$_SERVER['REQUEST_URI'];
491 if(!empty($_SERVER['HTTP_HOST']))
492 {
493 if(strpos($this->_requestUri,$_SERVER['HTTP_HOST'])!==false)
494 $this->_requestUri=preg_replace('/^\w+:\/\/[^\/]+/','',$this->_requestUri);
495 }
496 else
497 $this->_requestUri=preg_replace('/^(http|https):\/\/[^\/]+/i','',$this->_requestUri);
498 }
499 elseif(isset($_SERVER['ORIG_PATH_INFO'])) // IIS 5.0 CGI
500 {
501 $this->_requestUri=$_SERVER['ORIG_PATH_INFO'];
502 if(!empty($_SERVER['QUERY_STRING']))
503 $this->_requestUri.='?'.$_SERVER['QUERY_STRING'];
504 }
505 else
506 throw new CException(Yii::t('yii','CHttpRequest is unable to determine the request URI.'));
507 }
508
509 return $this->_requestUri;
510 }
511
512 /**
513 * Returns part of the request URL that is after the question mark.
514 * @return string part of the request URL that is after the question mark
515 */
516 public function getQueryString()
517 {
518 return isset($_SERVER['QUERY_STRING'])?$_SERVER['QUERY_STRING']:'';
519 }
520
521 /**
522 * Return if the request is sent via secure channel (https).
523 * @return boolean if the request is sent via secure channel (https)
524 */
525 public function getIsSecureConnection()
526 {
527 return !empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'],'off');
528 }
529
530 /**
531 * Returns the request type, such as GET, POST, HEAD, PUT, DELETE.
532 * Request type can be manually set in POST requests with a parameter named _method. Useful
533 * for RESTful request from older browsers which do not support PUT or DELETE
534 * natively (available since version 1.1.11).
535 * @return string request type, such as GET, POST, HEAD, PUT, DELETE.
536 */
537 public function getRequestType()
538 {
539 if(isset($_POST['_method']))
540 return strtoupper($_POST['_method']);
541
542 return strtoupper(isset($_SERVER['REQUEST_METHOD'])?$_SERVER['REQUEST_METHOD']:'GET');
543 }
544
545 /**
546 * Returns whether this is a POST request.
547 * @return boolean whether this is a POST request.
548 */
549 public function getIsPostRequest()
550 {
551 return isset($_SERVER['REQUEST_METHOD']) && !strcasecmp($_SERVER['REQUEST_METHOD'],'POST');
552 }
553
554 /**
555 * Returns whether this is a DELETE request.
556 * @return boolean whether this is a DELETE request.
557 * @since 1.1.7
558 */
559 public function getIsDeleteRequest()
560 {
561 return (isset($_SERVER['REQUEST_METHOD']) && !strcasecmp($_SERVER['REQUEST_METHOD'],'DELETE')) || $this->getIsDeleteViaPostRequest();
562 }
563
564 /**
565 * Returns whether this is a DELETE request which was tunneled through POST.
566 * @return boolean whether this is a DELETE request tunneled through POST.
567 * @since 1.1.11
568 */
569 protected function getIsDeleteViaPostRequest()
570 {
571 return isset($_POST['_method']) && !strcasecmp($_POST['_method'],'DELETE');
572 }
573
574 /**
575 * Returns whether this is a PUT request.
576 * @return boolean whether this is a PUT request.
577 * @since 1.1.7
578 */
579 public function getIsPutRequest()
580 {
581 return (isset($_SERVER['REQUEST_METHOD']) && !strcasecmp($_SERVER['REQUEST_METHOD'],'PUT')) || $this->getIsPutViaPostRequest();
582 }
583
584 /**
585 * Returns whether this is a PUT request which was tunneled through POST.
586 * @return boolean whether this is a PUT request tunneled through POST.
587 * @since 1.1.11
588 */
589 protected function getIsPutViaPostRequest()
590 {
591 return isset($_POST['_method']) && !strcasecmp($_POST['_method'],'PUT');
592 }
593
594 /**
595 * Returns whether this is an AJAX (XMLHttpRequest) request.
596 * @return boolean whether this is an AJAX (XMLHttpRequest) request.
597 */
598 public function getIsAjaxRequest()
599 {
600 return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']==='XMLHttpRequest';
601 }
602
603 /**
604 * Returns whether this is an Adobe Flash or Adobe Flex request.
605 * @return boolean whether this is an Adobe Flash or Adobe Flex request.
606 * @since 1.1.11
607 */
608 public function getIsFlashRequest()
609 {
610 return isset($_SERVER['HTTP_USER_AGENT']) && (stripos($_SERVER['HTTP_USER_AGENT'],'Shockwave')!==false || stripos($_SERVER['HTTP_USER_AGENT'],'Flash')!==false);
611 }
612
613 /**
614 * Returns the server name.
615 * @return string server name
616 */
617 public function getServerName()
618 {
619 return $_SERVER['SERVER_NAME'];
620 }
621
622 /**
623 * Returns the server port number.
624 * @return integer server port number
625 */
626 public function getServerPort()
627 {
628 return $_SERVER['SERVER_PORT'];
629 }
630
631 /**
632 * Returns the URL referrer, null if not present
633 * @return string URL referrer, null if not present
634 */
635 public function getUrlReferrer()
636 {
637 return isset($_SERVER['HTTP_REFERER'])?$_SERVER['HTTP_REFERER']:null;
638 }
639
640 /**
641 * Returns the user agent, null if not present.
642 * @return string user agent, null if not present
643 */
644 public function getUserAgent()
645 {
646 return isset($_SERVER['HTTP_USER_AGENT'])?$_SERVER['HTTP_USER_AGENT']:null;
647 }
648
649 /**
650 * Returns the user IP address.
651 * @return string user IP address
652 */
653 public function getUserHostAddress()
654 {
655 return isset($_SERVER['REMOTE_ADDR'])?$_SERVER['REMOTE_ADDR']:'127.0.0.1';
656 }
657
658 /**
659 * Returns the user host name, null if it cannot be determined.
660 * @return string user host name, null if cannot be determined
661 */
662 public function getUserHost()
663 {
664 return isset($_SERVER['REMOTE_HOST'])?$_SERVER['REMOTE_HOST']:null;
665 }
666
667 /**
668 * Returns entry script file path.
669 * @return string entry script file path (processed w/ realpath())
670 */
671 public function getScriptFile()
672 {
673 if($this->_scriptFile!==null)
674 return $this->_scriptFile;
675 else
676 return $this->_scriptFile=realpath($_SERVER['SCRIPT_FILENAME']);
677 }
678
679 /**
680 * Returns information about the capabilities of user browser.
681 * @param string $userAgent the user agent to be analyzed. Defaults to null, meaning using the
682 * current User-Agent HTTP header information.
683 * @return array user browser capabilities.
684 * @see http://www.php.net/manual/en/function.get-browser.php
685 */
686 public function getBrowser($userAgent=null)
687 {
688 return get_browser($userAgent,true);
689 }
690
691 /**
692 * Returns user browser accept types, null if not present.
693 * @return string user browser accept types, null if not present
694 */
695 public function getAcceptTypes()
696 {
697 return isset($_SERVER['HTTP_ACCEPT'])?$_SERVER['HTTP_ACCEPT']:null;
698 }
699
700 private $_port;
701
702 /**
703 * Returns the port to use for insecure requests.
704 * Defaults to 80, or the port specified by the server if the current
705 * request is insecure.
706 * You may explicitly specify it by setting the {@link setPort port} property.
707 * @return integer port number for insecure requests.
708 * @see setPort
709 * @since 1.1.3
710 */
711 public function getPort()
712 {
713 if($this->_port===null)
714 $this->_port=!$this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int)$_SERVER['SERVER_PORT'] : 80;
715 return $this->_port;
716 }
717
718 /**
719 * Sets the port to use for insecure requests.
720 * This setter is provided in case a custom port is necessary for certain
721 * server configurations.
722 * @param integer $value port number.
723 * @since 1.1.3
724 */
725 public function setPort($value)
726 {
727 $this->_port=(int)$value;
728 $this->_hostInfo=null;
729 }
730
731 private $_securePort;
732
733 /**
734 * Returns the port to use for secure requests.
735 * Defaults to 443, or the port specified by the server if the current
736 * request is secure.
737 * You may explicitly specify it by setting the {@link setSecurePort securePort} property.
738 * @return integer port number for secure requests.
739 * @see setSecurePort
740 * @since 1.1.3
741 */
742 public function getSecurePort()
743 {
744 if($this->_securePort===null)
745 $this->_securePort=$this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int)$_SERVER['SERVER_PORT'] : 443;
746 return $this->_securePort;
747 }
748
749 /**
750 * Sets the port to use for secure requests.
751 * This setter is provided in case a custom port is necessary for certain
752 * server configurations.
753 * @param integer $value port number.
754 * @since 1.1.3
755 */
756 public function setSecurePort($value)
757 {
758 $this->_securePort=(int)$value;
759 $this->_hostInfo=null;
760 }
761
762 /**
763 * Returns the cookie collection.
764 * The result can be used like an associative array. Adding {@link CHttpCookie} objects
765 * to the collection will send the cookies to the client; and removing the objects
766 * from the collection will delete those cookies on the client.
767 * @return CCookieCollection the cookie collection.
768 */
769 public function getCookies()
770 {
771 if($this->_cookies!==null)
772 return $this->_cookies;
773 else
774 return $this->_cookies=new CCookieCollection($this);
775 }
776
777 /**
778 * Redirects the browser to the specified URL.
779 * @param string $url URL to be redirected to. Note that when URL is not
780 * absolute (not starting with "/") it will be relative to current request URL.
781 * @param boolean $terminate whether to terminate the current application
782 * @param integer $statusCode the HTTP status code. Defaults to 302. See {@link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html}
783 * for details about HTTP status code.
784 */
785 public function redirect($url,$terminate=true,$statusCode=302)
786 {
787 if(strpos($url,'/')===0 && strpos($url,'//')!==0)
788 $url=$this->getHostInfo().$url;
789 header('Location: '.$url, true, $statusCode);
790 if($terminate)
791 Yii::app()->end();
792 }
793
794 /**
795 * Returns an array of user accepted languages in order of preference.
796 * The returned language IDs will NOT be canonicalized using {@link CLocale::getCanonicalID}.
797 * @return array the user accepted languages in the order of preference.
798 * See {@link http://tools.ietf.org/html/rfc2616#section-14.4}
799 */
800 public function getPreferredLanguages()
801 {
802 if($this->_preferredLanguages===null)
803 {
804 $sortedLanguages=array();
805 if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) && $n=preg_match_all('/([\w\-_]+)(?:\s*;\s*q\s*=\s*(\d*\.?\d*))?/',$_SERVER['HTTP_ACCEPT_LANGUAGE'],$matches))
806 {
807 $languages=array();
808
809 for($i=0;$i<$n;++$i)
810 {
811 $q=$matches[2][$i];
812 if($q==='')
813 $q=1;
814 if($q)
815 $languages[]=array((float)$q,$matches[1][$i]);
816 }
817
818 usort($languages,create_function('$a,$b','if($a[0]==$b[0]) {return 0;} return ($a[0]<$b[0]) ? 1 : -1;'));
819 foreach($languages as $language)
820 $sortedLanguages[]=$language[1];
821 }
822 $this->_preferredLanguages=$sortedLanguages;
823 }
824 return $this->_preferredLanguages;
825 }
826
827 /**
828 * Returns the user preferred language.
829 * The returned language ID will be canonicalized using {@link CLocale::getCanonicalID}.
830 * @return string the user preferred language or false if the user does not have any.
831 */
832 public function getPreferredLanguage()
833 {
834 $preferredLanguages=$this->getPreferredLanguages();
835 return !empty($preferredLanguages) ? CLocale::getCanonicalID($preferredLanguages[0]) : false;
836 }
837
838 /**
839 * Sends a file to user.
840 * @param string $fileName file name
841 * @param string $content content to be set.
842 * @param string $mimeType mime type of the content. If null, it will be guessed automatically based on the given file name.
843 * @param boolean $terminate whether to terminate the current application after calling this method
844 */
845 public function sendFile($fileName,$content,$mimeType=null,$terminate=true)
846 {
847 if($mimeType===null)
848 {
849 if(($mimeType=CFileHelper::getMimeTypeByExtension($fileName))===null)
850 $mimeType='text/plain';
851 }
852 header('Pragma: public');
853 header('Expires: 0');
854 header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
855 header("Content-type: $mimeType");
856 header('Content-Length: '.(function_exists('mb_strlen') ? mb_strlen($content,'8bit') : strlen($content)));
857 header("Content-Disposition: attachment; filename=\"$fileName\"");
858 header('Content-Transfer-Encoding: binary');
859
860 if($terminate)
861 {
862 // clean up the application first because the file downloading could take long time
863 // which may cause timeout of some resources (such as DB connection)
864 ob_start();
865 Yii::app()->end(0,false);
866 ob_end_clean();
867 echo $content;
868 exit(0);
869 }
870 else
871 echo $content;
872 }
873
874 /**
875 * Sends existing file to a browser as a download using x-sendfile.
876 *
877 * X-Sendfile is a feature allowing a web application to redirect the request for a file to the webserver
878 * that in turn processes the request, this way eliminating the need to perform tasks like reading the file
879 * and sending it to the user. When dealing with a lot of files (or very big files) this can lead to a great
880 * increase in performance as the web application is allowed to terminate earlier while the webserver is
881 * handling the request.
882 *
883 * The request is sent to the server through a special non-standard HTTP-header.
884 * When the web server encounters the presence of such header it will discard all output and send the file
885 * specified by that header using web server internals including all optimizations like caching-headers.
886 *
887 * As this header directive is non-standard different directives exists for different web servers applications:
888 * <ul>
889 * <li>Apache: {@link http://tn123.org/mod_xsendfile X-Sendfile}</li>
890 * <li>Lighttpd v1.4: {@link http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file X-LIGHTTPD-send-file}</li>
891 * <li>Lighttpd v1.5: {@link http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file X-Sendfile}</li>
892 * <li>Nginx: {@link http://wiki.nginx.org/XSendfile X-Accel-Redirect}</li>
893 * <li>Cherokee: {@link http://www.cherokee-project.com/doc/other_goodies.html#x-sendfile X-Sendfile and X-Accel-Redirect}</li>
894 * </ul>
895 * So for this method to work the X-SENDFILE option/module should be enabled by the web server and
896 * a proper xHeader should be sent.
897 *
898 * <b>Note:</b>
899 * This option allows to download files that are not under web folders, and even files that are otherwise protected (deny from all) like .htaccess
900 *
901 * <b>Side effects</b>:
902 * If this option is disabled by the web server, when this method is called a download configuration dialog
903 * will open but the downloaded file will have 0 bytes.
904 *
905 * <b>Known issues</b>:
906 * There is a Bug with Internet Explorer 6, 7 and 8 when X-SENDFILE is used over an SSL connection, it will show
907 * an error message like this: "Internet Explorer was not able to open this Internet site. The requested site is either unavailable or cannot be found.".
908 * You can work around this problem by removing the <code>Pragma</code>-header.
909 *
910 * <b>Example</b>:
911 * <pre>
912 * <?php
913 * Yii::app()->request->xSendFile('/home/user/Pictures/picture1.jpg',array(
914 * 'saveName'=>'image1.jpg',
915 * 'mimeType'=>'image/jpeg',
916 * 'terminate'=>false,
917 * ));
918 * ?>
919 * </pre>
920 * @param string $filePath file name with full path
921 * @param array $options additional options:
922 * <ul>
923 * <li>saveName: file name shown to the user, if not set real file name will be used</li>
924 * <li>mimeType: mime type of the file, if not set it will be guessed automatically based on the file name, if set to null no content-type header will be sent.</li>
925 * <li>xHeader: appropriate x-sendfile header, defaults to "X-Sendfile"</li>
926 * <li>terminate: whether to terminate the current application after calling this method, defaults to true</li>
927 * <li>forceDownload: specifies whether the file will be downloaded or shown inline, defaults to true. (Since version 1.1.9.)</li>
928 * <li>addHeaders: an array of additional http headers in header-value pairs (available since version 1.1.10)</li>
929 * </ul>
930 */
931 public function xSendFile($filePath, $options=array())
932 {
933 if(!isset($options['forceDownload']) || $options['forceDownload'])
934 $disposition='attachment';
935 else
936 $disposition='inline';
937
938 if(!isset($options['saveName']))
939 $options['saveName']=basename($filePath);
940
941 if(!isset($options['mimeType']))
942 {
943 if(($options['mimeType']=CFileHelper::getMimeTypeByExtension($filePath))===null)
944 $options['mimeType']='text/plain';
945 }
946
947 if(!isset($options['xHeader']))
948 $options['xHeader']='X-Sendfile';
949
950 if($options['mimeType'] !== null)
951 header('Content-type: '.$options['mimeType']);
952 header('Content-Disposition: '.$disposition.'; filename="'.$options['saveName'].'"');
953 if(isset($options['addHeaders']))
954 {
955 foreach($options['addHeaders'] as $header=>$value)
956 header($header.': '.$value);
957 }
958 header(trim($options['xHeader']).': '.$filePath);
959
960 if(!isset($options['terminate']) || $options['terminate'])
961 Yii::app()->end();
962 }
963
964 /**
965 * Returns the random token used to perform CSRF validation.
966 * The token will be read from cookie first. If not found, a new token
967 * will be generated.
968 * @return string the random token for CSRF validation.
969 * @see enableCsrfValidation
970 */
971 public function getCsrfToken()
972 {
973 if($this->_csrfToken===null)
974 {
975 $cookie=$this->getCookies()->itemAt($this->csrfTokenName);
976 if(!$cookie || ($this->_csrfToken=$cookie->value)==null)
977 {
978 $cookie=$this->createCsrfCookie();
979 $this->_csrfToken=$cookie->value;
980 $this->getCookies()->add($cookie->name,$cookie);
981 }
982 }
983
984 return $this->_csrfToken;
985 }
986
987 /**
988 * Creates a cookie with a randomly generated CSRF token.
989 * Initial values specified in {@link csrfCookie} will be applied
990 * to the generated cookie.
991 * @return CHttpCookie the generated cookie
992 * @see enableCsrfValidation
993 */
994 protected function createCsrfCookie()
995 {
996 $cookie=new CHttpCookie($this->csrfTokenName,sha1(uniqid(mt_rand(),true)));
997 if(is_array($this->csrfCookie))
998 {
999 foreach($this->csrfCookie as $name=>$value)
1000 $cookie->$name=$value;
1001 }
1002 return $cookie;
1003 }
1004
1005 /**
1006 * Performs the CSRF validation.
1007 * This is the event handler responding to {@link CApplication::onBeginRequest}.
1008 * The default implementation will compare the CSRF token obtained
1009 * from a cookie and from a POST field. If they are different, a CSRF attack is detected.
1010 * @param CEvent $event event parameter
1011 * @throws CHttpException if the validation fails
1012 */
1013 public function validateCsrfToken($event)
1014 {
1015 if ($this->getIsPostRequest() ||
1016 $this->getIsPutRequest() ||
1017 $this->getIsDeleteRequest())
1018 {
1019 $cookies=$this->getCookies();
1020
1021 $method=$this->getRequestType();
1022 switch($method)
1023 {
1024 case 'POST':
1025 $userToken=$this->getPost($this->csrfTokenName);
1026 break;
1027 case 'PUT':
1028 $userToken=$this->getPut($this->csrfTokenName);
1029 break;
1030 case 'DELETE':
1031 $userToken=$this->getDelete($this->csrfTokenName);
1032 }
1033
1034 if (!empty($userToken) && $cookies->contains($this->csrfTokenName))
1035 {
1036 $cookieToken=$cookies->itemAt($this->csrfTokenName)->value;
1037 $valid=$cookieToken===$userToken;
1038 }
1039 else
1040 $valid = false;
1041 if (!$valid)
1042 throw new CHttpException(400,Yii::t('yii','The CSRF token could not be verified.'));
1043 }
1044 }
1045}
1046
1047
1048/**
1049 * CCookieCollection implements a collection class to store cookies.
1050 *
1051 * You normally access it via {@link CHttpRequest::getCookies()}.
1052 *
1053 * Since CCookieCollection extends from {@link CMap}, it can be used
1054 * like an associative array as follows:
1055 * <pre>
1056 * $cookies[$name]=new CHttpCookie($name,$value); // sends a cookie
1057 * $value=$cookies[$name]->value; // reads a cookie value
1058 * unset($cookies[$name]); // removes a cookie
1059 * </pre>
1060 *
1061 * @author Qiang Xue <qiang.xue@gmail.com>
1062 * @package system.web
1063 * @since 1.0
1064 */
1065class CCookieCollection extends CMap
1066{
1067 private $_request;
1068 private $_initialized=false;
1069
1070 /**
1071 * Constructor.
1072 * @param CHttpRequest $request owner of this collection.
1073 */
1074 public function __construct(CHttpRequest $request)
1075 {
1076 $this->_request=$request;
1077 $this->copyfrom($this->getCookies());
1078 $this->_initialized=true;
1079 }
1080
1081 /**
1082 * @return CHttpRequest the request instance
1083 */
1084 public function getRequest()
1085 {
1086 return $this->_request;
1087 }
1088
1089 /**
1090 * @return array list of validated cookies
1091 */
1092 protected function getCookies()
1093 {
1094 $cookies=array();
1095 if($this->_request->enableCookieValidation)
1096 {
1097 $sm=Yii::app()->getSecurityManager();
1098 foreach($_COOKIE as $name=>$value)
1099 {
1100 if(is_string($value) && ($value=$sm->validateData($value))!==false)
1101 $cookies[$name]=new CHttpCookie($name,@unserialize($value));
1102 }
1103 }
1104 else
1105 {
1106 foreach($_COOKIE as $name=>$value)
1107 $cookies[$name]=new CHttpCookie($name,$value);
1108 }
1109 return $cookies;
1110 }
1111
1112 /**
1113 * Adds a cookie with the specified name.
1114 * This overrides the parent implementation by performing additional
1115 * operations for each newly added CHttpCookie object.
1116 * @param mixed $name Cookie name.
1117 * @param CHttpCookie $cookie Cookie object.
1118 * @throws CException if the item to be inserted is not a CHttpCookie object.
1119 */
1120 public function add($name,$cookie)
1121 {
1122 if($cookie instanceof CHttpCookie)
1123 {
1124 $this->remove($name);
1125 parent::add($name,$cookie);
1126 if($this->_initialized)
1127 $this->addCookie($cookie);
1128 }
1129 else
1130 throw new CException(Yii::t('yii','CHttpCookieCollection can only hold CHttpCookie objects.'));
1131 }
1132
1133 /**
1134 * Removes a cookie with the specified name.
1135 * This overrides the parent implementation by performing additional
1136 * cleanup work when removing a CHttpCookie object.
1137 * Since version 1.1.11, the second parameter is available that can be used to specify
1138 * the options of the CHttpCookie being removed. For example, this may be useful when dealing
1139 * with ".domain.tld" where multiple subdomains are expected to be able to manage cookies:
1140 *
1141 * <pre>
1142 * $options=array('domain'=>'.domain.tld');
1143 * Yii::app()->request->cookies['foo']=new CHttpCookie('cookie','value',$options);
1144 * Yii::app()->request->cookies->remove('cookie',$options);
1145 * </pre>
1146 *
1147 * @param mixed $name Cookie name.
1148 * @param array $options Cookie configuration array consisting of name-value pairs, available since 1.1.11.
1149 * @return CHttpCookie The removed cookie object.
1150 */
1151 public function remove($name,$options=array())
1152 {
1153 if(($cookie=parent::remove($name))!==null)
1154 {
1155 if($this->_initialized)
1156 {
1157 $cookie->configure($options);
1158 $this->removeCookie($cookie);
1159 }
1160 }
1161
1162 return $cookie;
1163 }
1164
1165 /**
1166 * Sends a cookie.
1167 * @param CHttpCookie $cookie cookie to be sent
1168 */
1169 protected function addCookie($cookie)
1170 {
1171 $value=$cookie->value;
1172 if($this->_request->enableCookieValidation)
1173 $value=Yii::app()->getSecurityManager()->hashData(serialize($value));
1174 if(version_compare(PHP_VERSION,'5.2.0','>='))
1175 setcookie($cookie->name,$value,$cookie->expire,$cookie->path,$cookie->domain,$cookie->secure,$cookie->httpOnly);
1176 else
1177 setcookie($cookie->name,$value,$cookie->expire,$cookie->path,$cookie->domain,$cookie->secure);
1178 }
1179
1180 /**
1181 * Deletes a cookie.
1182 * @param CHttpCookie $cookie cookie to be deleted
1183 */
1184 protected function removeCookie($cookie)
1185 {
1186 if(version_compare(PHP_VERSION,'5.2.0','>='))
1187 setcookie($cookie->name,'',0,$cookie->path,$cookie->domain,$cookie->secure,$cookie->httpOnly);
1188 else
1189 setcookie($cookie->name,'',0,$cookie->path,$cookie->domain,$cookie->secure);
1190 }
1191}