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