1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Http;
9:
10: use Nette;
11: use Nette\Utils\DateTime;
12:
13:
14: 15: 16: 17: 18:
19: class Response implements IResponse
20: {
21: use Nette\SmartObject;
22:
23:
24: public $cookieDomain = '';
25:
26:
27: public $cookiePath = '/';
28:
29:
30: public $cookieSecure = false;
31:
32:
33: public $cookieHttpOnly = true;
34:
35:
36: public $warnOnBuffer = true;
37:
38:
39: private static $fixIE = true;
40:
41:
42: private $code = self::S200_OK;
43:
44:
45: public function __construct()
46: {
47: if (is_int($code = http_response_code())) {
48: $this->code = $code;
49: }
50: }
51:
52:
53: 54: 55: 56: 57: 58: 59: 60:
61: public function setCode($code, $reason = null)
62: {
63: $code = (int) $code;
64: if ($code < 100 || $code > 599) {
65: throw new Nette\InvalidArgumentException("Bad HTTP response '$code'.");
66: }
67: self::checkHeaders();
68: $this->code = $code;
69:
70: static $hasReason = [
71: 100, 101,
72: 200, 201, 202, 203, 204, 205, 206,
73: 300, 301, 302, 303, 304, 305, 307, 308,
74: 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 426, 428, 429, 431,
75: 500, 501, 502, 503, 504, 505, 506, 511,
76: ];
77: if ($reason || !in_array($code, $hasReason, true)) {
78: $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
79: header("$protocol $code " . ($reason ?: 'Unknown status'));
80: } else {
81: http_response_code($code);
82: }
83: return $this;
84: }
85:
86:
87: 88: 89: 90:
91: public function getCode()
92: {
93: return $this->code;
94: }
95:
96:
97: 98: 99: 100: 101: 102: 103:
104: public function ($name, $value)
105: {
106: self::checkHeaders();
107: if ($value === null) {
108: header_remove($name);
109: } elseif (strcasecmp($name, 'Content-Length') === 0 && ini_get('zlib.output_compression')) {
110:
111: } else {
112: header($name . ': ' . $value, true, $this->code);
113: }
114: return $this;
115: }
116:
117:
118: 119: 120: 121: 122: 123: 124:
125: public function ($name, $value)
126: {
127: self::checkHeaders();
128: header($name . ': ' . $value, false, $this->code);
129: return $this;
130: }
131:
132:
133: 134: 135: 136: 137: 138: 139:
140: public function setContentType($type, $charset = null)
141: {
142: $this->setHeader('Content-Type', $type . ($charset ? '; charset=' . $charset : ''));
143: return $this;
144: }
145:
146:
147: 148: 149: 150: 151: 152: 153:
154: public function redirect($url, $code = self::S302_FOUND)
155: {
156: $this->setCode($code);
157: $this->setHeader('Location', $url);
158: if (preg_match('#^https?:|^\s*+[a-z0-9+.-]*+[^:]#i', $url)) {
159: $escapedUrl = htmlspecialchars($url, ENT_IGNORE | ENT_QUOTES, 'UTF-8');
160: echo "<h1>Redirect</h1>\n\n<p><a href=\"$escapedUrl\">Please click here to continue</a>.</p>";
161: }
162: }
163:
164:
165: 166: 167: 168: 169: 170:
171: public function setExpiration($time)
172: {
173: $this->setHeader('Pragma', null);
174: if (!$time) {
175: $this->setHeader('Cache-Control', 's-maxage=0, max-age=0, must-revalidate');
176: $this->setHeader('Expires', 'Mon, 23 Jan 1978 10:00:00 GMT');
177: return $this;
178: }
179:
180: $time = DateTime::from($time);
181: $this->setHeader('Cache-Control', 'max-age=' . ($time->format('U') - time()));
182: $this->setHeader('Expires', Helpers::formatDate($time));
183: return $this;
184: }
185:
186:
187: 188: 189: 190:
191: public function isSent()
192: {
193: return headers_sent();
194: }
195:
196:
197: 198: 199: 200: 201: 202:
203: public function ($header, $default = null)
204: {
205: $header .= ':';
206: $len = strlen($header);
207: foreach (headers_list() as $item) {
208: if (strncasecmp($item, $header, $len) === 0) {
209: return ltrim(substr($item, $len));
210: }
211: }
212: return $default;
213: }
214:
215:
216: 217: 218: 219:
220: public function ()
221: {
222: $headers = [];
223: foreach (headers_list() as $header) {
224: $a = strpos($header, ':');
225: $headers[substr($header, 0, $a)] = (string) substr($header, $a + 2);
226: }
227: return $headers;
228: }
229:
230:
231: 232: 233:
234: public static function date($time = null)
235: {
236: trigger_error('Method date() is deprecated, use Nette\Http\Helpers::formatDate() instead.', E_USER_DEPRECATED);
237: return Helpers::formatDate($time);
238: }
239:
240:
241: public function __destruct()
242: {
243: if (
244: self::$fixIE
245: && isset($_SERVER['HTTP_USER_AGENT'])
246: && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE ') !== false
247: && in_array($this->code, [400, 403, 404, 405, 406, 408, 409, 410, 500, 501, 505], true)
248: && preg_match('#^text/html(?:;|$)#', $this->getHeader('Content-Type'))
249: ) {
250: echo Nette\Utils\Random::generate(2e3, " \t\r\n");
251: self::$fixIE = false;
252: }
253: }
254:
255:
256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268:
269: public function setCookie($name, $value, $time, $path = null, $domain = null, $secure = null, $httpOnly = null, $sameSite = null)
270: {
271: self::checkHeaders();
272: $options = [
273: 'expires' => $time ? (int) DateTime::from($time)->format('U') : 0,
274: 'path' => $path === null ? $this->cookiePath : (string) $path,
275: 'domain' => $domain === null ? $this->cookieDomain : (string) $domain,
276: 'secure' => $secure === null ? $this->cookieSecure : (bool) $secure,
277: 'httponly' => $httpOnly === null ? $this->cookieHttpOnly : (bool) $httpOnly,
278: 'samesite' => (string) $sameSite,
279: ];
280: if (PHP_VERSION_ID >= 70300) {
281: setcookie($name, $value, $options);
282: } else {
283: setcookie(
284: $name,
285: $value,
286: $options['expires'],
287: $options['path'] . ($sameSite ? "; SameSite=$sameSite" : ''),
288: $options['domain'],
289: $options['secure'],
290: $options['httponly']
291: );
292: }
293: return $this;
294: }
295:
296:
297: 298: 299: 300: 301: 302: 303: 304: 305:
306: public function deleteCookie($name, $path = null, $domain = null, $secure = null)
307: {
308: $this->setCookie($name, false, 0, $path, $domain, $secure);
309: }
310:
311:
312: private function ()
313: {
314: if (PHP_SAPI === 'cli') {
315: } elseif (headers_sent($file, $line)) {
316: throw new Nette\InvalidStateException('Cannot send header after HTTP headers have been sent' . ($file ? " (output started at $file:$line)." : '.'));
317:
318: } elseif ($this->warnOnBuffer && ob_get_length() && !array_filter(ob_get_status(true), function ($i) { return !$i['chunk_size']; })) {
319: trigger_error('Possible problem: you are sending a HTTP header while already having some data in output buffer. Try Tracy\OutputDebugger or start session earlier.');
320: }
321: }
322: }
323: