"Fossies" - the Fresh Open Source Software Archive

Member "yii-1.1.22.bf1d26/framework/web/CHttpRequest.php" (16 Jan 2020, 54479 Bytes) of package /linux/www/yii-1.1.22.bf1d26.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "CHttpRequest.php" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.1.21.733ac5_vs_1.1.22.bf1d26.

    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 2008-2013 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, PATCH, 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 $isPatchRequest Whether this is a PATCH request.
   40  * @property boolean $isAjaxRequest Whether this is an AJAX (XMLHttpRequest) request.
   41  * @property boolean $isFlashRequest Whether this is an Adobe Flash or Adobe Flex request.
   42  * @property string $serverName Server name.
   43  * @property integer $serverPort Server port number.
   44  * @property string $urlReferrer URL referrer, null if not present.
   45  * @property string $userAgent User agent, null if not present.
   46  * @property string $userHostAddress User IP address.
   47  * @property string $userHost User host name, null if cannot be determined.
   48  * @property string $scriptFile Entry script file path (processed w/ realpath()).
   49  * @property array $browser User browser capabilities.
   50  * @property string $acceptTypes User browser accept types, null if not present.
   51  * @property integer $port Port number for insecure requests.
   52  * @property integer $securePort Port number for secure requests.
   53  * @property CCookieCollection|CHttpCookie[] $cookies The cookie collection.
   54  * @property array $preferredAcceptType The user preferred accept type as an array map, e.g. array('type' => 'application', 'subType' => 'xhtml', 'baseType' => 'xml', 'params' => array('q' => 0.9)).
   55  * @property array $preferredAcceptTypes An array of all user accepted types (as array maps like array('type' => 'application', 'subType' => 'xhtml', 'baseType' => 'xml', 'params' => array('q' => 0.9)) ) in order of preference.
   56  * @property string $preferredLanguage The user preferred language.
   57  * @property array $preferredLanguages An array of all user accepted languages in order of preference.
   58  * @property string $csrfToken The random token for CSRF validation.
   59  *
   60  * @author Qiang Xue <qiang.xue@gmail.com>
   61  * @package system.web
   62  * @since 1.0
   63  */
   64 class CHttpRequest extends CApplicationComponent
   65 {
   66     /**
   67      * @var boolean whether the parsing of JSON REST requests should return associative arrays for object data.
   68      * @see getRestParams
   69      * @since 1.1.17
   70      */
   71     public $jsonAsArray = true;
   72     /**
   73      * @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to false.
   74      */
   75     public $enableCookieValidation=false;
   76     /**
   77      * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to false.
   78      * By setting this property to true, forms submitted to an Yii Web application must be originated
   79      * from the same application. If not, a 400 HTTP exception will be raised.
   80      * Note, this feature requires that the user client accepts cookie.
   81      * You also need to use {@link CHtml::form} or {@link CHtml::statefulForm} to generate
   82      * the needed HTML forms in your pages.
   83      * @see http://seclab.stanford.edu/websec/csrf/csrf.pdf
   84      */
   85     public $enableCsrfValidation=false;
   86     /**
   87      * @var string the name of the token used to prevent CSRF. Defaults to 'YII_CSRF_TOKEN'.
   88      * This property is effectively only when {@link enableCsrfValidation} is true.
   89      */
   90     public $csrfTokenName='YII_CSRF_TOKEN';
   91     /**
   92      * @var array the property values (in name-value pairs) used to initialize the CSRF cookie.
   93      * Any property of {@link CHttpCookie} may be initialized.
   94      * This property is effective only when {@link enableCsrfValidation} is true.
   95      */
   96     public $csrfCookie;
   97 
   98     private $_requestUri;
   99     private $_pathInfo;
  100     private $_scriptFile;
  101     private $_scriptUrl;
  102     private $_hostInfo;
  103     private $_baseUrl;
  104     private $_cookies;
  105     private $_preferredAcceptTypes;
  106     private $_preferredLanguages;
  107     private $_csrfToken;
  108     private $_restParams;
  109     private $_httpVersion;
  110 
  111     /**
  112      * Initializes the application component.
  113      * This method overrides the parent implementation by preprocessing
  114      * the user request data.
  115      */
  116     public function init()
  117     {
  118         parent::init();
  119         $this->normalizeRequest();
  120     }
  121 
  122     /**
  123      * Normalizes the request data.
  124      * This method strips off slashes in request data if get_magic_quotes_gpc() returns true.
  125      * It also performs CSRF validation if {@link enableCsrfValidation} is true.
  126      */
  127     protected function normalizeRequest()
  128     {
  129         // normalize request
  130         if(version_compare(PHP_VERSION,'7.4.0','<'))
  131         {
  132             if(function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc())
  133             {
  134                 if(isset($_GET))
  135                     $_GET=$this->stripSlashes($_GET);
  136                 if(isset($_POST))
  137                     $_POST=$this->stripSlashes($_POST);
  138                 if(isset($_REQUEST))
  139                     $_REQUEST=$this->stripSlashes($_REQUEST);
  140                 if(isset($_COOKIE))
  141                     $_COOKIE=$this->stripSlashes($_COOKIE);
  142             }
  143         }
  144 
  145         if($this->enableCsrfValidation)
  146             Yii::app()->attachEventHandler('onBeginRequest',array($this,'validateCsrfToken'));
  147     }
  148 
  149 
  150     /**
  151      * Strips slashes from input data.
  152      * This method is applied when magic quotes is enabled.
  153      * @param mixed $data input data to be processed
  154      * @return mixed processed data
  155      */
  156     public function stripSlashes(&$data)
  157     {
  158         if(is_array($data))
  159         {
  160             if(count($data) == 0)
  161                 return $data;
  162             $keys=array_map('stripslashes',array_keys($data));
  163             $data=array_combine($keys,array_values($data));
  164             return array_map(array($this,'stripSlashes'),$data);
  165         }
  166         else
  167             return stripslashes($data);
  168     }
  169 
  170     /**
  171      * Returns the named GET or POST parameter value.
  172      * If the GET or POST parameter does not exist, the second parameter to this method will be returned.
  173      * If both GET and POST contains such a named parameter, the GET parameter takes precedence.
  174      * @param string $name the GET parameter name
  175      * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
  176      * @return mixed the GET parameter value
  177      * @see getQuery
  178      * @see getPost
  179      */
  180     public function getParam($name,$defaultValue=null)
  181     {
  182         return isset($_GET[$name]) ? $_GET[$name] : (isset($_POST[$name]) ? $_POST[$name] : $defaultValue);
  183     }
  184 
  185     /**
  186      * Returns the named GET parameter value.
  187      * If the GET parameter does not exist, the second parameter to this method will be returned.
  188      * @param string $name the GET parameter name
  189      * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
  190      * @return mixed the GET parameter value
  191      * @see getPost
  192      * @see getParam
  193      */
  194     public function getQuery($name,$defaultValue=null)
  195     {
  196         return isset($_GET[$name]) ? $_GET[$name] : $defaultValue;
  197     }
  198 
  199     /**
  200      * Returns the named POST parameter value.
  201      * If the POST parameter does not exist, the second parameter to this method will be returned.
  202      * @param string $name the POST parameter name
  203      * @param mixed $defaultValue the default parameter value if the POST parameter does not exist.
  204      * @return mixed the POST parameter value
  205      * @see getParam
  206      * @see getQuery
  207      */
  208     public function getPost($name,$defaultValue=null)
  209     {
  210         return isset($_POST[$name]) ? $_POST[$name] : $defaultValue;
  211     }
  212 
  213     /**
  214      * Returns the named DELETE parameter value.
  215      * If the DELETE parameter does not exist or if the current request is not a DELETE request,
  216      * the second parameter to this method will be returned.
  217      * If the DELETE request was tunneled through POST via _method parameter, the POST parameter
  218      * will be returned instead (available since version 1.1.11).
  219      * @param string $name the DELETE parameter name
  220      * @param mixed $defaultValue the default parameter value if the DELETE parameter does not exist.
  221      * @return mixed the DELETE parameter value
  222      * @since 1.1.7
  223      */
  224     public function getDelete($name,$defaultValue=null)
  225     {
  226         if($this->getIsDeleteViaPostRequest())
  227             return $this->getPost($name, $defaultValue);
  228 
  229         if($this->getIsDeleteRequest())
  230         {
  231             $restParams=$this->getRestParams();
  232             return isset($restParams[$name]) ? $restParams[$name] : $defaultValue;
  233         }
  234         else
  235             return $defaultValue;
  236     }
  237 
  238     /**
  239      * Returns the named PUT parameter value.
  240      * If the PUT parameter does not exist or if the current request is not a PUT request,
  241      * the second parameter to this method will be returned.
  242      * If the PUT request was tunneled through POST via _method parameter, the POST parameter
  243      * will be returned instead (available since version 1.1.11).
  244      * @param string $name the PUT parameter name
  245      * @param mixed $defaultValue the default parameter value if the PUT parameter does not exist.
  246      * @return mixed the PUT parameter value
  247      * @since 1.1.7
  248      */
  249     public function getPut($name,$defaultValue=null)
  250     {
  251         if($this->getIsPutViaPostRequest())
  252             return $this->getPost($name, $defaultValue);
  253 
  254         if($this->getIsPutRequest())
  255         {
  256             $restParams=$this->getRestParams();
  257             return isset($restParams[$name]) ? $restParams[$name] : $defaultValue;
  258         }
  259         else
  260             return $defaultValue;
  261     }
  262 
  263     /**
  264      * Returns the named PATCH parameter value.
  265      * If the PATCH parameter does not exist or if the current request is not a PATCH request,
  266      * the second parameter to this method will be returned.
  267      * If the PATCH request was tunneled through POST via _method parameter, the POST parameter
  268      * will be returned instead.
  269      * @param string $name the PATCH parameter name
  270      * @param mixed $defaultValue the default parameter value if the PATCH parameter does not exist.
  271      * @return mixed the PATCH parameter value
  272      * @since 1.1.16
  273      */
  274     public function getPatch($name,$defaultValue=null)
  275     {
  276         if($this->getIsPatchViaPostRequest())
  277             return $this->getPost($name, $defaultValue);
  278 
  279         if($this->getIsPatchRequest())
  280         {
  281             $restParams=$this->getRestParams();
  282             return isset($restParams[$name]) ? $restParams[$name] : $defaultValue;
  283         }
  284         else
  285             return $defaultValue;
  286     }
  287 
  288     /**
  289      * Returns request parameters. Typically PUT, PATCH or DELETE.
  290      * @return array the request parameters
  291      * @since 1.1.7
  292      * @since 1.1.13 method became public
  293      */
  294     public function getRestParams()
  295     {
  296         if($this->_restParams===null)
  297         {
  298             $result=array();
  299             if (strncmp($this->getContentType(), 'application/json', 16) === 0)
  300                 $result = CJSON::decode($this->getRawBody(), $this->jsonAsArray);
  301             elseif(function_exists('mb_parse_str'))
  302                 mb_parse_str($this->getRawBody(), $result);
  303             else
  304                 parse_str($this->getRawBody(), $result);
  305             $this->_restParams=$result;
  306         }
  307 
  308         return $this->_restParams;
  309     }
  310 
  311     /**
  312      * Returns the raw HTTP request body.
  313      * @return string the request body
  314      * @since 1.1.13
  315      */
  316     public function getRawBody()
  317     {
  318         static $rawBody;
  319         if($rawBody===null)
  320             $rawBody=file_get_contents('php://input');
  321         return $rawBody;
  322     }
  323 
  324     /**
  325      * Returns the currently requested URL.
  326      * This is the same as {@link getRequestUri}.
  327      * @return string part of the request URL after the host info.
  328      */
  329     public function getUrl()
  330     {
  331         return $this->getRequestUri();
  332     }
  333 
  334     /**
  335      * Returns the schema and host part of the application URL.
  336      * The returned URL does not have an ending slash.
  337      * By default this is determined based on the user request information.
  338      * You may explicitly specify it by setting the {@link setHostInfo hostInfo} property.
  339      * @param string $schema schema to use (e.g. http, https). If empty, the schema used for the current request will be used.
  340      * @return string schema and hostname part (with port number if needed) of the request URL (e.g. http://www.yiiframework.com)
  341      * @see setHostInfo
  342      */
  343     public function getHostInfo($schema='')
  344     {
  345         if($this->_hostInfo===null)
  346         {
  347             if($secure=$this->getIsSecureConnection())
  348                 $http='https';
  349             else
  350                 $http='http';
  351             if(isset($_SERVER['HTTP_HOST']))
  352                 $this->_hostInfo=$http.'://'.$_SERVER['HTTP_HOST'];
  353             else
  354             {
  355                 $this->_hostInfo=$http.'://'.$_SERVER['SERVER_NAME'];
  356                 $port=$secure ? $this->getSecurePort() : $this->getPort();
  357                 if(($port!==80 && !$secure) || ($port!==443 && $secure))
  358                     $this->_hostInfo.=':'.$port;
  359             }
  360         }
  361         if($schema!=='')
  362         {
  363             $secure=$this->getIsSecureConnection();
  364             if($secure && $schema==='https' || !$secure && $schema==='http')
  365                 return $this->_hostInfo;
  366 
  367             $port=$schema==='https' ? $this->getSecurePort() : $this->getPort();
  368             if($port!==80 && $schema==='http' || $port!==443 && $schema==='https')
  369                 $port=':'.$port;
  370             else
  371                 $port='';
  372 
  373             $pos=strpos($this->_hostInfo,':');
  374             return $schema.substr($this->_hostInfo,$pos,strcspn($this->_hostInfo,':',$pos+1)+1).$port;
  375         }
  376         else
  377             return $this->_hostInfo;
  378     }
  379 
  380     /**
  381      * Sets the schema and host part of the application URL.
  382      * This setter is provided in case the schema and hostname cannot be determined
  383      * on certain Web servers.
  384      * @param string $value the schema and host part of the application URL.
  385      */
  386     public function setHostInfo($value)
  387     {
  388         $this->_hostInfo=rtrim($value,'/');
  389     }
  390 
  391     /**
  392      * Returns the relative URL for the application.
  393      * This is similar to {@link getScriptUrl scriptUrl} except that
  394      * it does not have the script file name, and the ending slashes are stripped off.
  395      * @param boolean $absolute whether to return an absolute URL. Defaults to false, meaning returning a relative one.
  396      * @return string the relative URL for the application
  397      * @see setScriptUrl
  398      */
  399     public function getBaseUrl($absolute=false)
  400     {
  401         if($this->_baseUrl===null)
  402             $this->_baseUrl=rtrim(dirname($this->getScriptUrl()),'\\/');
  403         return $absolute ? $this->getHostInfo() . $this->_baseUrl : $this->_baseUrl;
  404     }
  405 
  406     /**
  407      * Sets the relative URL for the application.
  408      * By default the URL is determined based on the entry script URL.
  409      * This setter is provided in case you want to change this behavior.
  410      * @param string $value the relative URL for the application
  411      */
  412     public function setBaseUrl($value)
  413     {
  414         $this->_baseUrl=$value;
  415     }
  416 
  417     /**
  418      * Returns the relative URL of the entry script.
  419      * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
  420      * @throws CException when it is unable to determine the entry script URL.
  421      * @return string the relative URL of the entry script.
  422      */
  423     public function getScriptUrl()
  424     {
  425         if($this->_scriptUrl===null)
  426         {
  427             $scriptName=basename($_SERVER['SCRIPT_FILENAME']);
  428             if(basename($_SERVER['SCRIPT_NAME'])===$scriptName)
  429                 $this->_scriptUrl=$_SERVER['SCRIPT_NAME'];
  430             elseif(basename($_SERVER['PHP_SELF'])===$scriptName)
  431                 $this->_scriptUrl=$_SERVER['PHP_SELF'];
  432             elseif(isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME'])===$scriptName)
  433                 $this->_scriptUrl=$_SERVER['ORIG_SCRIPT_NAME'];
  434             elseif(($pos=strpos($_SERVER['PHP_SELF'],'/'.$scriptName))!==false)
  435                 $this->_scriptUrl=substr($_SERVER['SCRIPT_NAME'],0,$pos).'/'.$scriptName;
  436             elseif(isset($_SERVER['DOCUMENT_ROOT']) && strpos($_SERVER['SCRIPT_FILENAME'],$_SERVER['DOCUMENT_ROOT'])===0)
  437                 $this->_scriptUrl=str_replace('\\','/',str_replace($_SERVER['DOCUMENT_ROOT'],'',$_SERVER['SCRIPT_FILENAME']));
  438             else
  439                 throw new CException(Yii::t('yii','CHttpRequest is unable to determine the entry script URL.'));
  440         }
  441         return $this->_scriptUrl;
  442     }
  443 
  444     /**
  445      * Sets the relative URL for the application entry script.
  446      * This setter is provided in case the entry script URL cannot be determined
  447      * on certain Web servers.
  448      * @param string $value the relative URL for the application entry script.
  449      */
  450     public function setScriptUrl($value)
  451     {
  452         $this->_scriptUrl='/'.trim($value,'/');
  453     }
  454 
  455     /**
  456      * Returns the path info of the currently requested URL.
  457      * This refers to the part that is after the entry script and before the question mark.
  458      * The starting and ending slashes are stripped off.
  459      * @return string part of the request URL that is after the entry script and before the question mark.
  460      * Note, the returned pathinfo is decoded starting from 1.1.4.
  461      * Prior to 1.1.4, whether it is decoded or not depends on the server configuration
  462      * (in most cases it is not decoded).
  463      * @throws CException if the request URI cannot be determined due to improper server configuration
  464      */
  465     public function getPathInfo()
  466     {
  467         if($this->_pathInfo===null)
  468         {
  469             $pathInfo=$this->getRequestUri();
  470 
  471             if(($pos=strpos($pathInfo,'?'))!==false)
  472                $pathInfo=substr($pathInfo,0,$pos);
  473 
  474             $pathInfo=$this->decodePathInfo($pathInfo);
  475 
  476             $scriptUrl=$this->getScriptUrl();
  477             $baseUrl=$this->getBaseUrl();
  478             if(strpos($pathInfo,$scriptUrl)===0)
  479                 $pathInfo=substr($pathInfo,strlen($scriptUrl));
  480             elseif($baseUrl==='' || strpos($pathInfo,$baseUrl)===0)
  481                 $pathInfo=substr($pathInfo,strlen($baseUrl));
  482             elseif(strpos($_SERVER['PHP_SELF'],$scriptUrl)===0)
  483                 $pathInfo=substr($_SERVER['PHP_SELF'],strlen($scriptUrl));
  484             else
  485                 throw new CException(Yii::t('yii','CHttpRequest is unable to determine the path info of the request.'));
  486 
  487             if($pathInfo==='/' || $pathInfo===false)
  488                 $pathInfo='';
  489             elseif($pathInfo!=='' && $pathInfo[0]==='/')
  490                 $pathInfo=substr($pathInfo,1);
  491 
  492             if(($posEnd=strlen($pathInfo)-1)>0 && $pathInfo[$posEnd]==='/')
  493                 $pathInfo=substr($pathInfo,0,$posEnd);
  494 
  495             $this->_pathInfo=$pathInfo;
  496         }
  497         return $this->_pathInfo;
  498     }
  499 
  500     /**
  501      * Decodes the path info.
  502      * This method is an improved variant of the native urldecode() function and used in {@link getPathInfo getPathInfo()} to
  503      * decode the path part of the request URI. You may override this method to change the way the path info is being decoded.
  504      * @param string $pathInfo encoded path info
  505      * @return string decoded path info
  506      * @since 1.1.10
  507      */
  508     protected function decodePathInfo($pathInfo)
  509     {
  510         $pathInfo = urldecode($pathInfo);
  511 
  512         // is it UTF-8?
  513         // http://w3.org/International/questions/qa-forms-utf-8.html
  514         if(preg_match('%^(?:
  515            [\x09\x0A\x0D\x20-\x7E]            # ASCII
  516          | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
  517          | \xE0[\xA0-\xBF][\x80-\xBF]         # excluding overlongs
  518          | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
  519          | \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates
  520          | \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
  521          | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
  522          | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
  523         )*$%xs', $pathInfo))
  524         {
  525             return $pathInfo;
  526         }
  527         else
  528         {
  529             return utf8_encode($pathInfo);
  530         }
  531     }
  532 
  533     /**
  534      * Returns the request URI portion for the currently requested URL.
  535      * This refers to the portion that is after the {@link hostInfo host info} part.
  536      * It includes the {@link queryString query string} part if any.
  537      * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
  538      * @return string the request URI portion for the currently requested URL.
  539      * @throws CException if the request URI cannot be determined due to improper server configuration
  540      */
  541     public function getRequestUri()
  542     {
  543         if($this->_requestUri===null)
  544         {
  545             if(isset($_SERVER['REQUEST_URI']))
  546             {
  547                 $this->_requestUri=$_SERVER['REQUEST_URI'];
  548                 if(!empty($_SERVER['HTTP_HOST']))
  549                 {
  550                     if(strpos($this->_requestUri,$_SERVER['HTTP_HOST'])!==false)
  551                         $this->_requestUri=preg_replace('/^\w+:\/\/[^\/]+/','',$this->_requestUri);
  552                 }
  553                 else
  554                     $this->_requestUri=preg_replace('/^(http|https):\/\/[^\/]+/i','',$this->_requestUri);
  555             }
  556             elseif(isset($_SERVER['ORIG_PATH_INFO']))  // IIS 5.0 CGI
  557             {
  558                 $this->_requestUri=$_SERVER['ORIG_PATH_INFO'];
  559                 if(!empty($_SERVER['QUERY_STRING']))
  560                     $this->_requestUri.='?'.$_SERVER['QUERY_STRING'];
  561             }
  562             else
  563                 throw new CException(Yii::t('yii','CHttpRequest is unable to determine the request URI.'));
  564         }
  565 
  566         return $this->_requestUri;
  567     }
  568 
  569     /**
  570      * Returns part of the request URL that is after the question mark.
  571      * @return string part of the request URL that is after the question mark
  572      */
  573     public function getQueryString()
  574     {
  575         return isset($_SERVER['QUERY_STRING'])?$_SERVER['QUERY_STRING']:'';
  576     }
  577 
  578     /**
  579      * Return if the request is sent via secure channel (https).
  580      * @return boolean if the request is sent via secure channel (https)
  581      */
  582     public function getIsSecureConnection()
  583     {
  584         return isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'],'on')===0 || $_SERVER['HTTPS']==1)
  585             || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'],'https')===0;
  586     }
  587 
  588     /**
  589      * Returns the request type, such as GET, POST, HEAD, PUT, PATCH, DELETE.
  590      * Request type can be manually set in POST requests with a parameter named _method. Useful
  591      * for RESTful request from older browsers which do not support PUT, PATCH or DELETE
  592      * natively (available since version 1.1.11).
  593      * @return string request type, such as GET, POST, HEAD, PUT, PATCH, DELETE.
  594      */
  595     public function getRequestType()
  596     {
  597         if(isset($_POST['_method']))
  598             return strtoupper($_POST['_method']);
  599         elseif(isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']))
  600             return strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
  601 
  602         return strtoupper(isset($_SERVER['REQUEST_METHOD'])?$_SERVER['REQUEST_METHOD']:'GET');
  603     }
  604 
  605     /**
  606      * Returns whether this is a POST request.
  607      * @return boolean whether this is a POST request.
  608      */
  609     public function getIsPostRequest()
  610     {
  611         return isset($_SERVER['REQUEST_METHOD']) && !strcasecmp($_SERVER['REQUEST_METHOD'],'POST');
  612     }
  613 
  614     /**
  615      * Returns whether this is a DELETE request.
  616      * @return boolean whether this is a DELETE request.
  617      * @since 1.1.7
  618      */
  619     public function getIsDeleteRequest()
  620     {
  621         return (isset($_SERVER['REQUEST_METHOD']) && !strcasecmp($_SERVER['REQUEST_METHOD'],'DELETE')) || $this->getIsDeleteViaPostRequest();
  622     }
  623 
  624     /**
  625      * Returns whether this is a DELETE request which was tunneled through POST.
  626      * @return boolean whether this is a DELETE request tunneled through POST.
  627      * @since 1.1.11
  628      */
  629     protected function getIsDeleteViaPostRequest()
  630     {
  631         return isset($_POST['_method']) && !strcasecmp($_POST['_method'],'DELETE');
  632     }
  633 
  634     /**
  635      * Returns whether this is a PUT request.
  636      * @return boolean whether this is a PUT request.
  637      * @since 1.1.7
  638      */
  639     public function getIsPutRequest()
  640     {
  641         return (isset($_SERVER['REQUEST_METHOD']) && !strcasecmp($_SERVER['REQUEST_METHOD'],'PUT')) || $this->getIsPutViaPostRequest();
  642     }
  643 
  644     /**
  645      * Returns whether this is a PUT request which was tunneled through POST.
  646      * @return boolean whether this is a PUT request tunneled through POST.
  647      * @since 1.1.11
  648      */
  649     protected function getIsPutViaPostRequest()
  650     {
  651         return isset($_POST['_method']) && !strcasecmp($_POST['_method'],'PUT');
  652     }
  653 
  654     /**
  655      * Returns whether this is a PATCH request.
  656      * @return boolean whether this is a PATCH request.
  657      * @since 1.1.16
  658      */
  659     public function getIsPatchRequest()
  660     {
  661         return (isset($_SERVER['REQUEST_METHOD']) && !strcasecmp($_SERVER['REQUEST_METHOD'],'PATCH')) || $this->getIsPatchViaPostRequest();
  662     }
  663 
  664     /**
  665      * Returns whether this is a PATCH request which was tunneled through POST.
  666      * @return boolean whether this is a PATCH request tunneled through POST.
  667      * @since 1.1.16
  668      */
  669     protected function getIsPatchViaPostRequest()
  670     {
  671         return isset($_POST['_method']) && !strcasecmp($_POST['_method'],'PATCH');
  672     }
  673 
  674     /**
  675      * Returns whether this is an AJAX (XMLHttpRequest) request.
  676      * @return boolean whether this is an AJAX (XMLHttpRequest) request.
  677      */
  678     public function getIsAjaxRequest()
  679     {
  680         return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']==='XMLHttpRequest';
  681     }
  682 
  683     /**
  684      * Returns whether this is an Adobe Flash or Adobe Flex request.
  685      * @return boolean whether this is an Adobe Flash or Adobe Flex request.
  686      * @since 1.1.11
  687      */
  688     public function getIsFlashRequest()
  689     {
  690         return isset($_SERVER['HTTP_USER_AGENT']) && (stripos($_SERVER['HTTP_USER_AGENT'],'Shockwave')!==false || stripos($_SERVER['HTTP_USER_AGENT'],'Flash')!==false);
  691     }
  692 
  693     /**
  694      * Returns the server name.
  695      * @return string server name
  696      */
  697     public function getServerName()
  698     {
  699         return $_SERVER['SERVER_NAME'];
  700     }
  701 
  702     /**
  703      * Returns the server port number.
  704      * @return integer server port number
  705      */
  706     public function getServerPort()
  707     {
  708         return $_SERVER['SERVER_PORT'];
  709     }
  710 
  711     /**
  712      * Returns the URL referrer, null if not present
  713      * @return string URL referrer, null if not present
  714      */
  715     public function getUrlReferrer()
  716     {
  717         return isset($_SERVER['HTTP_REFERER'])?$_SERVER['HTTP_REFERER']:null;
  718     }
  719 
  720     /**
  721      * Returns the user agent, null if not present.
  722      * @return string user agent, null if not present
  723      */
  724     public function getUserAgent()
  725     {
  726         return isset($_SERVER['HTTP_USER_AGENT'])?$_SERVER['HTTP_USER_AGENT']:null;
  727     }
  728 
  729     /**
  730      * Returns the user IP address.
  731      * @return string user IP address
  732      */
  733     public function getUserHostAddress()
  734     {
  735         return isset($_SERVER['REMOTE_ADDR'])?$_SERVER['REMOTE_ADDR']:'127.0.0.1';
  736     }
  737 
  738     /**
  739      * Returns the user host name, null if it cannot be determined.
  740      * @return string user host name, null if cannot be determined
  741      */
  742     public function getUserHost()
  743     {
  744         return isset($_SERVER['REMOTE_HOST'])?$_SERVER['REMOTE_HOST']:null;
  745     }
  746 
  747     /**
  748      * Returns entry script file path.
  749      * @return string entry script file path (processed w/ realpath())
  750      */
  751     public function getScriptFile()
  752     {
  753         if($this->_scriptFile!==null)
  754             return $this->_scriptFile;
  755         else
  756             return $this->_scriptFile=realpath($_SERVER['SCRIPT_FILENAME']);
  757     }
  758 
  759     /**
  760      * Returns information about the capabilities of user browser.
  761      * @param string $userAgent the user agent to be analyzed. Defaults to null, meaning using the
  762      * current User-Agent HTTP header information.
  763      * @return array user browser capabilities.
  764      * @see http://www.php.net/manual/en/function.get-browser.php
  765      */
  766     public function getBrowser($userAgent=null)
  767     {
  768         return get_browser($userAgent,true);
  769     }
  770 
  771     /**
  772      * Returns user browser accept types, null if not present.
  773      * @return string user browser accept types, null if not present
  774      */
  775     public function getAcceptTypes()
  776     {
  777         return isset($_SERVER['HTTP_ACCEPT'])?$_SERVER['HTTP_ACCEPT']:null;
  778     }
  779     
  780     /**
  781      * Returns request content-type
  782      * The Content-Type header field indicates the MIME type of the data
  783      * contained in {@link getRawBody()} or, in the case of the HEAD method, the
  784      * media type that would have been sent had the request been a GET.
  785      * @return string request content-type. Null is returned if this information is not available.
  786      * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
  787      * HTTP 1.1 header field definitions
  788      * @since 1.1.17
  789      */
  790     public function getContentType()
  791     {
  792         if (isset($_SERVER["CONTENT_TYPE"])) {
  793             return $_SERVER["CONTENT_TYPE"];
  794         } elseif (isset($_SERVER["HTTP_CONTENT_TYPE"])) {
  795             //fix bug https://bugs.php.net/bug.php?id=66606
  796             return $_SERVER["HTTP_CONTENT_TYPE"];
  797         }
  798         return null;
  799     }
  800 
  801     private $_port;
  802 
  803     /**
  804      * Returns the port to use for insecure requests.
  805      * Defaults to 80, or the port specified by the server if the current
  806      * request is insecure.
  807      * You may explicitly specify it by setting the {@link setPort port} property.
  808      * @return integer port number for insecure requests.
  809      * @see setPort
  810      * @since 1.1.3
  811      */
  812     public function getPort()
  813     {
  814         if($this->_port===null)
  815             $this->_port=!$this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int)$_SERVER['SERVER_PORT'] : 80;
  816         return $this->_port;
  817     }
  818 
  819     /**
  820      * Sets the port to use for insecure requests.
  821      * This setter is provided in case a custom port is necessary for certain
  822      * server configurations.
  823      * @param integer $value port number.
  824      * @since 1.1.3
  825      */
  826     public function setPort($value)
  827     {
  828         $this->_port=(int)$value;
  829         $this->_hostInfo=null;
  830     }
  831 
  832     private $_securePort;
  833 
  834     /**
  835      * Returns the port to use for secure requests.
  836      * Defaults to 443, or the port specified by the server if the current
  837      * request is secure.
  838      * You may explicitly specify it by setting the {@link setSecurePort securePort} property.
  839      * @return integer port number for secure requests.
  840      * @see setSecurePort
  841      * @since 1.1.3
  842      */
  843     public function getSecurePort()
  844     {
  845         if($this->_securePort===null)
  846             $this->_securePort=$this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int)$_SERVER['SERVER_PORT'] : 443;
  847         return $this->_securePort;
  848     }
  849 
  850     /**
  851      * Sets the port to use for secure requests.
  852      * This setter is provided in case a custom port is necessary for certain
  853      * server configurations.
  854      * @param integer $value port number.
  855      * @since 1.1.3
  856      */
  857     public function setSecurePort($value)
  858     {
  859         $this->_securePort=(int)$value;
  860         $this->_hostInfo=null;
  861     }
  862 
  863     /**
  864      * Returns the cookie collection.
  865      * The result can be used like an associative array. Adding {@link CHttpCookie} objects
  866      * to the collection will send the cookies to the client; and removing the objects
  867      * from the collection will delete those cookies on the client.
  868      * @return CCookieCollection the cookie collection.
  869      */
  870     public function getCookies()
  871     {
  872         if($this->_cookies!==null)
  873             return $this->_cookies;
  874         else
  875             return $this->_cookies=new CCookieCollection($this);
  876     }
  877 
  878     /**
  879      * Redirects the browser to the specified URL.
  880      * @param string $url URL to be redirected to. Note that when URL is not
  881      * absolute (not starting with "/") it will be relative to current request URL.
  882      * @param boolean $terminate whether to terminate the current application
  883      * @param integer $statusCode the HTTP status code. Defaults to 302. See {@link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html}
  884      * for details about HTTP status code.
  885      */
  886     public function redirect($url,$terminate=true,$statusCode=302)
  887     {
  888         if(strpos($url,'/')===0 && strpos($url,'//')!==0)
  889             $url=$this->getHostInfo().$url;
  890         header('Location: '.$url, true, $statusCode);
  891         if($terminate)
  892             Yii::app()->end();
  893     }
  894 
  895     /**
  896      * Parses an HTTP Accept header, returning an array map with all parts of each entry.
  897      * Each array entry consists of a map with the type, subType, baseType and params, an array map of key-value parameters,
  898      * obligatorily including a `q` value (i.e. preference ranking) as a double.
  899      * For example, an Accept header value of <code>'application/xhtml+xml;q=0.9;level=1'</code> would give an array entry of
  900      * <pre>
  901      * array(
  902      *        'type' => 'application',
  903      *        'subType' => 'xhtml',
  904      *        'baseType' => 'xml',
  905      *        'params' => array(
  906      *            'q' => 0.9,
  907      *            'level' => '1',
  908      *        ),
  909      * )
  910      * </pre>
  911      *
  912      * <b>Please note:</b>
  913      * To avoid great complexity, there are no steps taken to ensure that quoted strings are treated properly.
  914      * If the header text includes quoted strings containing space or the , or ; characters then the results may not be correct!
  915      *
  916      * See also {@link http://tools.ietf.org/html/rfc2616#section-14.1} for details on Accept header.
  917      * @param string $header the accept header value to parse
  918      * @return array the user accepted MIME types.
  919      */
  920     public static function parseAcceptHeader($header)
  921     {
  922         $matches=array();
  923         $accepts=array();
  924         // get individual entries with their type, subtype, basetype and params
  925         preg_match_all('/(?:\G\s?,\s?|^)(\w+|\*)\/(\w+|\*)(?:\+(\w+))?|(?<!^)\G(?:\s?;\s?(\w+)=([\w\.]+))/',$header,$matches);
  926         // the regexp should (in theory) always return an array of 6 arrays
  927         if(count($matches)===6)
  928         {
  929             $i=0;
  930             $itemLen=count($matches[1]);
  931             while($i<$itemLen)
  932             {
  933                 // fill out a content type
  934                 $accept=array(
  935                     'type'=>$matches[1][$i],
  936                     'subType'=>$matches[2][$i],
  937                     'baseType'=>null,
  938                     'params'=>array(),
  939                 );
  940                 // fill in the base type if it exists
  941                 if($matches[3][$i]!==null && $matches[3][$i]!=='')
  942                     $accept['baseType']=$matches[3][$i];
  943                 // continue looping while there is no new content type, to fill in all accompanying params
  944                 for($i++;$i<$itemLen;$i++)
  945                 {
  946                     // if the next content type is null, then the item is a param for the current content type
  947                     if($matches[1][$i]===null || $matches[1][$i]==='')
  948                     {
  949                         // if this is the quality param, convert it to a double
  950                         if($matches[4][$i]==='q')
  951                         {
  952                             // sanity check on q value
  953                             $q=(double)$matches[5][$i];
  954                             if($q>1)
  955                                 $q=(double)1;
  956                             elseif($q<0)
  957                                 $q=(double)0;
  958                             $accept['params'][$matches[4][$i]]=$q;
  959                         }
  960                         else
  961                             $accept['params'][$matches[4][$i]]=$matches[5][$i];
  962                     }
  963                     else
  964                         break;
  965                 }
  966                 // q defaults to 1 if not explicitly given
  967                 if(!isset($accept['params']['q']))
  968                     $accept['params']['q']=(double)1;
  969                 $accepts[] = $accept;
  970             }
  971         }
  972         return $accepts;
  973     }
  974 
  975     /**
  976      * Compare function for determining the preference of accepted MIME type array maps
  977      * See {@link parseAcceptHeader()} for the format of $a and $b
  978      * @param array $a user accepted MIME type as an array map
  979      * @param array $b user accepted MIME type as an array map
  980      * @return integer -1, 0 or 1 if $a has respectively greater preference, equal preference or less preference than $b (higher preference comes first).
  981      */
  982     public static function compareAcceptTypes($a,$b)
  983     {
  984         // check for equal quality first
  985         if($a['params']['q']===$b['params']['q'])
  986             if(!($a['type']==='*' xor $b['type']==='*'))
  987                 if (!($a['subType']==='*' xor $b['subType']==='*'))
  988                     // finally, higher number of parameters counts as greater precedence
  989                     if(count($a['params'])===count($b['params']))
  990                         return 0;
  991                     else
  992                         return count($a['params'])<count($b['params']) ? 1 : -1;
  993                 // more specific takes precedence - whichever one doesn't have a * subType
  994                 else
  995                     return $a['subType']==='*' ? 1 : -1;
  996             // more specific takes precedence - whichever one doesn't have a * type
  997             else
  998                 return $a['type']==='*' ? 1 : -1;
  999         else
 1000             return ($a['params']['q']<$b['params']['q']) ? 1 : -1;
 1001     }
 1002 
 1003     /**
 1004      * Returns an array of user accepted MIME types in order of preference.
 1005      * Each array entry consists of a map with the type, subType, baseType and params, an array map of key-value parameters.
 1006      * See {@link parseAcceptHeader()} for a description of the array map.
 1007      * @return array the user accepted MIME types, as array maps, in the order of preference.
 1008      */
 1009     public function getPreferredAcceptTypes()
 1010     {
 1011         if($this->_preferredAcceptTypes===null)
 1012         {
 1013             $accepts=self::parseAcceptHeader($this->getAcceptTypes());
 1014             usort($accepts,array(get_class($this),'compareAcceptTypes'));
 1015             $this->_preferredAcceptTypes=$accepts;
 1016         }
 1017         return $this->_preferredAcceptTypes;
 1018     }
 1019 
 1020     /**
 1021      * Returns the user preferred accept MIME type.
 1022      * The MIME type is returned as an array map (see {@link parseAcceptHeader()}).
 1023      * @return array the user preferred accept MIME type or false if the user does not have any.
 1024      */
 1025     public function getPreferredAcceptType()
 1026     {
 1027         $preferredAcceptTypes=$this->getPreferredAcceptTypes();
 1028         return empty($preferredAcceptTypes) ? false : $preferredAcceptTypes[0];
 1029     }
 1030 
 1031     /**
 1032      * String compare function used by usort.
 1033      * Included to circumvent the use of closures (not supported by PHP 5.2) and create_function (deprecated since PHP 7.2.0)
 1034      * @param array $a
 1035      * @param array $b
 1036      * @return int -1 (a>b), 0 (a==b), 1 (a<b)
 1037      */
 1038     private function stringCompare($a, $b)
 1039     {
 1040         if ($a[0] == $b[0]) {
 1041             return 0;
 1042         }
 1043         return ($a[0] < $b[0]) ? 1 : -1;
 1044     }
 1045 
 1046     /**
 1047      * Returns an array of user accepted languages in order of preference.
 1048      * The returned language IDs will NOT be canonicalized using {@link CLocale::getCanonicalID}.
 1049      * @return array the user accepted languages in the order of preference.
 1050      * See {@link http://tools.ietf.org/html/rfc2616#section-14.4}
 1051      */
 1052     public function getPreferredLanguages()
 1053     {
 1054         if($this->_preferredLanguages===null)
 1055         {
 1056             $sortedLanguages=array();
 1057             if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) && $n=preg_match_all('/([\w\-_]+)(?:\s*;\s*q\s*=\s*(\d*\.?\d*))?/',$_SERVER['HTTP_ACCEPT_LANGUAGE'],$matches))
 1058             {
 1059                 $languages=array();
 1060 
 1061                 for($i=0;$i<$n;++$i)
 1062                 {
 1063                     $q=$matches[2][$i];
 1064                     if($q==='')
 1065                         $q=1;
 1066                     if($q)
 1067                         $languages[]=array((float)$q,$matches[1][$i]);
 1068                 }
 1069 
 1070                 usort($languages, array($this, 'stringCompare'));
 1071                 foreach($languages as $language)
 1072                     $sortedLanguages[]=$language[1];
 1073             }
 1074             $this->_preferredLanguages=$sortedLanguages;
 1075         }
 1076         return $this->_preferredLanguages;
 1077     }
 1078 
 1079     /**
 1080      * Returns the user-preferred language that should be used by this application.
 1081      * The language resolution is based on the user preferred languages and the languages
 1082      * supported by the application. The method will try to find the best match.
 1083      * @param array $languages a list of the languages supported by the application.
 1084      * If empty, this method will return the first language returned by [[getPreferredLanguages()]].
 1085      * @return string the language that the application should use. false is returned if both [[getPreferredLanguages()]]
 1086      * and `$languages` are empty.
 1087      */
 1088     public function getPreferredLanguage($languages=array())
 1089     {
 1090         $preferredLanguages=$this->getPreferredLanguages();
 1091         if(empty($languages)) {
 1092             return !empty($preferredLanguages) ? CLocale::getCanonicalID($preferredLanguages[0]) : false;
 1093         }
 1094         foreach ($preferredLanguages as $preferredLanguage) {
 1095             $preferredLanguage=CLocale::getCanonicalID($preferredLanguage);
 1096             foreach ($languages as $language) {
 1097                 $language=CLocale::getCanonicalID($language);
 1098                 // en_us==en_us, en==en_us, en_us==en
 1099                 if($language===$preferredLanguage || strpos($preferredLanguage,$language.'_')===0 || strpos($language,$preferredLanguage.'_')===0) {
 1100                     return $language;
 1101                 }
 1102             }
 1103         }
 1104         return reset($languages);
 1105     }
 1106 
 1107     /**
 1108      * Sends a file to user.
 1109      * @param string $fileName file name
 1110      * @param string $content content to be set.
 1111      * @param string $mimeType mime type of the content. If null, it will be guessed automatically based on the given file name.
 1112      * @param boolean $terminate whether to terminate the current application after calling this method
 1113      * @throws CHttpException
 1114      */
 1115     public function sendFile($fileName,$content,$mimeType=null,$terminate=true)
 1116     {
 1117         if($mimeType===null)
 1118         {
 1119             if(($mimeType=CFileHelper::getMimeTypeByExtension($fileName))===null)
 1120                 $mimeType='text/plain';
 1121         }
 1122 
 1123         $fileSize=(function_exists('mb_strlen') ? mb_strlen($content,'8bit') : strlen($content));
 1124         $contentStart=0;
 1125         $contentEnd=$fileSize-1;
 1126 
 1127         $httpVersion=$this->getHttpVersion();
 1128         if(isset($_SERVER['HTTP_RANGE']))
 1129         {
 1130             header('Accept-Ranges: bytes');
 1131 
 1132             //client sent us a multibyte range, can not hold this one for now
 1133             if(strpos($_SERVER['HTTP_RANGE'],',')!==false)
 1134             {
 1135                 header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
 1136                 throw new CHttpException(416,'Requested Range Not Satisfiable');
 1137             }
 1138 
 1139             $range=str_replace('bytes=','',$_SERVER['HTTP_RANGE']);
 1140 
 1141             //range requests starts from "-", so it means that data must be dumped the end point.
 1142             if($range[0]==='-')
 1143                 $contentStart=$fileSize-substr($range,1);
 1144             else
 1145             {
 1146                 $range=explode('-',$range);
 1147                 $contentStart=$range[0];
 1148 
 1149                 // check if the last-byte-pos presents in header
 1150                 if((isset($range[1]) && is_numeric($range[1])))
 1151                     $contentEnd=$range[1];
 1152             }
 1153 
 1154             /* Check the range and make sure it's treated according to the specs.
 1155              * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
 1156              */
 1157             // End bytes can not be larger than $end.
 1158             $contentEnd=($contentEnd > $fileSize) ? $fileSize-1 : $contentEnd;
 1159 
 1160             // Validate the requested range and return an error if it's not correct.
 1161             $wrongContentStart=($contentStart>$contentEnd || $contentStart>$fileSize-1 || $contentStart<0);
 1162 
 1163             if($wrongContentStart)
 1164             {
 1165                 header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
 1166                 throw new CHttpException(416,'Requested Range Not Satisfiable');
 1167             }
 1168 
 1169             header("HTTP/$httpVersion 206 Partial Content");
 1170             header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
 1171         }
 1172         else
 1173             header("HTTP/$httpVersion 200 OK");
 1174 
 1175         $length=$contentEnd-$contentStart+1; // Calculate new content length
 1176 
 1177         header('Pragma: public');
 1178         header('Expires: 0');
 1179         header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
 1180         header("Content-Type: $mimeType");
 1181         header('Content-Length: '.$length);
 1182         header("Content-Disposition: attachment; filename=\"$fileName\"");
 1183         header('Content-Transfer-Encoding: binary');
 1184         $content=function_exists('mb_substr') ? mb_substr($content,$contentStart,$length,'8bit') : substr($content,$contentStart,$length);
 1185 
 1186         if($terminate)
 1187         {
 1188             // clean up the application first because the file downloading could take long time
 1189             // which may cause timeout of some resources (such as DB connection)
 1190             ob_start();
 1191             Yii::app()->end(0,false);
 1192             ob_end_clean();
 1193             echo $content;
 1194             exit(0);
 1195         }
 1196         else
 1197             echo $content;
 1198     }
 1199 
 1200     /**
 1201      * Sends existing file to a browser as a download using x-sendfile.
 1202      *
 1203      * X-Sendfile is a feature allowing a web application to redirect the request for a file to the webserver
 1204      * that in turn processes the request, this way eliminating the need to perform tasks like reading the file
 1205      * and sending it to the user. When dealing with a lot of files (or very big files) this can lead to a great
 1206      * increase in performance as the web application is allowed to terminate earlier while the webserver is
 1207      * handling the request.
 1208      *
 1209      * The request is sent to the server through a special non-standard HTTP-header.
 1210      * When the web server encounters the presence of such header it will discard all output and send the file
 1211      * specified by that header using web server internals including all optimizations like caching-headers.
 1212      *
 1213      * As this header directive is non-standard different directives exists for different web servers applications:
 1214      * <ul>
 1215      * <li>Apache: {@link http://tn123.org/mod_xsendfile X-Sendfile}</li>
 1216      * <li>Lighttpd v1.4: {@link http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file X-LIGHTTPD-send-file}</li>
 1217      * <li>Lighttpd v1.5: {@link http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file X-Sendfile}</li>
 1218      * <li>Nginx: {@link http://wiki.nginx.org/XSendfile X-Accel-Redirect}</li>
 1219      * <li>Cherokee: {@link http://www.cherokee-project.com/doc/other_goodies.html#x-sendfile X-Sendfile and X-Accel-Redirect}</li>
 1220      * </ul>
 1221      * So for this method to work the X-SENDFILE option/module should be enabled by the web server and
 1222      * a proper xHeader should be sent.
 1223      *
 1224      * <b>Note:</b>
 1225      * This option allows to download files that are not under web folders, and even files that are otherwise protected (deny from all) like .htaccess
 1226      *
 1227      * <b>Side effects</b>:
 1228      * If this option is disabled by the web server, when this method is called a download configuration dialog
 1229      * will open but the downloaded file will have 0 bytes.
 1230      *
 1231      * <b>Known issues</b>:
 1232      * There is a Bug with Internet Explorer 6, 7 and 8 when X-SENDFILE is used over an SSL connection, it will show
 1233      * 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.".
 1234      * You can work around this problem by removing the <code>Pragma</code>-header.
 1235      *
 1236      * <b>Example</b>:
 1237      * <pre>
 1238      * <?php
 1239      *    Yii::app()->request->xSendFile('/home/user/Pictures/picture1.jpg',array(
 1240      *        'saveName'=>'image1.jpg',
 1241      *        'mimeType'=>'image/jpeg',
 1242      *        'terminate'=>false,
 1243      *    ));
 1244      * ?>
 1245      * </pre>
 1246      * @param string $filePath file name with full path
 1247      * @param array $options additional options:
 1248      * <ul>
 1249      * <li>saveName: file name shown to the user, if not set real file name will be used</li>
 1250      * <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>
 1251      * <li>xHeader: appropriate x-sendfile header, defaults to "X-Sendfile"</li>
 1252      * <li>terminate: whether to terminate the current application after calling this method, defaults to true</li>
 1253      * <li>forceDownload: specifies whether the file will be downloaded or shown inline, defaults to true. (Since version 1.1.9.)</li>
 1254      * <li>addHeaders: an array of additional http headers in header-value pairs (available since version 1.1.10)</li>
 1255      * </ul>
 1256      */
 1257     public function xSendFile($filePath, $options=array())
 1258     {
 1259         if(!isset($options['forceDownload']) || $options['forceDownload'])
 1260             $disposition='attachment';
 1261         else
 1262             $disposition='inline';
 1263 
 1264         if(!isset($options['saveName']))
 1265             $options['saveName']=basename($filePath);
 1266 
 1267         if(!isset($options['mimeType']))
 1268         {
 1269             if(($options['mimeType']=CFileHelper::getMimeTypeByExtension($filePath))===null)
 1270                 $options['mimeType']='text/plain';
 1271         }
 1272 
 1273         if(!isset($options['xHeader']))
 1274             $options['xHeader']='X-Sendfile';
 1275 
 1276         if($options['mimeType']!==null)
 1277             header('Content-Type: '.$options['mimeType']);
 1278         header('Content-Disposition: '.$disposition.'; filename="'.$options['saveName'].'"');
 1279         if(isset($options['addHeaders']))
 1280         {
 1281             foreach($options['addHeaders'] as $header=>$value)
 1282                 header($header.': '.$value);
 1283         }
 1284         header(trim($options['xHeader']).': '.$filePath);
 1285 
 1286         if(!isset($options['terminate']) || $options['terminate'])
 1287             Yii::app()->end();
 1288     }
 1289 
 1290     /**
 1291      * Returns the random token used to perform CSRF validation.
 1292      * The token will be read from cookie first. If not found, a new token
 1293      * will be generated.
 1294      * @return string the random token for CSRF validation.
 1295      * @see enableCsrfValidation
 1296      */
 1297     public function getCsrfToken()
 1298     {
 1299         if($this->_csrfToken===null)
 1300         {
 1301             $cookie=$this->getCookies()->itemAt($this->csrfTokenName);
 1302             if(!$cookie || ($this->_csrfToken=$cookie->value)==null)
 1303             {
 1304                 $cookie=$this->createCsrfCookie();
 1305                 $this->_csrfToken=$cookie->value;
 1306                 $this->getCookies()->add($cookie->name,$cookie);
 1307             }
 1308         }
 1309 
 1310         return $this->_csrfToken;
 1311     }
 1312 
 1313     /**
 1314      * Creates a cookie with a randomly generated CSRF token.
 1315      * Initial values specified in {@link csrfCookie} will be applied
 1316      * to the generated cookie.
 1317      * @return CHttpCookie the generated cookie
 1318      * @see enableCsrfValidation
 1319      */
 1320     protected function createCsrfCookie()
 1321     {
 1322         $securityManager=Yii::app()->getSecurityManager();
 1323         $token=$securityManager->generateRandomBytes(32);
 1324         $maskedToken=$securityManager->maskToken($token);
 1325         $cookie=new CHttpCookie($this->csrfTokenName,$maskedToken);
 1326         if(is_array($this->csrfCookie))
 1327         {
 1328             foreach($this->csrfCookie as $name=>$value)
 1329                 $cookie->$name=$value;
 1330         }
 1331         return $cookie;
 1332     }
 1333 
 1334     /**
 1335      * Performs the CSRF validation.
 1336      * This is the event handler responding to {@link CApplication::onBeginRequest}.
 1337      * The default implementation will compare the CSRF token obtained
 1338      * from a cookie and from a POST field. If they are different, a CSRF attack is detected.
 1339      * @param CEvent $event event parameter
 1340      * @throws CHttpException if the validation fails
 1341      */
 1342     public function validateCsrfToken($event)
 1343     {
 1344         if ($this->getIsPostRequest() ||
 1345             $this->getIsPutRequest() ||
 1346             $this->getIsPatchRequest() ||
 1347             $this->getIsDeleteRequest())
 1348         {
 1349             $cookies=$this->getCookies();
 1350 
 1351             $method=$this->getRequestType();
 1352             switch($method)
 1353             {
 1354                 case 'POST':
 1355                     $maskedUserToken=$this->getPost($this->csrfTokenName);
 1356                 break;
 1357                 case 'PUT':
 1358                     $maskedUserToken=$this->getPut($this->csrfTokenName);
 1359                 break;
 1360                 case 'PATCH':
 1361                     $maskedUserToken=$this->getPatch($this->csrfTokenName);
 1362                 break;
 1363                 case 'DELETE':
 1364                     $maskedUserToken=$this->getDelete($this->csrfTokenName);
 1365             }
 1366 
 1367             if (!empty($maskedUserToken) && $cookies->contains($this->csrfTokenName))
 1368             {
 1369                 $securityManager=Yii::app()->getSecurityManager();
 1370                 $maskedCookieToken=$cookies->itemAt($this->csrfTokenName)->value;
 1371                 $cookieToken=$securityManager->unmaskToken($maskedCookieToken);
 1372                 $userToken=$securityManager->unmaskToken($maskedUserToken);
 1373                 $valid=$cookieToken===$userToken;
 1374             }
 1375             else
 1376                 $valid = false;
 1377             if (!$valid)
 1378                 throw new CHttpException(400,Yii::t('yii','The CSRF token could not be verified.'));
 1379         }
 1380     }
 1381 
 1382 
 1383     /**
 1384      * Returns the version of the HTTP protocol used by client.
 1385      *
 1386      * @return string the version of the HTTP protocol.
 1387      * @since 1.1.16
 1388      */
 1389     public function getHttpVersion()
 1390     {
 1391         if($this->_httpVersion===null)
 1392         {
 1393             if(isset($_SERVER['SERVER_PROTOCOL']) && $_SERVER['SERVER_PROTOCOL']==='HTTP/1.0')
 1394                 $this->_httpVersion='1.0';
 1395             else
 1396                 $this->_httpVersion='1.1';
 1397         }
 1398         return $this->_httpVersion;
 1399     }
 1400 }
 1401 
 1402 
 1403 /**
 1404  * CCookieCollection implements a collection class to store cookies.
 1405  *
 1406  * You normally access it via {@link CHttpRequest::getCookies()}.
 1407  *
 1408  * Since CCookieCollection extends from {@link CMap}, it can be used
 1409  * like an associative array as follows:
 1410  * <pre>
 1411  * $cookies[$name]=new CHttpCookie($name,$value); // sends a cookie
 1412  * $value=$cookies[$name]->value; // reads a cookie value
 1413  * unset($cookies[$name]);  // removes a cookie
 1414  * </pre>
 1415  *
 1416  * @author Qiang Xue <qiang.xue@gmail.com>
 1417  * @package system.web
 1418  * @since 1.0
 1419  */
 1420 class CCookieCollection extends CMap
 1421 {
 1422     private $_request;
 1423     private $_initialized=false;
 1424 
 1425     /**
 1426      * Constructor.
 1427      * @param CHttpRequest $request owner of this collection.
 1428      */
 1429     public function __construct(CHttpRequest $request)
 1430     {
 1431         $this->_request=$request;
 1432         $this->copyfrom($this->getCookies());
 1433         $this->_initialized=true;
 1434     }
 1435 
 1436     /**
 1437      * @return CHttpRequest the request instance
 1438      */
 1439     public function getRequest()
 1440     {
 1441         return $this->_request;
 1442     }
 1443 
 1444     /**
 1445      * @return array list of validated cookies
 1446      */
 1447     protected function getCookies()
 1448     {
 1449         $cookies=array();
 1450         if($this->_request->enableCookieValidation)
 1451         {
 1452             $sm=Yii::app()->getSecurityManager();
 1453             foreach($_COOKIE as $name=>$value)
 1454             {
 1455                 if(is_string($value) && ($value=$sm->validateData($value))!==false)
 1456                     $cookies[$name]=new CHttpCookie($name,@unserialize($value));
 1457             }
 1458         }
 1459         else
 1460         {
 1461             foreach($_COOKIE as $name=>$value)
 1462                 $cookies[$name]=new CHttpCookie($name,$value);
 1463         }
 1464         return $cookies;
 1465     }
 1466 
 1467     /**
 1468      * Adds a cookie with the specified name.
 1469      * This overrides the parent implementation by performing additional
 1470      * operations for each newly added CHttpCookie object.
 1471      * @param mixed $name Cookie name.
 1472      * @param CHttpCookie $cookie Cookie object.
 1473      * @throws CException if the item to be inserted is not a CHttpCookie object.
 1474      */
 1475     public function add($name,$cookie)
 1476     {
 1477         if($cookie instanceof CHttpCookie)
 1478         {
 1479             $this->remove($name);
 1480             parent::add($name,$cookie);
 1481             if($this->_initialized)
 1482                 $this->addCookie($cookie);
 1483         }
 1484         else
 1485             throw new CException(Yii::t('yii','CHttpCookieCollection can only hold CHttpCookie objects.'));
 1486     }
 1487 
 1488     /**
 1489      * Removes a cookie with the specified name.
 1490      * This overrides the parent implementation by performing additional
 1491      * cleanup work when removing a CHttpCookie object.
 1492      * Since version 1.1.11, the second parameter is available that can be used to specify
 1493      * the options of the CHttpCookie being removed. For example, this may be useful when dealing
 1494      * with ".domain.tld" where multiple subdomains are expected to be able to manage cookies:
 1495      *
 1496      * <pre>
 1497      * $options=array('domain'=>'.domain.tld');
 1498      * Yii::app()->request->cookies['foo']=new CHttpCookie('cookie','value',$options);
 1499      * Yii::app()->request->cookies->remove('cookie',$options);
 1500      * </pre>
 1501      *
 1502      * @param mixed $name Cookie name.
 1503      * @param array $options Cookie configuration array consisting of name-value pairs, available since 1.1.11.
 1504      * @return CHttpCookie The removed cookie object.
 1505      */
 1506     public function remove($name,$options=array())
 1507     {
 1508         if(($cookie=parent::remove($name))!==null)
 1509         {
 1510             if($this->_initialized)
 1511             {
 1512                 $cookie->configure($options);
 1513                 $this->removeCookie($cookie);
 1514             }
 1515         }
 1516 
 1517         return $cookie;
 1518     }
 1519 
 1520     /**
 1521      * Sends a cookie.
 1522      * @param CHttpCookie $cookie cookie to be sent
 1523      */
 1524     protected function addCookie($cookie)
 1525     {
 1526         $value=$cookie->value;
 1527         if($this->_request->enableCookieValidation)
 1528             $value=Yii::app()->getSecurityManager()->hashData(serialize($value));
 1529         if(version_compare(PHP_VERSION,'7.3.0','>='))
 1530             setcookie($cookie->name,$value,$this->getCookieOptions($cookie));
 1531         elseif(version_compare(PHP_VERSION,'5.2.0','>='))
 1532             setcookie($cookie->name,$value,$cookie->expire,$cookie->path,$cookie->domain,$cookie->secure,$cookie->httpOnly);
 1533         else
 1534             setcookie($cookie->name,$value,$cookie->expire,$cookie->path,$cookie->domain,$cookie->secure);
 1535     }
 1536 
 1537     /**
 1538      * Deletes a cookie.
 1539      * @param CHttpCookie $cookie cookie to be deleted
 1540      */
 1541     protected function removeCookie($cookie)
 1542     {
 1543         $cookie->expire=0;
 1544         if(version_compare(PHP_VERSION,'7.3.0','>='))
 1545             setcookie($cookie->name,'',$this->getCookieOptions($cookie));
 1546         elseif(version_compare(PHP_VERSION,'5.2.0','>='))
 1547             setcookie($cookie->name,'',$cookie->expire,$cookie->path,$cookie->domain,$cookie->secure,$cookie->httpOnly);
 1548         else
 1549             setcookie($cookie->name,'',$cookie->expire,$cookie->path,$cookie->domain,$cookie->secure);
 1550     }
 1551 
 1552     /**
 1553      * Builds the setcookie $options parameter.
 1554      * @param CHttpCookie $cookie
 1555      * @return array
 1556      */
 1557     protected function getCookieOptions($cookie)
 1558     {
 1559         return array(
 1560             'expires'=>$cookie->expire,
 1561             'path'=>$cookie->path,
 1562             'domain'=>$cookie->domain,
 1563             'secure'=>$cookie->secure,
 1564             'httpOnly'=>$cookie->httpOnly,
 1565             'sameSite'=>$cookie->sameSite
 1566         );
 1567     }
 1568 }