From 4d6b3ddb9dc1288683f720be71802f114c0e1b5e Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 28 Feb 2013 20:30:50 -0500 Subject: [PATCH] request wip --- framework/web/CookieCollection.php | 51 +++++++++-- framework/web/Request.php | 167 +++++++++---------------------------- framework/web/Response.php | 73 ++++++++-------- 3 files changed, 122 insertions(+), 169 deletions(-) diff --git a/framework/web/CookieCollection.php b/framework/web/CookieCollection.php index bafd990..535faaf 100644 --- a/framework/web/CookieCollection.php +++ b/framework/web/CookieCollection.php @@ -9,6 +9,7 @@ namespace yii\web; +use Yii; use yii\base\DictionaryIterator; /** @@ -22,19 +23,24 @@ use yii\base\DictionaryIterator; class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ArrayAccess, \Countable { /** + * @var boolean whether to enable cookie validation. By setting this property to true, + * if a cookie is tampered on the client side, it will be ignored when received on the server side. + */ + public $enableValidation = true; + + /** * @var Cookie[] the cookies in this collection (indexed by the cookie names) */ private $_cookies = array(); /** * Constructor. - * @param Cookie[] $cookies the initial cookies in the collection. * @param array $config name-value pairs that will be used to initialize the object properties */ - public function __construct($cookies = array(), $config = array()) + public function __construct($config = array()) { - $this->_cookies = $cookies; parent::__construct($config); + $this->_cookies = $this->loadCookies(); } /** @@ -86,7 +92,7 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ * @return mixed the value of the named cookie. * @see get() */ - public function getValue($name, $defaultValue) + public function getValue($name, $defaultValue = null) { return isset($this->_cookies[$name]) ? $this->_cookies[$name]->value : $defaultValue; } @@ -102,7 +108,13 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ $c = $this->_cookies[$cookie->name]; setcookie($c->name, '', 0, $c->path, $c->domain, $c->secure, $c->httpOnly); } - setcookie($cookie->name, $cookie->value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly); + + $value = $cookie->value; + if ($this->enableValidation) { + $value = Yii::$app->getSecurityManager()->hashData(serialize($value)); + } + + setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly); $this->_cookies[$cookie->name] = $cookie; } @@ -192,4 +204,33 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ { $this->remove($name); } + + + /** + * Returns the current cookies in terms of [[Cookie]] objects. + * @return Cookie[] list of current cookies + */ + protected function loadCookies() + { + $cookies = array(); + if ($this->enableValidation) { + $sm = \Yii::$app->getSecurityManager(); + foreach ($_COOKIE as $name => $value) { + if (is_string($value) && ($value = $sm->validateData($value)) !== false) { + $cookies[$name] = new Cookie(array( + 'name' => $name, + 'value' => @unserialize($value), + )); + } + } + } else { + foreach ($_COOKIE as $name => $value) { + $cookies[$name] = new Cookie(array( + 'name' => $name, + 'value' => $value, + )); + } + } + return $cookies; + } } diff --git a/framework/web/Request.php b/framework/web/Request.php index 8c7d7b3..123fc62 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -178,15 +178,29 @@ class Request extends \yii\base\Request } else { $this->_restParams = array(); if (function_exists('mb_parse_str')) { - mb_parse_str(file_get_contents('php://input'), $this->_restParams); + mb_parse_str($this->getRawBody(), $this->_restParams); } else { - parse_str(file_get_contents('php://input'), $this->_restParams); + parse_str($this->getRawBody(), $this->_restParams); } } } return $this->_restParams; } + private $_rawBody; + + /** + * Returns the raw HTTP request body. + * @return string the request body + */ + public function getRawBody() + { + if ($this->_rawBody === null) { + $this->_rawBody = file_get_contents('php://input'); + } + return $this->_rawBody; + } + /** * Sets the RESTful parameters. * @param array $values the RESTful parameters (name-value pairs) @@ -382,6 +396,11 @@ class Request extends \yii\base\Request return $this->_pathInfo; } + /** + * Sets the path info of the current request. + * This method is mainly provided for testing purpose. + * @param string $value the path info of the current request + */ public function setPathInfo($value) { $this->_pathInfo = trim($value, '/'); @@ -403,7 +422,22 @@ class Request extends \yii\base\Request $pathInfo = substr($pathInfo, 0, $pos); } - $pathInfo = $this->decodeUrl($pathInfo); + $pathInfo = urldecode($pathInfo); + + // try to encode in UTF8 if not so + // http://w3.org/International/questions/qa-forms-utf-8.html + if (!preg_match('%^(?: + [\x09\x0A\x0D\x20-\x7E] # ASCII + | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte + | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs + | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte + | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates + | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 + )*$%xs', $pathInfo)) { + $pathInfo = utf8_encode($pathInfo); + } $scriptUrl = $this->getScriptUrl(); $baseUrl = $this->getBaseUrl(); @@ -414,42 +448,13 @@ class Request extends \yii\base\Request } elseif (strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) { $pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl)); } else { - return false; + throw new InvalidConfigException('Unable to determine the path info of the current request.'); } return trim($pathInfo, '/'); } /** - * Decodes the given URL. - * This method is an improved variant of the native urldecode() function. It will properly encode - * UTF-8 characters which may be returned by urldecode(). - * @param string $url encoded URL - * @return string decoded URL - */ - public function decodeUrl($url) - { - $url = urldecode($url); - - // is it UTF-8? - // http://w3.org/International/questions/qa-forms-utf-8.html - if (preg_match('%^(?: - [\x09\x0A\x0D\x20-\x7E] # ASCII - | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte - | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs - | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte - | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates - | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 - | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 - | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 - )*$%xs', $url)) { - return $url; - } else { - return utf8_encode($url); - } - } - - /** * Returns the currently requested URL. * This is a shortcut to the concatenation of [[hostInfo]] and [[requestUri]]. * @return string the currently requested URL. @@ -714,101 +719,11 @@ class Request extends \yii\base\Request public function getCookies() { if ($this->_cookies === null) { - $this->_cookies = new CookieCollection($this->loadCookies()); + $this->_cookies = new CookieCollection(array( + 'enableValidation' => $this->enableCookieValidation, + )); } return $this->_cookies; } - - /** - * Returns the current cookies in terms of [[Cookie]] objects. - * @return Cookie[] list of current cookies - */ - protected function loadCookies() - { - $cookies = array(); - if ($this->enableCookieValidation) { - $sm = Yii::app()->getSecurityManager(); - foreach ($_COOKIE as $name => $value) { - if (is_string($value) && ($value = $sm->validateData($value)) !== false) { - $cookies[$name] = new CHttpCookie($name, @unserialize($value)); - } - } - } else { - foreach ($_COOKIE as $name => $value) { - $cookies[$name] = new Cookie(array( - 'name' => $name, - 'value' => $value, - )); - } - } - return $cookies; - } - - private $_csrfToken; - - /** - * Returns the random token used to perform CSRF validation. - * The token will be read from cookie first. If not found, a new token - * will be generated. - * @return string the random token for CSRF validation. - * @see enableCsrfValidation - */ - public function getCsrfToken() - { - if ($this->_csrfToken === null) { - $cookie = $this->getCookies()->itemAt($this->csrfTokenName); - if (!$cookie || ($this->_csrfToken = $cookie->value) == null) { - $cookie = $this->createCsrfCookie(); - $this->_csrfToken = $cookie->value; - $this->getCookies()->add($cookie->name, $cookie); - } - } - - return $this->_csrfToken; - } - - /** - * Creates a cookie with a randomly generated CSRF token. - * Initial values specified in {@link csrfCookie} will be applied - * to the generated cookie. - * @return CHttpCookie the generated cookie - * @see enableCsrfValidation - */ - protected function createCsrfCookie() - { - $cookie = new CHttpCookie($this->csrfTokenName, sha1(uniqid(mt_rand(), true))); - if (is_array($this->csrfCookie)) { - foreach ($this->csrfCookie as $name => $value) { - $cookie->$name = $value; - } - } - return $cookie; - } - - /** - * Performs the CSRF validation. - * This is the event handler responding to {@link CApplication::onBeginRequest}. - * The default implementation will compare the CSRF token obtained - * from a cookie and from a POST field. If they are different, a CSRF attack is detected. - * @param CEvent $event event parameter - * @throws CHttpException if the validation fails - */ - public function validateCsrfToken($event) - { - if ($this->getIsPostRequest()) { - // only validate POST requests - $cookies = $this->getCookies(); - if ($cookies->contains($this->csrfTokenName) && isset($_POST[$this->csrfTokenName])) { - $tokenFromCookie = $cookies->itemAt($this->csrfTokenName)->value; - $tokenFromPost = $_POST[$this->csrfTokenName]; - $valid = $tokenFromCookie === $tokenFromPost; - } else { - $valid = false; - } - if (!$valid) { - throw new CHttpException(400, Yii::t('yii|The CSRF token could not be verified.')); - } - } - } } diff --git a/framework/web/Response.php b/framework/web/Response.php index 99c4865..73e1ff2 100644 --- a/framework/web/Response.php +++ b/framework/web/Response.php @@ -82,6 +82,11 @@ class Response extends \yii\base\Response * If this option is disabled by the web server, when this method is called a download configuration dialog * will open but the downloaded file will have 0 bytes. * + * Known issues: + * There is a Bug with Internet Explorer 6, 7 and 8 when X-SENDFILE is used over an SSL connection, it will show + * 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.". + * You can work around this problem by removing the Pragma-header. + * * Example: *
 	 * forceDownload: specifies whether the file will be downloaded or shown inline, defaults to true. (Since version 1.1.9.)
 	 * 
  • addHeaders: an array of additional http headers in header-value pairs (available since version 1.1.10)
  • * - * @todo */ - public function xSendFile($filePath, $options = array()) + public function xSendFile($filePath, $options=array()) { - if (!isset($options['forceDownload']) || $options['forceDownload']) { - $disposition = 'attachment'; - } else { - $disposition = 'inline'; - } + if(!isset($options['forceDownload']) || $options['forceDownload']) + $disposition='attachment'; + else + $disposition='inline'; - if (!isset($options['saveName'])) { - $options['saveName'] = basename($filePath); - } + if(!isset($options['saveName'])) + $options['saveName']=basename($filePath); - if (!isset($options['mimeType'])) { - if (($options['mimeType'] = CFileHelper::getMimeTypeByExtension($filePath)) === null) { - $options['mimeType'] = 'text/plain'; - } + if(!isset($options['mimeType'])) + { + if(($options['mimeType']=CFileHelper::getMimeTypeByExtension($filePath))===null) + $options['mimeType']='text/plain'; } - if (!isset($options['xHeader'])) { - $options['xHeader'] = 'X-Sendfile'; - } + if(!isset($options['xHeader'])) + $options['xHeader']='X-Sendfile'; - if ($options['mimeType'] !== null) { - header('Content-type: ' . $options['mimeType']); - } - header('Content-Disposition: ' . $disposition . '; filename="' . $options['saveName'] . '"'); - if (isset($options['addHeaders'])) { - foreach ($options['addHeaders'] as $header => $value) { - header($header . ': ' . $value); - } + if($options['mimeType'] !== null) + header('Content-type: '.$options['mimeType']); + header('Content-Disposition: '.$disposition.'; filename="'.$options['saveName'].'"'); + if(isset($options['addHeaders'])) + { + foreach($options['addHeaders'] as $header=>$value) + header($header.': '.$value); } - header(trim($options['xHeader']) . ': ' . $filePath); + header(trim($options['xHeader']).': '.$filePath); - if (!isset($options['terminate']) || $options['terminate']) { + if(!isset($options['terminate']) || $options['terminate']) Yii::app()->end(); - } } - /** * Redirects the browser to the specified URL. - * @param string $url URL to be redirected to. If the URL is a relative one, the base URL of - * the application will be inserted at the beginning. + * @param string $url URL to be redirected to. Note that when URL is not + * absolute (not starting with "/") it will be relative to current request URL. * @param boolean $terminate whether to terminate the current application * @param integer $statusCode the HTTP status code. Defaults to 302. See {@link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html} * for details about HTTP status code. */ - public function redirect($url, $terminate = true, $statusCode = 302) + public function redirect($url,$terminate=true,$statusCode=302) { - if (strpos($url, '/') === 0) { - $url = $this->getHostInfo() . $url; - } - header('Location: ' . $url, true, $statusCode); - if ($terminate) { + if(strpos($url,'/')===0 && strpos($url,'//')!==0) + $url=$this->getHostInfo().$url; + header('Location: '.$url, true, $statusCode); + if($terminate) Yii::app()->end(); - } } + /** * Returns the cookie collection. * Through the returned cookie collection, you add or remove cookies as follows,