PageRenderTime 3ms CodeModel.GetById 35ms app.highlight 10ms RepoModel.GetById 0ms app.codeStats 0ms

/yii/framework/web/CHttpRequest.php

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