From f2d477dce6ab0d5f215e8a4e285e8eda646eb673 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 9 Jun 2013 09:47:41 -0400 Subject: [PATCH] Response WIP --- apps/basic/controllers/SiteController.php | 1 + framework/yii/base/Application.php | 24 +++++-- framework/yii/base/ErrorHandler.php | 13 ++-- framework/yii/base/Response.php | 13 ++++ framework/yii/console/Response.php | 17 +++++ framework/yii/web/CaptchaAction.php | 28 +++++--- framework/yii/web/Cookie.php | 2 +- framework/yii/web/CookieCollection.php | 99 ++++++++++----------------- framework/yii/web/HeaderCollection.php | 6 +- framework/yii/web/HttpCache.php | 16 +++-- framework/yii/web/Request.php | 63 ++++++++++++++--- framework/yii/web/Response.php | 108 +++++++++++++++++++++++------- framework/yii/web/Session.php | 19 ++++-- framework/yii/web/User.php | 2 +- framework/yii/web/VerbFilter.php | 2 +- 15 files changed, 274 insertions(+), 139 deletions(-) create mode 100644 framework/yii/console/Response.php diff --git a/apps/basic/controllers/SiteController.php b/apps/basic/controllers/SiteController.php index ff3b8b4..9d1922b 100644 --- a/apps/basic/controllers/SiteController.php +++ b/apps/basic/controllers/SiteController.php @@ -20,6 +20,7 @@ class SiteController extends Controller public function actionIndex() { + Yii::$app->end(0, false); echo $this->render('index'); } diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index 09951bd..f5f3d6a 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -128,6 +128,11 @@ class Application extends Module ini_set('display_errors', 0); set_exception_handler(array($this, 'handleException')); 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) { $this->_ended = true; + $this->getResponse()->end(); $this->afterRequest(); } - $this->handleFatalError(); - if ($exit) { exit($status); } @@ -160,11 +164,10 @@ class Application extends Module public function run() { $this->beforeRequest(); - // 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); + $response = $this->getResponse(); + $response->begin(); $status = $this->processRequest(); + $response->end(); $this->afterRequest(); return $status; } @@ -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. * @return View the view object that is used to render various view files. */ diff --git a/framework/yii/base/ErrorHandler.php b/framework/yii/base/ErrorHandler.php index 7bf9e7e..4e3e92a 100644 --- a/framework/yii/base/ErrorHandler.php +++ b/framework/yii/base/ErrorHandler.php @@ -82,11 +82,12 @@ class ErrorHandler extends Component } elseif (!(Yii::$app instanceof \yii\web\Application)) { Yii::$app->renderException($exception); } else { + $response = Yii::$app->getResponse(); if (!headers_sent()) { if ($exception instanceof HttpException) { - header('HTTP/1.0 ' . $exception->statusCode . ' ' . $exception->getName()); + $response->setStatusCode($exception->statusCode); } 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') { @@ -100,13 +101,13 @@ class ErrorHandler extends Component $view = new View(); $request = ''; - foreach (array('GET', 'POST', 'SERVER', 'FILES', 'COOKIE', 'SESSION', 'ENV') as $name) { - if (!empty($GLOBALS['_' . $name])) { - $request .= '$_' . $name . ' = ' . var_export($GLOBALS['_' . $name], true) . ";\n\n"; + foreach (array('_GET', '_POST', '_SERVER', '_FILES', '_COOKIE', '_SESSION', '_ENV') as $name) { + if (!empty($GLOBALS[$name])) { + $request .= '$' . $name . ' = ' . var_export($GLOBALS[$name], true) . ";\n\n"; } } $request = rtrim($request, "\n\n"); - echo $view->renderFile($this->mainView, array( + $response->content = $view->renderFile($this->mainView, array( 'exception' => $exception, 'request' => $request, ), $this); diff --git a/framework/yii/base/Response.php b/framework/yii/base/Response.php index 396b073..b89b537 100644 --- a/framework/yii/base/Response.php +++ b/framework/yii/base/Response.php @@ -13,6 +13,9 @@ namespace yii\base; */ class Response extends Component { + const EVENT_BEGIN_RESPONSE = 'beginResponse'; + const EVENT_END_RESPONSE = 'endResponse'; + /** * Starts output buffering */ @@ -56,4 +59,14 @@ class Response extends Component ob_end_clean(); } } + + public function begin() + { + $this->trigger(self::EVENT_BEGIN_RESPONSE); + } + + public function end() + { + $this->trigger(self::EVENT_END_RESPONSE); + } } diff --git a/framework/yii/console/Response.php b/framework/yii/console/Response.php new file mode 100644 index 0000000..34f105d --- /dev/null +++ b/framework/yii/console/Response.php @@ -0,0 +1,17 @@ + + * @since 2.0 + */ +class Response extends \yii\base\Response +{ + +} diff --git a/framework/yii/web/CaptchaAction.php b/framework/yii/web/CaptchaAction.php index cff2314..1ed1fb0 100644 --- a/framework/yii/web/CaptchaAction.php +++ b/framework/yii/web/CaptchaAction.php @@ -277,11 +277,8 @@ class CaptchaAction extends Action imagecolordeallocate($image, $foreColor); - 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"); + $this->sendHttpHeaders(); + imagepng($image); imagedestroy($image); } @@ -319,12 +316,21 @@ class CaptchaAction extends Action $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'); - 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'); } } diff --git a/framework/yii/web/Cookie.php b/framework/yii/web/Cookie.php index 610e5aa..8cbb412 100644 --- a/framework/yii/web/Cookie.php +++ b/framework/yii/web/Cookie.php @@ -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, * 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]]. diff --git a/framework/yii/web/CookieCollection.php b/framework/yii/web/CookieCollection.php index fc9375e..3e22092 100644 --- a/framework/yii/web/CookieCollection.php +++ b/framework/yii/web/CookieCollection.php @@ -9,7 +9,8 @@ namespace yii\web; use Yii; use ArrayIterator; -use yii\helpers\SecurityHelper; +use yii\base\InvalidCallException; +use yii\base\Object; /** * CookieCollection maintains the cookies available in the current request. @@ -19,17 +20,12 @@ use yii\helpers\SecurityHelper; * @author Qiang Xue * @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, - * if a cookie is tampered on the client side, it will be ignored when received on the server side. + * @var boolean whether this collection is read only. */ - public $enableValidation = true; - /** - * @var string the secret key used for cookie validation. If not set, a random key will be generated and used. - */ - public $validationKey; + public $readOnly = false; /** * @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. + * @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 */ - public function __construct($config = array()) + public function __construct($cookies = array(), $config = array()) { + $this->_cookies = $cookies; 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. * 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 + * @throws InvalidCallException if the cookie collection is read only */ public function add($cookie) { - if (isset($this->_cookies[$cookie->name])) { - $c = $this->_cookies[$cookie->name]; - 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); + if ($this->readOnly) { + throw new InvalidCallException('The cookie collection is read only.'); } - - setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly); $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 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])) { - $cookie = $this->_cookies[$cookie]; + if ($this->readOnly) { + throw new InvalidCallException('The cookie collection is read only.'); } 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]); } } /** * Removes all cookies. + * @throws InvalidCallException if the cookie collection is read only */ public function removeAll() { - foreach ($this->_cookies as $cookie) { - setcookie($cookie->name, '', 0, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly); + if ($this->readOnly) { + throw new InvalidCallException('The cookie collection is read only.'); } $this->_cookies = array(); } @@ -222,36 +223,4 @@ 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) { - 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; - } } diff --git a/framework/yii/web/HeaderCollection.php b/framework/yii/web/HeaderCollection.php index c7e1462..ed9ec6f 100644 --- a/framework/yii/web/HeaderCollection.php +++ b/framework/yii/web/HeaderCollection.php @@ -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. * @param string $name the name 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); $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. * @param string $name the name of the header * @param string $value the value of the header + * @return HeaderCollection the collection object itself */ public function add($name, $value) { $name = strtolower($name); $this->_headers[$name][] = $value; + return $this; } /** diff --git a/framework/yii/web/HttpCache.php b/framework/yii/web/HttpCache.php index 5b7682d..cc9e6ed 100644 --- a/framework/yii/web/HttpCache.php +++ b/framework/yii/web/HttpCache.php @@ -50,7 +50,7 @@ class HttpCache extends ActionFilter /** * @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.) @@ -60,7 +60,7 @@ class HttpCache extends ActionFilter */ 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) { return true; } @@ -75,17 +75,18 @@ class HttpCache extends ActionFilter } $this->sendCacheControlHeader(); + $response = Yii::$app->getResponse(); if ($etag !== null) { - header("ETag: $etag"); + $response->getHeaders()->set('Etag', $etag); } if ($this->validateCache($lastModified, $etag)) { - header('HTTP/1.1 304 Not Modified'); + $response->setStatusCode(304); return false; } 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; } @@ -113,9 +114,10 @@ class HttpCache extends ActionFilter protected function sendCacheControlHeader() { session_cache_limiter('public'); - header('Pragma:', true); + $headers = Yii::$app->getResponse()->getHeaders(); + $headers->set('Pragma'); if ($this->cacheControlHeader !== null) { - header($this->cacheControlHeader, true); + $headers->set('Cache-Control', $this->cacheControlHeader); } } diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 7cec044..6f5cdb5 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -10,6 +10,7 @@ namespace yii\web; use Yii; use yii\base\HttpException; use yii\base\InvalidConfigException; +use yii\helpers\SecurityHelper; /** * @author Qiang Xue @@ -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. * @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. */ 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 * request tunneled through POST. Default to '_method'. * @see getMethod @@ -717,14 +714,64 @@ class Request extends \yii\base\Request public function getCookies() { if ($this->_cookies === null) { - $this->_cookies = new CookieCollection(array( - 'enableValidation' => $this->enableCookieValidation, - 'validationKey' => $this->cookieValidationKey, + $this->_cookies = new CookieCollection($this->loadCookies(), array( + 'readOnly' => true, )); } 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; /** diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index 86978d5..a16d04d 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -13,6 +13,7 @@ use yii\base\InvalidParamException; use yii\helpers\FileHelper; use yii\helpers\Html; use yii\helpers\Json; +use yii\helpers\SecurityHelper; 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() { return $this->_statusCode; } - public function setStatusCode($value) + public function setStatusCode($value, $text = null) { $this->_statusCode = (int)$value; if ($this->isInvalid()) { 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() { + if (headers_sent()) { + return; + } header("HTTP/{$this->version} " . $this->getStatusCode() . " {$this->statusText}"); - foreach ($this->_headers as $name => $values) { - foreach ($values as $value) { - header("$name: $value"); + 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->_headers->removeAll(); + $this->getCookies()->removeAll(); } /** @@ -222,13 +269,15 @@ class Response extends \yii\base\Response $contentStart = 0; $contentEnd = $fileSize - 1; + $headers = $this->getHeaders(); + // tell the client that we accept range requests - header('Accept-Ranges: bytes'); + $headers->set('Accept-Ranges', 'bytes'); if (isset($_SERVER['HTTP_RANGE'])) { // client sent us a multibyte range, can not hold this one for now 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'); } @@ -257,25 +306,26 @@ class Response extends \yii\base\Response $wrongContentStart = ($contentStart > $contentEnd || $contentStart > $fileSize - 1 || $contentStart < 0); 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'); } - header('HTTP/1.1 206 Partial Content'); - header("Content-Range: bytes $contentStart-$contentEnd/$fileSize"); + $this->setStatusCode(206); + $headers->set('Content-Range', "bytes $contentStart-$contentEnd/$fileSize"); } else { - header('HTTP/1.1 200 OK'); + $this->setStatusCode(200); } $length = $contentEnd - $contentStart + 1; // Calculate new content length - header('Pragma: public'); - header('Expires: 0'); - header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header('Content-Type: ' . $mimeType); - header('Content-Length: ' . $length); - header('Content-Disposition: attachment; filename="' . $fileName . '"'); - header('Content-Transfer-Encoding: binary'); + $headers->set('Pragma', 'public') + ->set('Expires', '0') + ->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') + ->set('Content-Type', $mimeType) + ->set('Content-Length', $length) + ->set('Content-Disposition', "attachment; filename=\"$fileName\"") + ->set('Content-Transfer-Encoding', 'binary'); + $content = StringHelper::substr($content, $contentStart, $length); if ($terminate) { @@ -371,16 +421,18 @@ class Response extends \yii\base\Response $options['xHeader'] = 'X-Sendfile'; } + $headers = $this->getHeaders(); + 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'])) { 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']) { Yii::$app->end(); @@ -422,7 +474,8 @@ class Response extends \yii\base\Response if (Yii::$app->getRequest()->getIsAjax()) { $statusCode = $this->ajaxRedirectCode; } - header('Location: ' . $url, true, $statusCode); + $this->getHeaders()->set('Location', $url); + $this->setStatusCode($statusCode); if ($terminate) { Yii::$app->end(); } @@ -441,6 +494,8 @@ class Response extends \yii\base\Response $this->redirect(Yii::$app->getRequest()->getUrl() . $anchor, $terminate); } + private $_cookies; + /** * Returns the cookie collection. * 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() { - return Yii::$app->getRequest()->getCookies(); + if ($this->_cookies === null) { + $this->_cookies = new CookieCollection; + } + return $this->_cookies; } /** diff --git a/framework/yii/web/Session.php b/framework/yii/web/Session.php index 1b48433..cf1fa21 100644 --- a/framework/yii/web/Session.php +++ b/framework/yii/web/Session.php @@ -63,7 +63,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co * @var array parameter-value pairs to override default session cookie parameters */ public $cookieParams = array( - 'httponly' => true + 'httpOnly' => true ); /** @@ -241,26 +241,31 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co */ 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. * The effect of this method only lasts for the duration of the script. * 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. * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php */ public function setCookieParams($value) { - $data = session_get_cookie_params(); + $data = $this->getCookieParams(); extract($data); extract($value); - if (isset($lifetime, $path, $domain, $secure, $httponly)) { - session_set_cookie_params($lifetime, $path, $domain, $secure, $httponly); + if (isset($lifetime, $path, $domain, $secure, $httpOnly)) { + session_set_cookie_params($lifetime, $path, $domain, $secure, $httpOnly); } 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.'); } } diff --git a/framework/yii/web/User.php b/framework/yii/web/User.php index 005f987..7ea561c 100644 --- a/framework/yii/web/User.php +++ b/framework/yii/web/User.php @@ -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. * @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 * remains inactive. If this property is not set, the user will be logged out after diff --git a/framework/yii/web/VerbFilter.php b/framework/yii/web/VerbFilter.php index ca6d47d..2b7567f 100644 --- a/framework/yii/web/VerbFilter.php +++ b/framework/yii/web/VerbFilter.php @@ -81,7 +81,7 @@ class VerbFilter extends Behavior if (!in_array($verb, $allowed)) { $event->isValid = false; // 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)); } }