Commit f2d477dc by Qiang Xue

Response WIP

parent cf1e12ad
...@@ -20,6 +20,7 @@ class SiteController extends Controller ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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;
} }
...@@ -315,6 +318,15 @@ class Application extends Module ...@@ -315,6 +318,15 @@ class Application extends Module
} }
/** /**
* 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 ...@@ -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 ...@@ -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; ...@@ -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 ...@@ -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);
}
} }
<?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 ...@@ -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 ...@@ -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 ...@@ -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; ...@@ -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; ...@@ -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, \ ...@@ -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, \ ...@@ -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, \ ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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; ...@@ -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,16 +38,12 @@ class Request extends \yii\base\Request ...@@ -37,16 +38,12 @@ 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'.
* @see getMethod * @see getMethod
...@@ -717,14 +714,64 @@ class Request extends \yii\base\Request ...@@ -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; ...@@ -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 ...@@ -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 ...@@ -186,13 +204,42 @@ class Response extends \yii\base\Response
*/ */
protected function sendHeaders() protected function sendHeaders()
{ {
if (headers_sent()) {
return;
}
header("HTTP/{$this->version} " . $this->getStatusCode() . " {$this->statusText}"); header("HTTP/{$this->version} " . $this->getStatusCode() . " {$this->statusText}");
foreach ($this->_headers as $name => $values) { if ($this->_headers) {
foreach ($values as $value) { $headers = $this->getHeaders();
header("$name: $value"); 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->_headers->removeAll(); $this->getCookies()->removeAll();
} }
/** /**
...@@ -222,13 +269,15 @@ class Response extends \yii\base\Response ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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));
} }
} }
......
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