3223691eb6fe4ef5f79b245fdc734558a85d0e05
[yaffs-website] / vendor / symfony / http-foundation / ResponseHeaderBag.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Symfony\Component\HttpFoundation;
13
14 /**
15  * ResponseHeaderBag is a container for Response HTTP headers.
16  *
17  * @author Fabien Potencier <fabien@symfony.com>
18  */
19 class ResponseHeaderBag extends HeaderBag
20 {
21     const COOKIES_FLAT = 'flat';
22     const COOKIES_ARRAY = 'array';
23
24     const DISPOSITION_ATTACHMENT = 'attachment';
25     const DISPOSITION_INLINE = 'inline';
26
27     /**
28      * @var array
29      */
30     protected $computedCacheControl = array();
31
32     /**
33      * @var array
34      */
35     protected $cookies = array();
36
37     /**
38      * @var array
39      */
40     protected $headerNames = array();
41
42     /**
43      * Constructor.
44      *
45      * @param array $headers An array of HTTP headers
46      */
47     public function __construct(array $headers = array())
48     {
49         parent::__construct($headers);
50
51         if (!isset($this->headers['cache-control'])) {
52             $this->set('Cache-Control', '');
53         }
54     }
55
56     /**
57      * {@inheritdoc}
58      */
59     public function __toString()
60     {
61         $cookies = '';
62         foreach ($this->getCookies() as $cookie) {
63             $cookies .= 'Set-Cookie: '.$cookie."\r\n";
64         }
65
66         ksort($this->headerNames);
67
68         return parent::__toString().$cookies;
69     }
70
71     /**
72      * Returns the headers, with original capitalizations.
73      *
74      * @return array An array of headers
75      */
76     public function allPreserveCase()
77     {
78         return array_combine($this->headerNames, $this->headers);
79     }
80
81     /**
82      * {@inheritdoc}
83      */
84     public function replace(array $headers = array())
85     {
86         $this->headerNames = array();
87
88         parent::replace($headers);
89
90         if (!isset($this->headers['cache-control'])) {
91             $this->set('Cache-Control', '');
92         }
93     }
94
95     /**
96      * {@inheritdoc}
97      */
98     public function set($key, $values, $replace = true)
99     {
100         parent::set($key, $values, $replace);
101
102         $uniqueKey = str_replace('_', '-', strtolower($key));
103         $this->headerNames[$uniqueKey] = $key;
104
105         // ensure the cache-control header has sensible defaults
106         if (in_array($uniqueKey, array('cache-control', 'etag', 'last-modified', 'expires'))) {
107             $computed = $this->computeCacheControlValue();
108             $this->headers['cache-control'] = array($computed);
109             $this->headerNames['cache-control'] = 'Cache-Control';
110             $this->computedCacheControl = $this->parseCacheControl($computed);
111         }
112     }
113
114     /**
115      * {@inheritdoc}
116      */
117     public function remove($key)
118     {
119         parent::remove($key);
120
121         $uniqueKey = str_replace('_', '-', strtolower($key));
122         unset($this->headerNames[$uniqueKey]);
123
124         if ('cache-control' === $uniqueKey) {
125             $this->computedCacheControl = array();
126         }
127     }
128
129     /**
130      * {@inheritdoc}
131      */
132     public function hasCacheControlDirective($key)
133     {
134         return array_key_exists($key, $this->computedCacheControl);
135     }
136
137     /**
138      * {@inheritdoc}
139      */
140     public function getCacheControlDirective($key)
141     {
142         return array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null;
143     }
144
145     /**
146      * Sets a cookie.
147      *
148      * @param Cookie $cookie
149      */
150     public function setCookie(Cookie $cookie)
151     {
152         $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
153     }
154
155     /**
156      * Removes a cookie from the array, but does not unset it in the browser.
157      *
158      * @param string $name
159      * @param string $path
160      * @param string $domain
161      */
162     public function removeCookie($name, $path = '/', $domain = null)
163     {
164         if (null === $path) {
165             $path = '/';
166         }
167
168         unset($this->cookies[$domain][$path][$name]);
169
170         if (empty($this->cookies[$domain][$path])) {
171             unset($this->cookies[$domain][$path]);
172
173             if (empty($this->cookies[$domain])) {
174                 unset($this->cookies[$domain]);
175             }
176         }
177     }
178
179     /**
180      * Returns an array with all cookies.
181      *
182      * @param string $format
183      *
184      * @return array
185      *
186      * @throws \InvalidArgumentException When the $format is invalid
187      */
188     public function getCookies($format = self::COOKIES_FLAT)
189     {
190         if (!in_array($format, array(self::COOKIES_FLAT, self::COOKIES_ARRAY))) {
191             throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', array(self::COOKIES_FLAT, self::COOKIES_ARRAY))));
192         }
193
194         if (self::COOKIES_ARRAY === $format) {
195             return $this->cookies;
196         }
197
198         $flattenedCookies = array();
199         foreach ($this->cookies as $path) {
200             foreach ($path as $cookies) {
201                 foreach ($cookies as $cookie) {
202                     $flattenedCookies[] = $cookie;
203                 }
204             }
205         }
206
207         return $flattenedCookies;
208     }
209
210     /**
211      * Clears a cookie in the browser.
212      *
213      * @param string $name
214      * @param string $path
215      * @param string $domain
216      * @param bool   $secure
217      * @param bool   $httpOnly
218      */
219     public function clearCookie($name, $path = '/', $domain = null, $secure = false, $httpOnly = true)
220     {
221         $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly));
222     }
223
224     /**
225      * Generates a HTTP Content-Disposition field-value.
226      *
227      * @param string $disposition      One of "inline" or "attachment"
228      * @param string $filename         A unicode string
229      * @param string $filenameFallback A string containing only ASCII characters that
230      *                                 is semantically equivalent to $filename. If the filename is already ASCII,
231      *                                 it can be omitted, or just copied from $filename
232      *
233      * @return string A string suitable for use as a Content-Disposition field-value
234      *
235      * @throws \InvalidArgumentException
236      *
237      * @see RFC 6266
238      */
239     public function makeDisposition($disposition, $filename, $filenameFallback = '')
240     {
241         if (!in_array($disposition, array(self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE))) {
242             throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
243         }
244
245         if ('' == $filenameFallback) {
246             $filenameFallback = $filename;
247         }
248
249         // filenameFallback is not ASCII.
250         if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) {
251             throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.');
252         }
253
254         // percent characters aren't safe in fallback.
255         if (false !== strpos($filenameFallback, '%')) {
256             throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.');
257         }
258
259         // path separators aren't allowed in either.
260         if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) {
261             throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.');
262         }
263
264         $output = sprintf('%s; filename="%s"', $disposition, str_replace('"', '\\"', $filenameFallback));
265
266         if ($filename !== $filenameFallback) {
267             $output .= sprintf("; filename*=utf-8''%s", rawurlencode($filename));
268         }
269
270         return $output;
271     }
272
273     /**
274      * Returns the calculated value of the cache-control header.
275      *
276      * This considers several other headers and calculates or modifies the
277      * cache-control header to a sensible, conservative value.
278      *
279      * @return string
280      */
281     protected function computeCacheControlValue()
282     {
283         if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) {
284             return 'no-cache';
285         }
286
287         if (!$this->cacheControl) {
288             // conservative by default
289             return 'private, must-revalidate';
290         }
291
292         $header = $this->getCacheControlHeader();
293         if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) {
294             return $header;
295         }
296
297         // public if s-maxage is defined, private otherwise
298         if (!isset($this->cacheControl['s-maxage'])) {
299             return $header.', private';
300         }
301
302         return $header;
303     }
304 }