~ubuntu-branches/debian/experimental/php-nette/experimental

« back to all changes in this revision

Viewing changes to Nette-2.1.0RC/Nette/Http/Response.php

  • Committer: Package Import Robot
  • Author(s): David Prévot
  • Date: 2013-11-30 08:47:54 UTC
  • mfrom: (1.1.1)
  • Revision ID: package-import@ubuntu.com-20131130084754-4udf1xsu9085tnfc
Tags: 2.1.0~rc-1
* New upstream branch
* Update copyright

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
 
 
3
/**
 
4
 * This file is part of the Nette Framework (http://nette.org)
 
5
 *
 
6
 * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
 
7
 *
 
8
 * For the full copyright and license information, please view
 
9
 * the file license.txt that was distributed with this source code.
 
10
 */
 
11
 
 
12
namespace Nette\Http;
 
13
 
 
14
use Nette;
 
15
 
 
16
 
 
17
/**
 
18
 * HttpResponse class.
 
19
 *
 
20
 * @author     David Grudl
 
21
 *
 
22
 * @property   int $code
 
23
 * @property-read bool $sent
 
24
 * @property-read array $headers
 
25
 */
 
26
final class Response extends Nette\Object implements IResponse
 
27
{
 
28
        /** @var bool  Send invisible garbage for IE 6? */
 
29
        private static $fixIE = TRUE;
 
30
 
 
31
        /** @var string The domain in which the cookie will be available */
 
32
        public $cookieDomain = '';
 
33
 
 
34
        /** @var string The path in which the cookie will be available */
 
35
        public $cookiePath = '/';
 
36
 
 
37
        /** @var string Whether the cookie is available only through HTTPS */
 
38
        public $cookieSecure = FALSE;
 
39
 
 
40
        /** @var string Whether the cookie is hidden from client-side */
 
41
        public $cookieHttpOnly = TRUE;
 
42
 
 
43
        /** @var int HTTP response code */
 
44
        private $code = self::S200_OK;
 
45
 
 
46
 
 
47
        public function __construct()
 
48
        {
 
49
                if (PHP_VERSION_ID >= 50400) {
 
50
                        if (is_int(http_response_code())) {
 
51
                                $this->code = http_response_code();
 
52
                        }
 
53
                        header_register_callback($this->removeDuplicateCookies);
 
54
                }
 
55
        }
 
56
 
 
57
 
 
58
        /**
 
59
         * Sets HTTP response code.
 
60
         * @param  int
 
61
         * @return self
 
62
         * @throws Nette\InvalidArgumentException  if code is invalid
 
63
         * @throws Nette\InvalidStateException  if HTTP headers have been sent
 
64
         */
 
65
        public function setCode($code)
 
66
        {
 
67
                $code = (int) $code;
 
68
 
 
69
                if ($code < 100 || $code > 599) {
 
70
                        throw new Nette\InvalidArgumentException("Bad HTTP response '$code'.");
 
71
 
 
72
                } elseif (headers_sent($file, $line)) {
 
73
                        throw new Nette\InvalidStateException("Cannot set HTTP code after HTTP headers have been sent" . ($file ? " (output started at $file:$line)." : "."));
 
74
 
 
75
                } else {
 
76
                        $this->code = $code;
 
77
                        $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
 
78
                        header($protocol . ' ' . $code, TRUE, $code);
 
79
                }
 
80
                return $this;
 
81
        }
 
82
 
 
83
 
 
84
        /**
 
85
         * Returns HTTP response code.
 
86
         * @return int
 
87
         */
 
88
        public function getCode()
 
89
        {
 
90
                return $this->code;
 
91
        }
 
92
 
 
93
 
 
94
        /**
 
95
         * Sends a HTTP header and replaces a previous one.
 
96
         * @param  string  header name
 
97
         * @param  string  header value
 
98
         * @return self
 
99
         * @throws Nette\InvalidStateException  if HTTP headers have been sent
 
100
         */
 
101
        public function setHeader($name, $value)
 
102
        {
 
103
                if (headers_sent($file, $line)) {
 
104
                        throw new Nette\InvalidStateException("Cannot send header after HTTP headers have been sent" . ($file ? " (output started at $file:$line)." : "."));
 
105
                }
 
106
 
 
107
                if ($value === NULL && function_exists('header_remove')) {
 
108
                        header_remove($name);
 
109
                } elseif (strcasecmp($name, 'Content-Length') === 0 && ini_get('zlib.output_compression')) {
 
110
                        // ignore, PHP bug #44164
 
111
                } else {
 
112
                        header($name . ': ' . $value, TRUE, $this->code);
 
113
                }
 
114
                return $this;
 
115
        }
 
116
 
 
117
 
 
118
        /**
 
119
         * Adds HTTP header.
 
120
         * @param  string  header name
 
121
         * @param  string  header value
 
122
         * @return self
 
123
         * @throws Nette\InvalidStateException  if HTTP headers have been sent
 
124
         */
 
125
        public function addHeader($name, $value)
 
126
        {
 
127
                if (headers_sent($file, $line)) {
 
128
                        throw new Nette\InvalidStateException("Cannot send header after HTTP headers have been sent" . ($file ? " (output started at $file:$line)." : "."));
 
129
                }
 
130
 
 
131
                header($name . ': ' . $value, FALSE, $this->code);
 
132
                return $this;
 
133
        }
 
134
 
 
135
 
 
136
        /**
 
137
         * Sends a Content-type HTTP header.
 
138
         * @param  string  mime-type
 
139
         * @param  string  charset
 
140
         * @return self
 
141
         * @throws Nette\InvalidStateException  if HTTP headers have been sent
 
142
         */
 
143
        public function setContentType($type, $charset = NULL)
 
144
        {
 
145
                $this->setHeader('Content-Type', $type . ($charset ? '; charset=' . $charset : ''));
 
146
                return $this;
 
147
        }
 
148
 
 
149
 
 
150
        /**
 
151
         * Redirects to a new URL. Note: call exit() after it.
 
152
         * @param  string  URL
 
153
         * @param  int     HTTP code
 
154
         * @return void
 
155
         * @throws Nette\InvalidStateException  if HTTP headers have been sent
 
156
         */
 
157
        public function redirect($url, $code = self::S302_FOUND)
 
158
        {
 
159
                $this->setCode($code);
 
160
                $this->setHeader('Location', $url);
 
161
                echo "<h1>Redirect</h1>\n\n<p><a href=\"" . htmlSpecialChars($url, ENT_IGNORE | ENT_QUOTES) . "\">Please click here to continue</a>.</p>";
 
162
        }
 
163
 
 
164
 
 
165
        /**
 
166
         * Sets the number of seconds before a page cached on a browser expires.
 
167
         * @param  string|int|DateTime  time, value 0 means "until the browser is closed"
 
168
         * @return self
 
169
         * @throws Nette\InvalidStateException  if HTTP headers have been sent
 
170
         */
 
171
        public function setExpiration($time)
 
172
        {
 
173
                if (!$time) { // no cache
 
174
                        $this->setHeader('Cache-Control', 's-maxage=0, max-age=0, must-revalidate');
 
175
                        $this->setHeader('Expires', 'Mon, 23 Jan 1978 10:00:00 GMT');
 
176
                        return $this;
 
177
                }
 
178
 
 
179
                $time = Nette\DateTime::from($time);
 
180
                $this->setHeader('Cache-Control', 'max-age=' . ($time->format('U') - time()));
 
181
                $this->setHeader('Expires', self::date($time));
 
182
                return $this;
 
183
        }
 
184
 
 
185
 
 
186
        /**
 
187
         * Checks if headers have been sent.
 
188
         * @return bool
 
189
         */
 
190
        public function isSent()
 
191
        {
 
192
                return headers_sent();
 
193
        }
 
194
 
 
195
 
 
196
        /**
 
197
         * Return the value of the HTTP header.
 
198
         * @param  string
 
199
         * @param  mixed
 
200
         * @return mixed
 
201
         */
 
202
        public function getHeader($header, $default = NULL)
 
203
        {
 
204
                $header .= ':';
 
205
                $len = strlen($header);
 
206
                foreach (headers_list() as $item) {
 
207
                        if (strncasecmp($item, $header, $len) === 0) {
 
208
                                return ltrim(substr($item, $len));
 
209
                        }
 
210
                }
 
211
                return $default;
 
212
        }
 
213
 
 
214
 
 
215
        /**
 
216
         * Returns a list of headers to sent.
 
217
         * @return array
 
218
         */
 
219
        public function getHeaders()
 
220
        {
 
221
                $headers = array();
 
222
                foreach (headers_list() as $header) {
 
223
                        $a = strpos($header, ':');
 
224
                        $headers[substr($header, 0, $a)] = (string) substr($header, $a + 2);
 
225
                }
 
226
                return $headers;
 
227
        }
 
228
 
 
229
 
 
230
        /**
 
231
         * Returns HTTP valid date format.
 
232
         * @param  string|int|DateTime
 
233
         * @return string
 
234
         */
 
235
        public static function date($time = NULL)
 
236
        {
 
237
                $time = Nette\DateTime::from($time);
 
238
                $time->setTimezone(new \DateTimeZone('GMT'));
 
239
                return $time->format('D, d M Y H:i:s \G\M\T');
 
240
        }
 
241
 
 
242
 
 
243
        /**
 
244
         * @return void
 
245
         */
 
246
        public function __destruct()
 
247
        {
 
248
                if (self::$fixIE && isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE ') !== FALSE
 
249
                        && in_array($this->code, array(400, 403, 404, 405, 406, 408, 409, 410, 500, 501, 505), TRUE)
 
250
                        && $this->getHeader('Content-Type', 'text/html') === 'text/html'
 
251
                ) {
 
252
                        echo Nette\Utils\Strings::random(2e3, " \t\r\n"); // sends invisible garbage for IE
 
253
                        self::$fixIE = FALSE;
 
254
                }
 
255
        }
 
256
 
 
257
 
 
258
        /**
 
259
         * Sends a cookie.
 
260
         * @param  string name of the cookie
 
261
         * @param  string value
 
262
         * @param  string|int|DateTime  expiration time, value 0 means "until the browser is closed"
 
263
         * @param  string
 
264
         * @param  string
 
265
         * @param  bool
 
266
         * @param  bool
 
267
         * @return self
 
268
         * @throws Nette\InvalidStateException  if HTTP headers have been sent
 
269
         */
 
270
        public function setCookie($name, $value, $time, $path = NULL, $domain = NULL, $secure = NULL, $httpOnly = NULL)
 
271
        {
 
272
                if (!headers_sent() && ob_get_level() && ob_get_length()) {
 
273
                        trigger_error("Possible problem: you are sending a cookie while already having some data in output buffer.  This may not work if the outputted data grows. Try starting the session earlier.", E_USER_NOTICE);
 
274
                }
 
275
 
 
276
                if (headers_sent($file, $line)) {
 
277
                        throw new Nette\InvalidStateException("Cannot set cookie after HTTP headers have been sent" . ($file ? " (output started at $file:$line)." : "."));
 
278
                }
 
279
 
 
280
                setcookie(
 
281
                        $name,
 
282
                        $value,
 
283
                        $time ? Nette\DateTime::from($time)->format('U') : 0,
 
284
                        $path === NULL ? $this->cookiePath : (string) $path,
 
285
                        $domain === NULL ? $this->cookieDomain : (string) $domain,
 
286
                        $secure === NULL ? $this->cookieSecure : (bool) $secure,
 
287
                        $httpOnly === NULL ? $this->cookieHttpOnly : (bool) $httpOnly
 
288
                );
 
289
 
 
290
                $this->removeDuplicateCookies();
 
291
                return $this;
 
292
        }
 
293
 
 
294
 
 
295
        /**
 
296
         * Removes duplicate cookies from response.
 
297
         * @return void
 
298
         */
 
299
        public function removeDuplicateCookies()
 
300
        {
 
301
                if (headers_sent($file, $line) || ini_get('suhosin.cookie.encrypt')) {
 
302
                        return;
 
303
                }
 
304
 
 
305
                $flatten = array();
 
306
                foreach (headers_list() as $header) {
 
307
                        if (preg_match('#^Set-Cookie: .+?=#', $header, $m)) {
 
308
                                $flatten[$m[0]] = $header;
 
309
                                header_remove('Set-Cookie');
 
310
                        }
 
311
                }
 
312
                foreach (array_values($flatten) as $key => $header) {
 
313
                        header($header, $key === 0);
 
314
                }
 
315
        }
 
316
 
 
317
 
 
318
        /**
 
319
         * Deletes a cookie.
 
320
         * @param  string name of the cookie.
 
321
         * @param  string
 
322
         * @param  string
 
323
         * @param  bool
 
324
         * @return void
 
325
         * @throws Nette\InvalidStateException  if HTTP headers have been sent
 
326
         */
 
327
        public function deleteCookie($name, $path = NULL, $domain = NULL, $secure = NULL)
 
328
        {
 
329
                $this->setCookie($name, FALSE, 0, $path, $domain, $secure);
 
330
        }
 
331
 
 
332
}