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