Commit 4d6b3ddb by Qiang Xue

request wip

parent 1536a682
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
namespace yii\web; namespace yii\web;
use Yii;
use yii\base\DictionaryIterator; use yii\base\DictionaryIterator;
/** /**
...@@ -22,19 +23,24 @@ use yii\base\DictionaryIterator; ...@@ -22,19 +23,24 @@ use yii\base\DictionaryIterator;
class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ArrayAccess, \Countable 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) * @var Cookie[] the cookies in this collection (indexed by the cookie names)
*/ */
private $_cookies = array(); private $_cookies = array();
/** /**
* Constructor. * 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 * @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); parent::__construct($config);
$this->_cookies = $this->loadCookies();
} }
/** /**
...@@ -86,7 +92,7 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ ...@@ -86,7 +92,7 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
* @return mixed the value of the named cookie. * @return mixed the value of the named cookie.
* @see get() * @see get()
*/ */
public function getValue($name, $defaultValue) public function getValue($name, $defaultValue = null)
{ {
return isset($this->_cookies[$name]) ? $this->_cookies[$name]->value : $defaultValue; return isset($this->_cookies[$name]) ? $this->_cookies[$name]->value : $defaultValue;
} }
...@@ -102,7 +108,13 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ ...@@ -102,7 +108,13 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
$c = $this->_cookies[$cookie->name]; $c = $this->_cookies[$cookie->name];
setcookie($c->name, '', 0, $c->path, $c->domain, $c->secure, $c->httpOnly); 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; $this->_cookies[$cookie->name] = $cookie;
} }
...@@ -192,4 +204,33 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ ...@@ -192,4 +204,33 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
{ {
$this->remove($name); $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;
}
} }
...@@ -178,15 +178,29 @@ class Request extends \yii\base\Request ...@@ -178,15 +178,29 @@ class Request extends \yii\base\Request
} else { } else {
$this->_restParams = array(); $this->_restParams = array();
if (function_exists('mb_parse_str')) { if (function_exists('mb_parse_str')) {
mb_parse_str(file_get_contents('php://input'), $this->_restParams); mb_parse_str($this->getRawBody(), $this->_restParams);
} else { } else {
parse_str(file_get_contents('php://input'), $this->_restParams); parse_str($this->getRawBody(), $this->_restParams);
} }
} }
} }
return $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. * Sets the RESTful parameters.
* @param array $values the RESTful parameters (name-value pairs) * @param array $values the RESTful parameters (name-value pairs)
...@@ -382,6 +396,11 @@ class Request extends \yii\base\Request ...@@ -382,6 +396,11 @@ class Request extends \yii\base\Request
return $this->_pathInfo; 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) public function setPathInfo($value)
{ {
$this->_pathInfo = trim($value, '/'); $this->_pathInfo = trim($value, '/');
...@@ -403,7 +422,22 @@ class Request extends \yii\base\Request ...@@ -403,7 +422,22 @@ class Request extends \yii\base\Request
$pathInfo = substr($pathInfo, 0, $pos); $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(); $scriptUrl = $this->getScriptUrl();
$baseUrl = $this->getBaseUrl(); $baseUrl = $this->getBaseUrl();
...@@ -414,42 +448,13 @@ class Request extends \yii\base\Request ...@@ -414,42 +448,13 @@ class Request extends \yii\base\Request
} elseif (strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) { } elseif (strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) {
$pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl)); $pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl));
} else { } else {
return false; throw new InvalidConfigException('Unable to determine the path info of the current request.');
} }
return trim($pathInfo, '/'); 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. * Returns the currently requested URL.
* This is a shortcut to the concatenation of [[hostInfo]] and [[requestUri]]. * This is a shortcut to the concatenation of [[hostInfo]] and [[requestUri]].
* @return string the currently requested URL. * @return string the currently requested URL.
...@@ -714,101 +719,11 @@ class Request extends \yii\base\Request ...@@ -714,101 +719,11 @@ class Request extends \yii\base\Request
public function getCookies() public function getCookies()
{ {
if ($this->_cookies === null) { if ($this->_cookies === null) {
$this->_cookies = new CookieCollection($this->loadCookies()); $this->_cookies = new CookieCollection(array(
'enableValidation' => $this->enableCookieValidation,
));
} }
return $this->_cookies; 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.'));
}
}
}
} }
...@@ -82,6 +82,11 @@ class Response extends \yii\base\Response ...@@ -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 * 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. * will open but the downloaded file will have 0 bytes.
* *
* <b>Known issues</b>:
* 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 <code>Pragma</code>-header.
*
* <b>Example</b>: * <b>Example</b>:
* <pre> * <pre>
* <?php * <?php
...@@ -102,66 +107,58 @@ class Response extends \yii\base\Response ...@@ -102,66 +107,58 @@ class Response extends \yii\base\Response
* <li>forceDownload: specifies whether the file will be downloaded or shown inline, defaults to true. (Since version 1.1.9.)</li> * <li>forceDownload: specifies whether the file will be downloaded or shown inline, defaults to true. (Since version 1.1.9.)</li>
* <li>addHeaders: an array of additional http headers in header-value pairs (available since version 1.1.10)</li> * <li>addHeaders: an array of additional http headers in header-value pairs (available since version 1.1.10)</li>
* </ul> * </ul>
* @todo
*/ */
public function xSendFile($filePath, $options = array()) public function xSendFile($filePath, $options=array())
{ {
if (!isset($options['forceDownload']) || $options['forceDownload']) { if(!isset($options['forceDownload']) || $options['forceDownload'])
$disposition = 'attachment'; $disposition='attachment';
} else { else
$disposition = 'inline'; $disposition='inline';
}
if (!isset($options['saveName'])) { if(!isset($options['saveName']))
$options['saveName'] = basename($filePath); $options['saveName']=basename($filePath);
}
if (!isset($options['mimeType'])) { if(!isset($options['mimeType']))
if (($options['mimeType'] = CFileHelper::getMimeTypeByExtension($filePath)) === null) { {
$options['mimeType'] = 'text/plain'; if(($options['mimeType']=CFileHelper::getMimeTypeByExtension($filePath))===null)
} $options['mimeType']='text/plain';
} }
if (!isset($options['xHeader'])) { if(!isset($options['xHeader']))
$options['xHeader'] = 'X-Sendfile'; $options['xHeader']='X-Sendfile';
}
if ($options['mimeType'] !== null) { if($options['mimeType'] !== null)
header('Content-type: ' . $options['mimeType']); header('Content-type: '.$options['mimeType']);
} header('Content-Disposition: '.$disposition.'; filename="'.$options['saveName'].'"');
header('Content-Disposition: ' . $disposition . '; filename="' . $options['saveName'] . '"'); if(isset($options['addHeaders']))
if (isset($options['addHeaders'])) { {
foreach ($options['addHeaders'] as $header => $value) { foreach($options['addHeaders'] as $header=>$value)
header($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(); Yii::app()->end();
}
} }
/** /**
* Redirects the browser to the specified URL. * 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 * @param string $url URL to be redirected to. Note that when URL is not
* the application will be inserted at the beginning. * absolute (not starting with "/") it will be relative to current request URL.
* @param boolean $terminate whether to terminate the current application * @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} * @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. * 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) { if(strpos($url,'/')===0 && strpos($url,'//')!==0)
$url = $this->getHostInfo() . $url; $url=$this->getHostInfo().$url;
} header('Location: '.$url, true, $statusCode);
header('Location: ' . $url, true, $statusCode); if($terminate)
if ($terminate) {
Yii::app()->end(); Yii::app()->end();
}
} }
/** /**
* Returns the cookie collection. * Returns the cookie collection.
* Through the returned cookie collection, you add or remove cookies as follows, * Through the returned cookie collection, you add or remove cookies as follows,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment