Context.php
1 month ago
FileUpload.php
1 month ago
Helpers.php
1 month ago
IRequest.php
1 month ago
IResponse.php
1 month ago
Request.php
1 month ago
RequestFactory.php
1 month ago
Response.php
1 month ago
Session.php
1 month ago
SessionSection.php
1 month ago
Url.php
1 month ago
UrlImmutable.php
1 month ago
UrlScript.php
1 month ago
UserStorage.php
1 month ago
Response.php
231 lines
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * This file is part of the Nette Framework (https://nette.org) |
| 5 | * Copyright (c) 2004 David Grudl (https://davidgrudl.com) |
| 6 | */ |
| 7 | declare (strict_types=1); |
| 8 | namespace FapiMember\Library\Nette\Http; |
| 9 | |
| 10 | use FapiMember\Library\Nette; |
| 11 | use FapiMember\Library\Nette\Utils\DateTime; |
| 12 | /** |
| 13 | * HttpResponse class. |
| 14 | * |
| 15 | * @property-read array $headers |
| 16 | */ |
| 17 | final class Response implements IResponse |
| 18 | { |
| 19 | use Nette\SmartObject; |
| 20 | /** @var string The domain in which the cookie will be available */ |
| 21 | public $cookieDomain = ''; |
| 22 | /** @var string The path in which the cookie will be available */ |
| 23 | public $cookiePath = '/'; |
| 24 | /** @var bool Whether the cookie is available only through HTTPS */ |
| 25 | public $cookieSecure = \false; |
| 26 | /** @deprecated */ |
| 27 | public $cookieHttpOnly; |
| 28 | /** @var bool Whether warn on possible problem with data in output buffer */ |
| 29 | public $warnOnBuffer = \true; |
| 30 | /** @deprecated */ |
| 31 | private static $fixIE = \true; |
| 32 | /** @var int HTTP response code */ |
| 33 | private $code = self::S200_OK; |
| 34 | public function __construct() |
| 35 | { |
| 36 | if (is_int($code = http_response_code())) { |
| 37 | $this->code = $code; |
| 38 | } |
| 39 | } |
| 40 | /** |
| 41 | * Sets HTTP response code. |
| 42 | * @return static |
| 43 | * @throws Nette\InvalidArgumentException if code is invalid |
| 44 | * @throws Nette\InvalidStateException if HTTP headers have been sent |
| 45 | */ |
| 46 | public function setCode(int $code, ?string $reason = null) |
| 47 | { |
| 48 | if ($code < 100 || $code > 599) { |
| 49 | throw new Nette\InvalidArgumentException("Bad HTTP response '{$code}'."); |
| 50 | } |
| 51 | self::checkHeaders(); |
| 52 | $this->code = $code; |
| 53 | $protocol = $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1'; |
| 54 | $reason = $reason ?? self::ReasonPhrases[$code] ?? 'Unknown status'; |
| 55 | header("{$protocol} {$code} {$reason}"); |
| 56 | return $this; |
| 57 | } |
| 58 | /** |
| 59 | * Returns HTTP response code. |
| 60 | */ |
| 61 | public function getCode(): int |
| 62 | { |
| 63 | return $this->code; |
| 64 | } |
| 65 | /** |
| 66 | * Sends an HTTP header and overwrites previously sent header of the same name. |
| 67 | * @return static |
| 68 | * @throws Nette\InvalidStateException if HTTP headers have been sent |
| 69 | */ |
| 70 | public function setHeader(string $name, ?string $value) |
| 71 | { |
| 72 | self::checkHeaders(); |
| 73 | if ($value === null) { |
| 74 | header_remove($name); |
| 75 | } elseif (strcasecmp($name, 'Content-Length') === 0 && ini_get('zlib.output_compression')) { |
| 76 | // ignore, PHP bug #44164 |
| 77 | } else { |
| 78 | header($name . ': ' . $value); |
| 79 | } |
| 80 | return $this; |
| 81 | } |
| 82 | /** |
| 83 | * Sends an HTTP header and doesn't overwrite previously sent header of the same name. |
| 84 | * @return static |
| 85 | * @throws Nette\InvalidStateException if HTTP headers have been sent |
| 86 | */ |
| 87 | public function addHeader(string $name, string $value) |
| 88 | { |
| 89 | self::checkHeaders(); |
| 90 | header($name . ': ' . $value, \false); |
| 91 | return $this; |
| 92 | } |
| 93 | /** |
| 94 | * Deletes a previously sent HTTP header. |
| 95 | * @return static |
| 96 | * @throws Nette\InvalidStateException if HTTP headers have been sent |
| 97 | */ |
| 98 | public function deleteHeader(string $name) |
| 99 | { |
| 100 | self::checkHeaders(); |
| 101 | header_remove($name); |
| 102 | return $this; |
| 103 | } |
| 104 | /** |
| 105 | * Sends a Content-type HTTP header. |
| 106 | * @return static |
| 107 | * @throws Nette\InvalidStateException if HTTP headers have been sent |
| 108 | */ |
| 109 | public function setContentType(string $type, ?string $charset = null) |
| 110 | { |
| 111 | $this->setHeader('Content-Type', $type . ($charset ? '; charset=' . $charset : '')); |
| 112 | return $this; |
| 113 | } |
| 114 | /** |
| 115 | * Response should be downloaded with 'Save as' dialog. |
| 116 | * @return static |
| 117 | * @throws Nette\InvalidStateException if HTTP headers have been sent |
| 118 | */ |
| 119 | public function sendAsFile(string $fileName) |
| 120 | { |
| 121 | $this->setHeader('Content-Disposition', 'attachment; filename="' . str_replace('"', '', $fileName) . '"; ' . "filename*=utf-8''" . rawurlencode($fileName)); |
| 122 | return $this; |
| 123 | } |
| 124 | /** |
| 125 | * Redirects to another URL. Don't forget to quit the script then. |
| 126 | * @throws Nette\InvalidStateException if HTTP headers have been sent |
| 127 | */ |
| 128 | public function redirect(string $url, int $code = self::S302_Found): void |
| 129 | { |
| 130 | $this->setCode($code); |
| 131 | $this->setHeader('Location', $url); |
| 132 | if (preg_match('#^https?:|^\s*+[a-z0-9+.-]*+[^:]#i', $url)) { |
| 133 | $escapedUrl = htmlspecialchars($url, \ENT_IGNORE | \ENT_QUOTES, 'UTF-8'); |
| 134 | echo "<h1>Redirect</h1>\n\n<p><a href=\"{$escapedUrl}\">Please click here to continue</a>.</p>"; |
| 135 | } |
| 136 | } |
| 137 | /** |
| 138 | * Sets the expiration of the HTTP document using the `Cache-Control` and `Expires` headers. |
| 139 | * The parameter is either a time interval (as text) or `null`, which disables caching. |
| 140 | * @return static |
| 141 | * @throws Nette\InvalidStateException if HTTP headers have been sent |
| 142 | */ |
| 143 | public function setExpiration(?string $expire) |
| 144 | { |
| 145 | $this->setHeader('Pragma', null); |
| 146 | if (!$expire) { |
| 147 | // no cache |
| 148 | $this->setHeader('Cache-Control', 's-maxage=0, max-age=0, must-revalidate'); |
| 149 | $this->setHeader('Expires', 'Mon, 23 Jan 1978 10:00:00 GMT'); |
| 150 | return $this; |
| 151 | } |
| 152 | $expire = DateTime::from($expire); |
| 153 | $this->setHeader('Cache-Control', 'max-age=' . ($expire->format('U') - time())); |
| 154 | $this->setHeader('Expires', Helpers::formatDate($expire)); |
| 155 | return $this; |
| 156 | } |
| 157 | /** |
| 158 | * Returns whether headers have already been sent from the server to the browser, |
| 159 | * so it is no longer possible to send headers or change the response code. |
| 160 | */ |
| 161 | public function isSent(): bool |
| 162 | { |
| 163 | return headers_sent(); |
| 164 | } |
| 165 | /** |
| 166 | * Returns the sent HTTP header, or `null` if it does not exist. The parameter is case-insensitive. |
| 167 | */ |
| 168 | public function getHeader(string $header): ?string |
| 169 | { |
| 170 | if (func_num_args() > 1) { |
| 171 | trigger_error(__METHOD__ . '() parameter $default is deprecated, use operator ??', \E_USER_DEPRECATED); |
| 172 | } |
| 173 | $header .= ':'; |
| 174 | $len = strlen($header); |
| 175 | foreach (headers_list() as $item) { |
| 176 | if (strncasecmp($item, $header, $len) === 0) { |
| 177 | return ltrim(substr($item, $len)); |
| 178 | } |
| 179 | } |
| 180 | return null; |
| 181 | } |
| 182 | /** |
| 183 | * Returns all sent HTTP headers as associative array. |
| 184 | */ |
| 185 | public function getHeaders(): array |
| 186 | { |
| 187 | $headers = []; |
| 188 | foreach (headers_list() as $header) { |
| 189 | $a = strpos($header, ':'); |
| 190 | $headers[substr($header, 0, $a)] = (string) substr($header, $a + 2); |
| 191 | } |
| 192 | return $headers; |
| 193 | } |
| 194 | /** |
| 195 | * Sends a cookie. |
| 196 | * @param string|int|\DateTimeInterface $expire expiration time, value null means "until the browser session ends" |
| 197 | * @return static |
| 198 | * @throws Nette\InvalidStateException if HTTP headers have been sent |
| 199 | */ |
| 200 | public function setCookie(string $name, string $value, $expire, ?string $path = null, ?string $domain = null, ?bool $secure = null, ?bool $httpOnly = null, ?string $sameSite = null) |
| 201 | { |
| 202 | self::checkHeaders(); |
| 203 | $options = ['expires' => $expire ? (int) DateTime::from($expire)->format('U') : 0, 'path' => $path ?? ($domain ? '/' : $this->cookiePath), 'domain' => $domain ?? ($path ? '' : $this->cookieDomain), 'secure' => $secure ?? $this->cookieSecure, 'httponly' => $httpOnly ?? \true, 'samesite' => $sameSite = $sameSite ?? self::SameSiteLax]; |
| 204 | if (\PHP_VERSION_ID >= 70300) { |
| 205 | setcookie($name, $value, $options); |
| 206 | } else { |
| 207 | setcookie($name, $value, $options['expires'], $options['path'] . ($sameSite ? "; SameSite={$sameSite}" : ''), $options['domain'], $options['secure'], $options['httponly']); |
| 208 | } |
| 209 | return $this; |
| 210 | } |
| 211 | /** |
| 212 | * Deletes a cookie. |
| 213 | * @throws Nette\InvalidStateException if HTTP headers have been sent |
| 214 | */ |
| 215 | public function deleteCookie(string $name, ?string $path = null, ?string $domain = null, ?bool $secure = null): void |
| 216 | { |
| 217 | $this->setCookie($name, '', 0, $path, $domain, $secure); |
| 218 | } |
| 219 | private function checkHeaders(): void |
| 220 | { |
| 221 | if (\PHP_SAPI === 'cli') { |
| 222 | } elseif (headers_sent($file, $line)) { |
| 223 | throw new Nette\InvalidStateException('Cannot send header after HTTP headers have been sent' . ($file ? " (output started at {$file}:{$line})." : '.')); |
| 224 | } elseif ($this->warnOnBuffer && ob_get_length() && !array_filter(ob_get_status(\true), function (array $i): bool { |
| 225 | return !$i['chunk_size']; |
| 226 | })) { |
| 227 | trigger_error('Possible problem: you are sending a HTTP header while already having some data in output buffer. Try Tracy\OutputDebugger or send cookies/start session earlier.'); |
| 228 | } |
| 229 | } |
| 230 | } |
| 231 |