PluginProbe
Packeta / trunk
Packeta vtrunk
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 trunk, at deps/nette/http/src/Http/Response.php

231 lines 8.8 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 /** @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 \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 /**
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 \Packetery\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 \Packetery\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 \Packetery\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