PluginProbe
Packeta / 1.6.1
Packeta v1.6.1
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / deps / nette / http / src / Http / Response.php

Response.php in Packeta 1.6.1, at deps/nette/http/src/Http/Response.php

239 lines 9.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 Packetery\Nette\Http;
9
10 use Packetery\Nette;
11 use Packetery\Nette\Utils\DateTime;
12 /**
13 * HttpResponse class.
14 *
15 * @property-read array $headers
16 */
17 final class Response implements IResponse
18 {
19 use \Packetery\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 /** @var bool Send invisible garbage for IE 6? */
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 \Packetery\Nette\InvalidArgumentException if code is invalid
44 * @throws \Packetery\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 \Packetery\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 \Packetery\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 \Packetery\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 \Packetery\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 \Packetery\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 \Packetery\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 \Packetery\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 \Packetery\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 public function __destruct()
195 {
196 if (self::$fixIE && \strpos($_SERVER['HTTP_USER_AGENT'] ?? '', 'MSIE ') !== \false && \in_array($this->code, [400, 403, 404, 405, 406, 408, 409, 410, 500, 501, 505], \true) && \preg_match('#^text/html(?:;|$)#', (string) $this->getHeader('Content-Type'))) {
197 echo \Packetery\Nette\Utils\Random::generate(2000, " \t\r\n");
198 // sends invisible garbage for IE
199 self::$fixIE = \false;
200 }
201 }
202 /**
203 * Sends a cookie.
204 * @param string|int|\DateTimeInterface $expire expiration time, value null means "until the browser session ends"
205 * @return static
206 * @throws \Packetery\Nette\InvalidStateException if HTTP headers have been sent
207 */
208 public function setCookie(string $name, string $value, $expire, ?string $path = null, ?string $domain = null, ?bool $secure = null, ?bool $httpOnly = null, ?string $sameSite = null)
209 {
210 self::checkHeaders();
211 $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];
212 if (\PHP_VERSION_ID >= 70300) {
213 \setcookie($name, $value, $options);
214 } else {
215 \setcookie($name, $value, $options['expires'], $options['path'] . ($sameSite ? "; SameSite={$sameSite}" : ''), $options['domain'], $options['secure'], $options['httponly']);
216 }
217 return $this;
218 }
219 /**
220 * Deletes a cookie.
221 * @throws \Packetery\Nette\InvalidStateException if HTTP headers have been sent
222 */
223 public function deleteCookie(string $name, ?string $path = null, ?string $domain = null, ?bool $secure = null) : void
224 {
225 $this->setCookie($name, '', 0, $path, $domain, $secure);
226 }
227 private function checkHeaders() : void
228 {
229 if (\PHP_SAPI === 'cli') {
230 } elseif (\headers_sent($file, $line)) {
231 throw new \Packetery\Nette\InvalidStateException('Cannot send header after HTTP headers have been sent' . ($file ? " (output started at {$file}:{$line})." : '.'));
232 } elseif ($this->warnOnBuffer && \ob_get_length() && !\array_filter(\ob_get_status(\true), function (array $i) : bool {
233 return !$i['chunk_size'];
234 })) {
235 \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.');
236 }
237 }
238 }
239