mirror of
https://github.com/yiisoft/yii2.git
synced 2025-08-26 06:15:19 +08:00
Response WIP
This commit is contained in:
@ -20,6 +20,7 @@ class SiteController extends Controller
|
|||||||
|
|
||||||
public function actionIndex()
|
public function actionIndex()
|
||||||
{
|
{
|
||||||
|
Yii::$app->end(0, false);
|
||||||
echo $this->render('index');
|
echo $this->render('index');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +128,11 @@ class Application extends Module
|
|||||||
ini_set('display_errors', 0);
|
ini_set('display_errors', 0);
|
||||||
set_exception_handler(array($this, 'handleException'));
|
set_exception_handler(array($this, 'handleException'));
|
||||||
set_error_handler(array($this, 'handleError'), error_reporting());
|
set_error_handler(array($this, 'handleError'), error_reporting());
|
||||||
|
// Allocating twice more than required to display memory exhausted error
|
||||||
|
// in case of trying to allocate last 1 byte while all memory is taken.
|
||||||
|
$this->_memoryReserve = str_repeat('x', 1024 * 256);
|
||||||
|
register_shutdown_function(array($this, 'end'), 0, false);
|
||||||
|
register_shutdown_function(array($this, 'handleFatalError'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,11 +147,10 @@ class Application extends Module
|
|||||||
{
|
{
|
||||||
if (!$this->_ended) {
|
if (!$this->_ended) {
|
||||||
$this->_ended = true;
|
$this->_ended = true;
|
||||||
|
$this->getResponse()->end();
|
||||||
$this->afterRequest();
|
$this->afterRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->handleFatalError();
|
|
||||||
|
|
||||||
if ($exit) {
|
if ($exit) {
|
||||||
exit($status);
|
exit($status);
|
||||||
}
|
}
|
||||||
@ -160,11 +164,10 @@ class Application extends Module
|
|||||||
public function run()
|
public function run()
|
||||||
{
|
{
|
||||||
$this->beforeRequest();
|
$this->beforeRequest();
|
||||||
// Allocating twice more than required to display memory exhausted error
|
$response = $this->getResponse();
|
||||||
// in case of trying to allocate last 1 byte while all memory is taken.
|
$response->begin();
|
||||||
$this->_memoryReserve = str_repeat('x', 1024 * 256);
|
|
||||||
register_shutdown_function(array($this, 'end'), 0, false);
|
|
||||||
$status = $this->processRequest();
|
$status = $this->processRequest();
|
||||||
|
$response->end();
|
||||||
$this->afterRequest();
|
$this->afterRequest();
|
||||||
return $status;
|
return $status;
|
||||||
}
|
}
|
||||||
@ -314,6 +317,15 @@ class Application extends Module
|
|||||||
return $this->getComponent('request');
|
return $this->getComponent('request');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the response component.
|
||||||
|
* @return \yii\web\Response|\yii\console\Response the response component
|
||||||
|
*/
|
||||||
|
public function getResponse()
|
||||||
|
{
|
||||||
|
return $this->getComponent('response');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the view object.
|
* Returns the view object.
|
||||||
* @return View the view object that is used to render various view files.
|
* @return View the view object that is used to render various view files.
|
||||||
|
@ -82,11 +82,12 @@ class ErrorHandler extends Component
|
|||||||
} elseif (!(Yii::$app instanceof \yii\web\Application)) {
|
} elseif (!(Yii::$app instanceof \yii\web\Application)) {
|
||||||
Yii::$app->renderException($exception);
|
Yii::$app->renderException($exception);
|
||||||
} else {
|
} else {
|
||||||
|
$response = Yii::$app->getResponse();
|
||||||
if (!headers_sent()) {
|
if (!headers_sent()) {
|
||||||
if ($exception instanceof HttpException) {
|
if ($exception instanceof HttpException) {
|
||||||
header('HTTP/1.0 ' . $exception->statusCode . ' ' . $exception->getName());
|
$response->setStatusCode($exception->statusCode);
|
||||||
} else {
|
} else {
|
||||||
header('HTTP/1.0 500 ' . get_class($exception));
|
$response->setStatusCode(500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
|
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
|
||||||
@ -100,13 +101,13 @@ class ErrorHandler extends Component
|
|||||||
|
|
||||||
$view = new View();
|
$view = new View();
|
||||||
$request = '';
|
$request = '';
|
||||||
foreach (array('GET', 'POST', 'SERVER', 'FILES', 'COOKIE', 'SESSION', 'ENV') as $name) {
|
foreach (array('_GET', '_POST', '_SERVER', '_FILES', '_COOKIE', '_SESSION', '_ENV') as $name) {
|
||||||
if (!empty($GLOBALS['_' . $name])) {
|
if (!empty($GLOBALS[$name])) {
|
||||||
$request .= '$_' . $name . ' = ' . var_export($GLOBALS['_' . $name], true) . ";\n\n";
|
$request .= '$' . $name . ' = ' . var_export($GLOBALS[$name], true) . ";\n\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$request = rtrim($request, "\n\n");
|
$request = rtrim($request, "\n\n");
|
||||||
echo $view->renderFile($this->mainView, array(
|
$response->content = $view->renderFile($this->mainView, array(
|
||||||
'exception' => $exception,
|
'exception' => $exception,
|
||||||
'request' => $request,
|
'request' => $request,
|
||||||
), $this);
|
), $this);
|
||||||
|
@ -13,6 +13,9 @@ namespace yii\base;
|
|||||||
*/
|
*/
|
||||||
class Response extends Component
|
class Response extends Component
|
||||||
{
|
{
|
||||||
|
const EVENT_BEGIN_RESPONSE = 'beginResponse';
|
||||||
|
const EVENT_END_RESPONSE = 'endResponse';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts output buffering
|
* Starts output buffering
|
||||||
*/
|
*/
|
||||||
@ -56,4 +59,14 @@ class Response extends Component
|
|||||||
ob_end_clean();
|
ob_end_clean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function begin()
|
||||||
|
{
|
||||||
|
$this->trigger(self::EVENT_BEGIN_RESPONSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function end()
|
||||||
|
{
|
||||||
|
$this->trigger(self::EVENT_END_RESPONSE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
17
framework/yii/console/Response.php
Normal file
17
framework/yii/console/Response.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @link http://www.yiiframework.com/
|
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||||
|
* @license http://www.yiiframework.com/license/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace yii\console;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
class Response extends \yii\base\Response
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -277,11 +277,8 @@ class CaptchaAction extends Action
|
|||||||
|
|
||||||
imagecolordeallocate($image, $foreColor);
|
imagecolordeallocate($image, $foreColor);
|
||||||
|
|
||||||
header('Pragma: public');
|
$this->sendHttpHeaders();
|
||||||
header('Expires: 0');
|
|
||||||
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
|
|
||||||
header('Content-Transfer-Encoding: binary');
|
|
||||||
header("Content-type: image/png");
|
|
||||||
imagepng($image);
|
imagepng($image);
|
||||||
imagedestroy($image);
|
imagedestroy($image);
|
||||||
}
|
}
|
||||||
@ -319,12 +316,21 @@ class CaptchaAction extends Action
|
|||||||
$x += (int)($fontMetrics['textWidth']) + $this->offset;
|
$x += (int)($fontMetrics['textWidth']) + $this->offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
header('Pragma: public');
|
|
||||||
header('Expires: 0');
|
|
||||||
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
|
|
||||||
header('Content-Transfer-Encoding: binary');
|
|
||||||
header("Content-type: image/png");
|
|
||||||
$image->setImageFormat('png');
|
$image->setImageFormat('png');
|
||||||
echo $image;
|
Yii::$app->getResponse()->content = (string)$image;
|
||||||
|
$this->sendHttpHeaders();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the HTTP headers needed by image response.
|
||||||
|
*/
|
||||||
|
protected function sendHttpHeaders()
|
||||||
|
{
|
||||||
|
Yii::$app->getResponse()->getHeaders()
|
||||||
|
->set('Pragma', 'public')
|
||||||
|
->set('Expires', '0')
|
||||||
|
->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
|
||||||
|
->set('Content-Transfer-Encoding', 'binary')
|
||||||
|
->set('Content-type', 'image/png');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ class Cookie extends \yii\base\Object
|
|||||||
* By setting this property to true, the cookie will not be accessible by scripting languages,
|
* By setting this property to true, the cookie will not be accessible by scripting languages,
|
||||||
* such as JavaScript, which can effectively help to reduce identity theft through XSS attacks.
|
* such as JavaScript, which can effectively help to reduce identity theft through XSS attacks.
|
||||||
*/
|
*/
|
||||||
public $httponly = false;
|
public $httpOnly = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Magic method to turn a cookie object into a string without having to explicitly access [[value]].
|
* Magic method to turn a cookie object into a string without having to explicitly access [[value]].
|
||||||
|
@ -9,7 +9,8 @@ namespace yii\web;
|
|||||||
|
|
||||||
use Yii;
|
use Yii;
|
||||||
use ArrayIterator;
|
use ArrayIterator;
|
||||||
use yii\helpers\SecurityHelper;
|
use yii\base\InvalidCallException;
|
||||||
|
use yii\base\Object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CookieCollection maintains the cookies available in the current request.
|
* CookieCollection maintains the cookies available in the current request.
|
||||||
@ -19,17 +20,12 @@ use yii\helpers\SecurityHelper;
|
|||||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ArrayAccess, \Countable
|
class CookieCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var boolean whether to enable cookie validation. By setting this property to true,
|
* @var boolean whether this collection is read only.
|
||||||
* if a cookie is tampered on the client side, it will be ignored when received on the server side.
|
|
||||||
*/
|
*/
|
||||||
public $enableValidation = true;
|
public $readOnly = false;
|
||||||
/**
|
|
||||||
* @var string the secret key used for cookie validation. If not set, a random key will be generated and used.
|
|
||||||
*/
|
|
||||||
public $validationKey;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Cookie[] the cookies in this collection (indexed by the cookie names)
|
* @var Cookie[] the cookies in this collection (indexed by the cookie names)
|
||||||
@ -38,12 +34,14 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
|
* @param array $cookies the cookies that this collection initially contains. This should be
|
||||||
|
* an array of name-value pairs.s
|
||||||
* @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($config = array())
|
public function __construct($cookies = array(), $config = array())
|
||||||
{
|
{
|
||||||
|
$this->_cookies = $cookies;
|
||||||
parent::__construct($config);
|
parent::__construct($config);
|
||||||
$this->_cookies = $this->loadCookies();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -114,50 +112,53 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
|
|||||||
* Adds a cookie to the collection.
|
* Adds a cookie to the collection.
|
||||||
* If there is already a cookie with the same name in the collection, it will be removed first.
|
* If there is already a cookie with the same name in the collection, it will be removed first.
|
||||||
* @param Cookie $cookie the cookie to be added
|
* @param Cookie $cookie the cookie to be added
|
||||||
|
* @throws InvalidCallException if the cookie collection is read only
|
||||||
*/
|
*/
|
||||||
public function add($cookie)
|
public function add($cookie)
|
||||||
{
|
{
|
||||||
if (isset($this->_cookies[$cookie->name])) {
|
if ($this->readOnly) {
|
||||||
$c = $this->_cookies[$cookie->name];
|
throw new InvalidCallException('The cookie collection is read only.');
|
||||||
setcookie($c->name, '', 0, $c->path, $c->domain, $c->secure, $c->httponly);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$value = $cookie->value;
|
|
||||||
if ($this->enableValidation) {
|
|
||||||
if ($this->validationKey === null) {
|
|
||||||
$key = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id);
|
|
||||||
} else {
|
|
||||||
$key = $this->validationKey;
|
|
||||||
}
|
|
||||||
$value = SecurityHelper::hashData(serialize($value), $key);
|
|
||||||
}
|
|
||||||
|
|
||||||
setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly);
|
|
||||||
$this->_cookies[$cookie->name] = $cookie;
|
$this->_cookies[$cookie->name] = $cookie;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a cookie from the collection.
|
* Removes a cookie.
|
||||||
|
* If `$removeFromBrowser` is true, the cookie will be removed from the browser.
|
||||||
|
* In this case, a cookie with outdated expiry will be added to the collection.
|
||||||
* @param Cookie|string $cookie the cookie object or the name of the cookie to be removed.
|
* @param Cookie|string $cookie the cookie object or the name of the cookie to be removed.
|
||||||
|
* @param boolean $removeFromBrowser whether to remove the cookie from browser
|
||||||
|
* @throws InvalidCallException if the cookie collection is read only
|
||||||
*/
|
*/
|
||||||
public function remove($cookie)
|
public function remove($cookie, $removeFromBrowser = true)
|
||||||
{
|
{
|
||||||
if (is_string($cookie) && isset($this->_cookies[$cookie])) {
|
if ($this->readOnly) {
|
||||||
$cookie = $this->_cookies[$cookie];
|
throw new InvalidCallException('The cookie collection is read only.');
|
||||||
}
|
}
|
||||||
if ($cookie instanceof Cookie) {
|
if ($cookie instanceof Cookie) {
|
||||||
setcookie($cookie->name, '', 0, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly);
|
$cookie->expire = 1;
|
||||||
|
$cookie->value = '';
|
||||||
|
} else {
|
||||||
|
$cookie = new Cookie(array(
|
||||||
|
'name' => $cookie,
|
||||||
|
'expire' => 1,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if ($removeFromBrowser) {
|
||||||
|
$this->_cookies[$cookie->name] = $cookie;
|
||||||
|
} else {
|
||||||
unset($this->_cookies[$cookie->name]);
|
unset($this->_cookies[$cookie->name]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all cookies.
|
* Removes all cookies.
|
||||||
|
* @throws InvalidCallException if the cookie collection is read only
|
||||||
*/
|
*/
|
||||||
public function removeAll()
|
public function removeAll()
|
||||||
{
|
{
|
||||||
foreach ($this->_cookies as $cookie) {
|
if ($this->readOnly) {
|
||||||
setcookie($cookie->name, '', 0, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly);
|
throw new InvalidCallException('The cookie collection is read only.');
|
||||||
}
|
}
|
||||||
$this->_cookies = array();
|
$this->_cookies = array();
|
||||||
}
|
}
|
||||||
@ -222,36 +223,4 @@ 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) {
|
|
||||||
if ($this->validationKey === null) {
|
|
||||||
$key = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id);
|
|
||||||
} else {
|
|
||||||
$key = $this->validationKey;
|
|
||||||
}
|
|
||||||
foreach ($_COOKIE as $name => $value) {
|
|
||||||
if (is_string($value) && ($value = SecurityHelper::validateData($value, $key)) !== 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -79,11 +79,13 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces
|
|||||||
* If there is already a header with the same name, it will be replaced.
|
* If there is already a header with the same name, it will be replaced.
|
||||||
* @param string $name the name of the header
|
* @param string $name the name of the header
|
||||||
* @param string $value the value of the header
|
* @param string $value the value of the header
|
||||||
|
* @return HeaderCollection the collection object itself
|
||||||
*/
|
*/
|
||||||
public function set($name, $value)
|
public function set($name, $value = '')
|
||||||
{
|
{
|
||||||
$name = strtolower($name);
|
$name = strtolower($name);
|
||||||
$this->_headers[$name] = (array)$value;
|
$this->_headers[$name] = (array)$value;
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,11 +94,13 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces
|
|||||||
* be appended to it instead of replacing it.
|
* be appended to it instead of replacing it.
|
||||||
* @param string $name the name of the header
|
* @param string $name the name of the header
|
||||||
* @param string $value the value of the header
|
* @param string $value the value of the header
|
||||||
|
* @return HeaderCollection the collection object itself
|
||||||
*/
|
*/
|
||||||
public function add($name, $value)
|
public function add($name, $value)
|
||||||
{
|
{
|
||||||
$name = strtolower($name);
|
$name = strtolower($name);
|
||||||
$this->_headers[$name][] = $value;
|
$this->_headers[$name][] = $value;
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,7 +50,7 @@ class HttpCache extends ActionFilter
|
|||||||
/**
|
/**
|
||||||
* @var string HTTP cache control header. If null, the header will not be sent.
|
* @var string HTTP cache control header. If null, the header will not be sent.
|
||||||
*/
|
*/
|
||||||
public $cacheControlHeader = 'Cache-Control: max-age=3600, public';
|
public $cacheControlHeader = 'max-age=3600, public';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is invoked right before an action is to be executed (after all possible filters.)
|
* This method is invoked right before an action is to be executed (after all possible filters.)
|
||||||
@ -60,7 +60,7 @@ class HttpCache extends ActionFilter
|
|||||||
*/
|
*/
|
||||||
public function beforeAction($action)
|
public function beforeAction($action)
|
||||||
{
|
{
|
||||||
$verb = Yii::$app->request->getMethod();
|
$verb = Yii::$app->getRequest()->getMethod();
|
||||||
if ($verb !== 'GET' && $verb !== 'HEAD' || $this->lastModified === null && $this->etagSeed === null) {
|
if ($verb !== 'GET' && $verb !== 'HEAD' || $this->lastModified === null && $this->etagSeed === null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -75,17 +75,18 @@ class HttpCache extends ActionFilter
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->sendCacheControlHeader();
|
$this->sendCacheControlHeader();
|
||||||
|
$response = Yii::$app->getResponse();
|
||||||
if ($etag !== null) {
|
if ($etag !== null) {
|
||||||
header("ETag: $etag");
|
$response->getHeaders()->set('Etag', $etag);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->validateCache($lastModified, $etag)) {
|
if ($this->validateCache($lastModified, $etag)) {
|
||||||
header('HTTP/1.1 304 Not Modified');
|
$response->setStatusCode(304);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($lastModified !== null) {
|
if ($lastModified !== null) {
|
||||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
|
$response->getHeaders()->set('Last-Modified', gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -113,9 +114,10 @@ class HttpCache extends ActionFilter
|
|||||||
protected function sendCacheControlHeader()
|
protected function sendCacheControlHeader()
|
||||||
{
|
{
|
||||||
session_cache_limiter('public');
|
session_cache_limiter('public');
|
||||||
header('Pragma:', true);
|
$headers = Yii::$app->getResponse()->getHeaders();
|
||||||
|
$headers->set('Pragma');
|
||||||
if ($this->cacheControlHeader !== null) {
|
if ($this->cacheControlHeader !== null) {
|
||||||
header($this->cacheControlHeader, true);
|
$headers->set('Cache-Control', $this->cacheControlHeader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ namespace yii\web;
|
|||||||
use Yii;
|
use Yii;
|
||||||
use yii\base\HttpException;
|
use yii\base\HttpException;
|
||||||
use yii\base\InvalidConfigException;
|
use yii\base\InvalidConfigException;
|
||||||
|
use yii\helpers\SecurityHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||||
@ -37,15 +38,11 @@ class Request extends \yii\base\Request
|
|||||||
* @var array the configuration of the CSRF cookie. This property is used only when [[enableCsrfValidation]] is true.
|
* @var array the configuration of the CSRF cookie. This property is used only when [[enableCsrfValidation]] is true.
|
||||||
* @see Cookie
|
* @see Cookie
|
||||||
*/
|
*/
|
||||||
public $csrfCookie = array('httponly' => true);
|
public $csrfCookie = array('httpOnly' => true);
|
||||||
/**
|
/**
|
||||||
* @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to true.
|
* @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to true.
|
||||||
*/
|
*/
|
||||||
public $enableCookieValidation = true;
|
public $enableCookieValidation = true;
|
||||||
/**
|
|
||||||
* @var string the secret key used for cookie validation. If not set, a random key will be generated and used.
|
|
||||||
*/
|
|
||||||
public $cookieValidationKey;
|
|
||||||
/**
|
/**
|
||||||
* @var string|boolean the name of the POST parameter that is used to indicate if a request is a PUT or DELETE
|
* @var string|boolean the name of the POST parameter that is used to indicate if a request is a PUT or DELETE
|
||||||
* request tunneled through POST. Default to '_method'.
|
* request tunneled through POST. Default to '_method'.
|
||||||
@ -717,14 +714,64 @@ class Request extends \yii\base\Request
|
|||||||
public function getCookies()
|
public function getCookies()
|
||||||
{
|
{
|
||||||
if ($this->_cookies === null) {
|
if ($this->_cookies === null) {
|
||||||
$this->_cookies = new CookieCollection(array(
|
$this->_cookies = new CookieCollection($this->loadCookies(), array(
|
||||||
'enableValidation' => $this->enableCookieValidation,
|
'readOnly' => true,
|
||||||
'validationKey' => $this->cookieValidationKey,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
return $this->_cookies;
|
return $this->_cookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts `$_COOKIE` into an array of [[Cookie]].
|
||||||
|
* @return array the cookies obtained from request
|
||||||
|
*/
|
||||||
|
protected function loadCookies()
|
||||||
|
{
|
||||||
|
$cookies = array();
|
||||||
|
if ($this->enableCookieValidation) {
|
||||||
|
$key = $this->getCookieValidationKey();
|
||||||
|
foreach ($_COOKIE as $name => $value) {
|
||||||
|
if (is_string($value) && ($value = SecurityHelper::validateData($value, $key)) !== 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private $_cookieValidationKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string the secret key used for cookie validation. If it was set previously,
|
||||||
|
* a random key will be generated and used.
|
||||||
|
*/
|
||||||
|
public function getCookieValidationKey()
|
||||||
|
{
|
||||||
|
if ($this->_cookieValidationKey === null) {
|
||||||
|
$this->_cookieValidationKey = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id);
|
||||||
|
}
|
||||||
|
return $this->_cookieValidationKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the secret key used for cookie validation.
|
||||||
|
* @param string $value the secret key used for cookie validation.
|
||||||
|
*/
|
||||||
|
public function setCookieValidationKey($value)
|
||||||
|
{
|
||||||
|
$this->_cookieValidationKey = $value;
|
||||||
|
}
|
||||||
|
|
||||||
private $_csrfToken;
|
private $_csrfToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,6 +13,7 @@ use yii\base\InvalidParamException;
|
|||||||
use yii\helpers\FileHelper;
|
use yii\helpers\FileHelper;
|
||||||
use yii\helpers\Html;
|
use yii\helpers\Html;
|
||||||
use yii\helpers\Json;
|
use yii\helpers\Json;
|
||||||
|
use yii\helpers\SecurityHelper;
|
||||||
use yii\helpers\StringHelper;
|
use yii\helpers\StringHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,18 +132,35 @@ class Response extends \yii\base\Response
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function begin()
|
||||||
|
{
|
||||||
|
parent::begin();
|
||||||
|
$this->beginOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function end()
|
||||||
|
{
|
||||||
|
$this->content .= $this->endOutput();
|
||||||
|
$this->send();
|
||||||
|
parent::end();
|
||||||
|
}
|
||||||
|
|
||||||
public function getStatusCode()
|
public function getStatusCode()
|
||||||
{
|
{
|
||||||
return $this->_statusCode;
|
return $this->_statusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setStatusCode($value)
|
public function setStatusCode($value, $text = null)
|
||||||
{
|
{
|
||||||
$this->_statusCode = (int)$value;
|
$this->_statusCode = (int)$value;
|
||||||
if ($this->isInvalid()) {
|
if ($this->isInvalid()) {
|
||||||
throw new InvalidParamException("The HTTP status code is invalid: $value");
|
throw new InvalidParamException("The HTTP status code is invalid: $value");
|
||||||
}
|
}
|
||||||
$this->statusText = isset(self::$statusTexts[$this->_statusCode]) ? self::$statusTexts[$this->_statusCode] : '';
|
if ($text === null) {
|
||||||
|
$this->statusText = isset(self::$statusTexts[$this->_statusCode]) ? self::$statusTexts[$this->_statusCode] : '';
|
||||||
|
} else {
|
||||||
|
$this->statusText = $text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -186,13 +204,42 @@ class Response extends \yii\base\Response
|
|||||||
*/
|
*/
|
||||||
protected function sendHeaders()
|
protected function sendHeaders()
|
||||||
{
|
{
|
||||||
header("HTTP/{$this->version} " . $this->getStatusCode() . " {$this->statusText}");
|
if (headers_sent()) {
|
||||||
foreach ($this->_headers as $name => $values) {
|
return;
|
||||||
foreach ($values as $value) {
|
|
||||||
header("$name: $value");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$this->_headers->removeAll();
|
header("HTTP/{$this->version} " . $this->getStatusCode() . " {$this->statusText}");
|
||||||
|
if ($this->_headers) {
|
||||||
|
$headers = $this->getHeaders();
|
||||||
|
foreach ($headers as $name => $values) {
|
||||||
|
foreach ($values as $value) {
|
||||||
|
header("$name: $value", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$headers->removeAll();
|
||||||
|
}
|
||||||
|
$this->sendCookies();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the cookies to the client.
|
||||||
|
*/
|
||||||
|
protected function sendCookies()
|
||||||
|
{
|
||||||
|
if ($this->_cookies === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$request = Yii::$app->getRequest();
|
||||||
|
if ($request->enableCookieValidation) {
|
||||||
|
$validationKey = $request->getCookieValidationKey();
|
||||||
|
}
|
||||||
|
foreach ($this->getCookies() as $cookie) {
|
||||||
|
$value = $cookie->value;
|
||||||
|
if ($cookie->expire != 1 && isset($validationKey)) {
|
||||||
|
$value = SecurityHelper::hashData(serialize($value), $validationKey);
|
||||||
|
}
|
||||||
|
setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly);
|
||||||
|
}
|
||||||
|
$this->getCookies()->removeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -222,13 +269,15 @@ class Response extends \yii\base\Response
|
|||||||
$contentStart = 0;
|
$contentStart = 0;
|
||||||
$contentEnd = $fileSize - 1;
|
$contentEnd = $fileSize - 1;
|
||||||
|
|
||||||
|
$headers = $this->getHeaders();
|
||||||
|
|
||||||
// tell the client that we accept range requests
|
// tell the client that we accept range requests
|
||||||
header('Accept-Ranges: bytes');
|
$headers->set('Accept-Ranges', 'bytes');
|
||||||
|
|
||||||
if (isset($_SERVER['HTTP_RANGE'])) {
|
if (isset($_SERVER['HTTP_RANGE'])) {
|
||||||
// client sent us a multibyte range, can not hold this one for now
|
// client sent us a multibyte range, can not hold this one for now
|
||||||
if (strpos($_SERVER['HTTP_RANGE'], ',') !== false) {
|
if (strpos($_SERVER['HTTP_RANGE'], ',') !== false) {
|
||||||
header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
|
$headers->set('Content-Range', "bytes $contentStart-$contentEnd/$fileSize");
|
||||||
throw new HttpException(416, 'Requested Range Not Satisfiable');
|
throw new HttpException(416, 'Requested Range Not Satisfiable');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,25 +306,26 @@ class Response extends \yii\base\Response
|
|||||||
$wrongContentStart = ($contentStart > $contentEnd || $contentStart > $fileSize - 1 || $contentStart < 0);
|
$wrongContentStart = ($contentStart > $contentEnd || $contentStart > $fileSize - 1 || $contentStart < 0);
|
||||||
|
|
||||||
if ($wrongContentStart) {
|
if ($wrongContentStart) {
|
||||||
header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
|
$headers->set('Content-Range', "bytes $contentStart-$contentEnd/$fileSize");
|
||||||
throw new HttpException(416, 'Requested Range Not Satisfiable');
|
throw new HttpException(416, 'Requested Range Not Satisfiable');
|
||||||
}
|
}
|
||||||
|
|
||||||
header('HTTP/1.1 206 Partial Content');
|
$this->setStatusCode(206);
|
||||||
header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
|
$headers->set('Content-Range', "bytes $contentStart-$contentEnd/$fileSize");
|
||||||
} else {
|
} else {
|
||||||
header('HTTP/1.1 200 OK');
|
$this->setStatusCode(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
$length = $contentEnd - $contentStart + 1; // Calculate new content length
|
$length = $contentEnd - $contentStart + 1; // Calculate new content length
|
||||||
|
|
||||||
header('Pragma: public');
|
$headers->set('Pragma', 'public')
|
||||||
header('Expires: 0');
|
->set('Expires', '0')
|
||||||
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
|
->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
|
||||||
header('Content-Type: ' . $mimeType);
|
->set('Content-Type', $mimeType)
|
||||||
header('Content-Length: ' . $length);
|
->set('Content-Length', $length)
|
||||||
header('Content-Disposition: attachment; filename="' . $fileName . '"');
|
->set('Content-Disposition', "attachment; filename=\"$fileName\"")
|
||||||
header('Content-Transfer-Encoding: binary');
|
->set('Content-Transfer-Encoding', 'binary');
|
||||||
|
|
||||||
$content = StringHelper::substr($content, $contentStart, $length);
|
$content = StringHelper::substr($content, $contentStart, $length);
|
||||||
|
|
||||||
if ($terminate) {
|
if ($terminate) {
|
||||||
@ -371,16 +421,18 @@ class Response extends \yii\base\Response
|
|||||||
$options['xHeader'] = 'X-Sendfile';
|
$options['xHeader'] = 'X-Sendfile';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$headers = $this->getHeaders();
|
||||||
|
|
||||||
if ($options['mimeType'] !== null) {
|
if ($options['mimeType'] !== null) {
|
||||||
header('Content-type: ' . $options['mimeType']);
|
$headers->set('Content-Type', $options['mimeType']);
|
||||||
}
|
}
|
||||||
header('Content-Disposition: ' . $disposition . '; filename="' . $options['saveName'] . '"');
|
$headers->set('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);
|
$headers->set($header, $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
header(trim($options['xHeader']) . ': ' . $filePath);
|
$headers->set(trim($options['xHeader']), $filePath);
|
||||||
|
|
||||||
if (!isset($options['terminate']) || $options['terminate']) {
|
if (!isset($options['terminate']) || $options['terminate']) {
|
||||||
Yii::$app->end();
|
Yii::$app->end();
|
||||||
@ -422,7 +474,8 @@ class Response extends \yii\base\Response
|
|||||||
if (Yii::$app->getRequest()->getIsAjax()) {
|
if (Yii::$app->getRequest()->getIsAjax()) {
|
||||||
$statusCode = $this->ajaxRedirectCode;
|
$statusCode = $this->ajaxRedirectCode;
|
||||||
}
|
}
|
||||||
header('Location: ' . $url, true, $statusCode);
|
$this->getHeaders()->set('Location', $url);
|
||||||
|
$this->setStatusCode($statusCode);
|
||||||
if ($terminate) {
|
if ($terminate) {
|
||||||
Yii::$app->end();
|
Yii::$app->end();
|
||||||
}
|
}
|
||||||
@ -441,6 +494,8 @@ class Response extends \yii\base\Response
|
|||||||
$this->redirect(Yii::$app->getRequest()->getUrl() . $anchor, $terminate);
|
$this->redirect(Yii::$app->getRequest()->getUrl() . $anchor, $terminate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private $_cookies;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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,
|
||||||
@ -462,7 +517,10 @@ class Response extends \yii\base\Response
|
|||||||
*/
|
*/
|
||||||
public function getCookies()
|
public function getCookies()
|
||||||
{
|
{
|
||||||
return Yii::$app->getRequest()->getCookies();
|
if ($this->_cookies === null) {
|
||||||
|
$this->_cookies = new CookieCollection;
|
||||||
|
}
|
||||||
|
return $this->_cookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,7 +63,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
|
|||||||
* @var array parameter-value pairs to override default session cookie parameters
|
* @var array parameter-value pairs to override default session cookie parameters
|
||||||
*/
|
*/
|
||||||
public $cookieParams = array(
|
public $cookieParams = array(
|
||||||
'httponly' => true
|
'httpOnly' => true
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -241,26 +241,31 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
|
|||||||
*/
|
*/
|
||||||
public function getCookieParams()
|
public function getCookieParams()
|
||||||
{
|
{
|
||||||
return session_get_cookie_params();
|
$params = session_get_cookie_params();
|
||||||
|
if (isset($params['httponly'])) {
|
||||||
|
$params['httpOnly'] = $params['httponly'];
|
||||||
|
unset($params['httponly']);
|
||||||
|
}
|
||||||
|
return $params;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the session cookie parameters.
|
* Sets the session cookie parameters.
|
||||||
* The effect of this method only lasts for the duration of the script.
|
* The effect of this method only lasts for the duration of the script.
|
||||||
* Call this method before the session starts.
|
* Call this method before the session starts.
|
||||||
* @param array $value cookie parameters, valid keys include: lifetime, path, domain, secure and httponly.
|
* @param array $value cookie parameters, valid keys include: `lifetime`, `path`, `domain`, `secure` and `httpOnly`.
|
||||||
* @throws InvalidParamException if the parameters are incomplete.
|
* @throws InvalidParamException if the parameters are incomplete.
|
||||||
* @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
|
* @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
|
||||||
*/
|
*/
|
||||||
public function setCookieParams($value)
|
public function setCookieParams($value)
|
||||||
{
|
{
|
||||||
$data = session_get_cookie_params();
|
$data = $this->getCookieParams();
|
||||||
extract($data);
|
extract($data);
|
||||||
extract($value);
|
extract($value);
|
||||||
if (isset($lifetime, $path, $domain, $secure, $httponly)) {
|
if (isset($lifetime, $path, $domain, $secure, $httpOnly)) {
|
||||||
session_set_cookie_params($lifetime, $path, $domain, $secure, $httponly);
|
session_set_cookie_params($lifetime, $path, $domain, $secure, $httpOnly);
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidParamException('Please make sure these parameters are provided: lifetime, path, domain, secure and httponly.');
|
throw new InvalidParamException('Please make sure these parameters are provided: lifetime, path, domain, secure and httpOnly.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ class User extends Component
|
|||||||
* @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is true.
|
* @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is true.
|
||||||
* @see Cookie
|
* @see Cookie
|
||||||
*/
|
*/
|
||||||
public $identityCookie = array('name' => '_identity', 'httponly' => true);
|
public $identityCookie = array('name' => '_identity', 'httpOnly' => true);
|
||||||
/**
|
/**
|
||||||
* @var integer the number of seconds in which the user will be logged out automatically if he
|
* @var integer the number of seconds in which the user will be logged out automatically if he
|
||||||
* remains inactive. If this property is not set, the user will be logged out after
|
* remains inactive. If this property is not set, the user will be logged out after
|
||||||
|
@ -81,7 +81,7 @@ class VerbFilter extends Behavior
|
|||||||
if (!in_array($verb, $allowed)) {
|
if (!in_array($verb, $allowed)) {
|
||||||
$event->isValid = false;
|
$event->isValid = false;
|
||||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
|
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
|
||||||
header('Allow: ' . implode(', ', $allowed));
|
Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $allowed));
|
||||||
throw new HttpException(405, 'Method Not Allowed. This url can only handle the following request methods: ' . implode(', ', $allowed));
|
throw new HttpException(405, 'Method Not Allowed. This url can only handle the following request methods: ' . implode(', ', $allowed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user