diff --git a/src/Http/Context.php b/src/Http/Context.php index 86301b98..d1073eb7 100644 --- a/src/Http/Context.php +++ b/src/Http/Context.php @@ -37,14 +37,14 @@ public function __construct(IRequest $request, IResponse $response) /** * Attempts to cache the sent entity by its last modification date. - * @param string|int|DateTime last modified time + * @param string|int|\DateTime last modified time * @param string strong entity tag validator * @return bool */ public function isModified($lastModified = NULL, $etag = NULL) { if ($lastModified) { - $this->response->setHeader('Last-Modified', $this->response->date($lastModified)); + $this->response->setHeader('Last-Modified', Helpers::formatDate($lastModified)); } if ($etag) { $this->response->setHeader('ETag', '"' . addslashes($etag) . '"'); diff --git a/src/Http/FileUpload.php b/src/Http/FileUpload.php index 95f8bf5f..4a188302 100644 --- a/src/Http/FileUpload.php +++ b/src/Http/FileUpload.php @@ -17,14 +17,14 @@ * * @property-read string $name * @property-read string $sanitizedName - * @property-read string $contentType + * @property-read string|NULL $contentType * @property-read int $size * @property-read string $temporaryFile * @property-read int $error * @property-read bool $ok * @property-read bool $image - * @property-read array $imageSize - * @property-read string $contents + * @property-read array|NULL $imageSize + * @property-read string|NULL $contents */ class FileUpload extends Nette\Object { @@ -81,7 +81,7 @@ public function getSanitizedName() /** * Returns the MIME content type of an uploaded file. - * @return string + * @return string|NULL */ public function getContentType() { @@ -173,6 +173,7 @@ public function isImage() /** * Returns the image. * @return Nette\Utils\Image + * @throws Nette\Utils\ImageException */ public function toImage() { @@ -182,7 +183,7 @@ public function toImage() /** * Returns the dimensions of an uploaded image as array. - * @return array + * @return array|NULL */ public function getImageSize() { @@ -192,7 +193,7 @@ public function getImageSize() /** * Get file contents. - * @return string + * @return string|NULL */ public function getContents() { diff --git a/src/Http/Helpers.php b/src/Http/Helpers.php index 0cad5293..ef7f7946 100644 --- a/src/Http/Helpers.php +++ b/src/Http/Helpers.php @@ -7,7 +7,8 @@ namespace Nette\Http; -use Nette; +use Nette, + Nette\Utils\DateTime; /** @@ -18,6 +19,19 @@ class Helpers { + /** + * Returns HTTP valid date format. + * @param string|int|\DateTime + * @return string + */ + public static function formatDate($time) + { + $time = DateTime::from($time); + $time->setTimezone(new \DateTimeZone('GMT')); + return $time->format('D, d M Y H:i:s \G\M\T'); + } + + /** * Is IP address in CIDR block? * @return bool diff --git a/src/Http/IRequest.php b/src/Http/IRequest.php index f855436d..338f4e90 100644 --- a/src/Http/IRequest.php +++ b/src/Http/IRequest.php @@ -54,7 +54,7 @@ function getPost($key = NULL, $default = NULL); /** * Returns uploaded file. * @param string key (or more keys) - * @return FileUpload + * @return FileUpload|NULL */ function getFile($key); @@ -122,19 +122,19 @@ function isAjax(); /** * Returns the IP address of the remote client. - * @return string + * @return string|NULL */ function getRemoteAddress(); /** * Returns the host of the remote client. - * @return string + * @return string|NULL */ function getRemoteHost(); /** * Returns raw content of HTTP request body. - * @return string + * @return string|NULL */ function getRawBody(); diff --git a/src/Http/IResponse.php b/src/Http/IResponse.php index e0c40703..a3c7eecd 100644 --- a/src/Http/IResponse.php +++ b/src/Http/IResponse.php @@ -115,7 +115,7 @@ function redirect($url, $code = self::S302_FOUND); /** * Sets the number of seconds before a page cached on a browser expires. - * @param mixed timestamp or number of seconds + * @param string|int|\DateTime time, value 0 means "until the browser is closed" * @return void */ function setExpiration($seconds); @@ -126,9 +126,17 @@ function setExpiration($seconds); */ function isSent(); + /** + * Returns value of an HTTP header. + * @param string + * @param mixed + * @return mixed + */ + function getHeader($header, $default = NULL); + /** * Returns a list of headers to sent. - * @return array + * @return array (name => value) */ function getHeaders(); diff --git a/src/Http/Request.php b/src/Http/Request.php index 44a97aba..d40eab72 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -16,8 +16,8 @@ * @author David Grudl * * @property-read UrlScript $url - * @property-read mixed $query - * @property-read bool $post + * @property-read array $query + * @property-read array $post * @property-read array $files * @property-read array $cookies * @property-read string $method @@ -25,9 +25,9 @@ * @property-read Url|NULL $referer * @property-read bool $secured * @property-read bool $ajax - * @property-read string $remoteAddress - * @property-read string $remoteHost - * @property-read string $rawBody + * @property-read string|NULL $remoteAddress + * @property-read string|NULL $remoteHost + * @property-read string|NULL $rawBody */ class Request extends Nette\Object implements IRequest { @@ -52,22 +52,22 @@ class Request extends Nette\Object implements IRequest /** @var array */ private $headers; - /** @var string */ + /** @var string|NULL */ private $remoteAddress; - /** @var string */ + /** @var string|NULL */ private $remoteHost; - /** @var string */ - private $rawBody; + /** @var callable|NULL */ + private $rawBodyCallback; public function __construct(UrlScript $url, $query = NULL, $post = NULL, $files = NULL, $cookies = NULL, - $headers = NULL, $method = NULL, $remoteAddress = NULL, $remoteHost = NULL) + $headers = NULL, $method = NULL, $remoteAddress = NULL, $remoteHost = NULL, $rawBodyCallback = NULL) { $this->url = $url; if ($query === NULL) { - parse_str($url->query, $this->query); + parse_str($url->getQuery(), $this->query); } else { $this->query = (array) $query; } @@ -75,9 +75,10 @@ public function __construct(UrlScript $url, $query = NULL, $post = NULL, $files $this->files = (array) $files; $this->cookies = (array) $cookies; $this->headers = array_change_key_case((array) $headers, CASE_LOWER); - $this->method = $method; + $this->method = $method ?: 'GET'; $this->remoteAddress = $remoteAddress; $this->remoteHost = $remoteHost; + $this->rawBodyCallback = $rawBodyCallback; } @@ -139,7 +140,7 @@ public function getPost($key = NULL, $default = NULL) /** * Returns uploaded file. * @param string key (or more keys) - * @return FileUpload + * @return FileUpload|NULL */ public function getFile($key) { @@ -204,8 +205,7 @@ public function isMethod($method) /** - * Checks if the request method is POST. - * @return bool + * @deprecated */ public function isPost() { @@ -223,11 +223,7 @@ public function isPost() public function getHeader($header, $default = NULL) { $header = strtolower($header); - if (isset($this->headers[$header])) { - return $this->headers[$header]; - } else { - return $default; - } + return isset($this->headers[$header]) ? $this->headers[$header] : $default; } @@ -257,7 +253,7 @@ public function getReferer() */ public function isSecured() { - return $this->url->scheme === 'https'; + return $this->url->getScheme() === 'https'; } @@ -273,7 +269,7 @@ public function isAjax() /** * Returns the IP address of the remote client. - * @return string + * @return string|NULL */ public function getRemoteAddress() { @@ -283,12 +279,12 @@ public function getRemoteAddress() /** * Returns the host of the remote client. - * @return string + * @return string|NULL */ public function getRemoteHost() { - if (!$this->remoteHost) { - $this->remoteHost = $this->remoteAddress ? getHostByAddr($this->remoteAddress) : NULL; + if ($this->remoteHost === NULL && $this->remoteAddress !== NULL) { + $this->remoteHost = getHostByAddr($this->remoteAddress); } return $this->remoteHost; } @@ -296,25 +292,18 @@ public function getRemoteHost() /** * Returns raw content of HTTP request body. - * @return string + * @return string|NULL */ public function getRawBody() { - if (PHP_VERSION_ID >= 50600) { - return file_get_contents('php://input'); - - } elseif ($this->rawBody === NULL) { // can be read only once in PHP < 5.6 - $this->rawBody = (string) file_get_contents('php://input'); - } - - return $this->rawBody; + return $this->rawBodyCallback ? call_user_func($this->rawBodyCallback) : NULL; } /** - * Parse Accept-Language header and returns prefered language. - * @param array Supported languages - * @return string|null + * Parse Accept-Language header and returns preferred language. + * @param string[] supported languages + * @return string|NULL */ public function detectLanguage(array $langs) { diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 19d9e799..7752654e 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -64,19 +64,19 @@ public function createHttpRequest() { // DETECTS URI, base path and script path of the request. $url = new UrlScript; - $url->scheme = !empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https' : 'http'; - $url->user = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : ''; - $url->password = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''; + $url->setScheme(!empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https' : 'http'); + $url->setUser(isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : ''); + $url->setPassword(isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''); // host & port if ((isset($_SERVER[$tmp = 'HTTP_HOST']) || isset($_SERVER[$tmp = 'SERVER_NAME'])) && preg_match('#^([a-z0-9_.-]+|\[[a-f0-9:]+\])(:\d+)?\z#i', $_SERVER[$tmp], $pair) ) { - $url->host = strtolower($pair[1]); + $url->setHost(strtolower($pair[1])); if (isset($pair[2])) { - $url->port = (int) substr($pair[2], 1); + $url->setPort(substr($pair[2], 1)); } elseif (isset($_SERVER['SERVER_PORT'])) { - $url->port = (int) $_SERVER['SERVER_PORT']; + $url->setPort($_SERVER['SERVER_PORT']); } } @@ -95,12 +95,12 @@ public function createHttpRequest() $requestUrl = Strings::replace($requestUrl, $this->urlFilters['url']); $tmp = explode('?', $requestUrl, 2); - $url->path = Strings::replace($tmp[0], $this->urlFilters['path']); - $url->query = isset($tmp[1]) ? $tmp[1] : ''; + $url->setPath(Strings::replace($tmp[0], $this->urlFilters['path'])); + $url->setQuery(isset($tmp[1]) ? $tmp[1] : ''); // normalized url $url->canonicalize(); - $url->path = Strings::fixEncoding($url->path); + $url->setPath(Strings::fixEncoding($url->getPath())); // detect script path if (isset($_SERVER['SCRIPT_NAME'])) { @@ -113,21 +113,21 @@ public function createHttpRequest() $script = '/'; } - $path = strtolower($url->path) . '/'; + $path = strtolower($url->getPath()) . '/'; $script = strtolower($script) . '/'; $max = min(strlen($path), strlen($script)); for ($i = 0; $i < $max; $i++) { if ($path[$i] !== $script[$i]) { break; } elseif ($path[$i] === '/') { - $url->scriptPath = substr($url->path, 0, $i + 1); + $url->setScriptPath(substr($url->getPath(), 0, $i + 1)); } } // GET, POST, COOKIE $useFilter = (!in_array(ini_get('filter.default'), array('', 'unsafe_raw')) || ini_get('filter.default_flags')); - parse_str($url->query, $query); + parse_str($url->getQuery(), $query); if (!$query) { $query = $useFilter ? filter_input_array(INPUT_GET, FILTER_UNSAFE_RAW) : (empty($_GET) ? array() : $_GET); } @@ -255,7 +255,21 @@ public function createHttpRequest() $method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']; } - return new Request($url, $query, $post, $files, $cookies, $headers, $method, $remoteAddr, $remoteHost); + // raw body + $rawBodyCallback = function() { + static $rawBody; + + if (PHP_VERSION_ID >= 50600) { + return file_get_contents('php://input'); + + } elseif ($rawBody === NULL) { // can be read only once in PHP < 5.6 + $rawBody = (string) file_get_contents('php://input'); + } + + return $rawBody; + }; + + return new Request($url, $query, $post, $files, $cookies, $headers, $method, $remoteAddr, $remoteHost, $rawBodyCallback); } } diff --git a/src/Http/Response.php b/src/Http/Response.php index e7a3cf5f..8695c43a 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -47,10 +47,10 @@ class Response extends Nette\Object implements IResponse public function __construct() { if (PHP_VERSION_ID >= 50400) { - if (is_int(http_response_code())) { - $this->code = http_response_code(); + if (is_int($code = http_response_code())) { + $this->code = $code; } - header_register_callback($this->removeDuplicateCookies); + header_register_callback(array($this, 'removeDuplicateCookies')); } } @@ -147,13 +147,14 @@ public function redirect($url, $code = self::S302_FOUND) { $this->setCode($code); $this->setHeader('Location', $url); - echo "
Please click here to continue.
"; + $escapedUrl = htmlSpecialChars($url, ENT_IGNORE | ENT_QUOTES); + echo "