|
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\BrowserKit; |
|
13 |
|
14 /** |
|
15 * Cookie represents an HTTP cookie. |
|
16 * |
|
17 * @author Fabien Potencier <fabien@symfony.com> |
|
18 * |
|
19 * @api |
|
20 */ |
|
21 class Cookie |
|
22 { |
|
23 /** |
|
24 * Handles dates as defined by RFC 2616 section 3.3.1, and also some other |
|
25 * non-standard, but common formats. |
|
26 * |
|
27 * @var array |
|
28 */ |
|
29 private static $dateFormats = array( |
|
30 'D, d M Y H:i:s T', |
|
31 'D, d-M-y H:i:s T', |
|
32 'D, d-M-Y H:i:s T', |
|
33 'D M j G:i:s Y', |
|
34 ); |
|
35 |
|
36 protected $name; |
|
37 protected $value; |
|
38 protected $expires; |
|
39 protected $path; |
|
40 protected $domain; |
|
41 protected $secure; |
|
42 protected $httponly; |
|
43 protected $rawValue; |
|
44 |
|
45 /** |
|
46 * Sets a cookie. |
|
47 * |
|
48 * @param string $name The cookie name |
|
49 * @param string $value The value of the cookie |
|
50 * @param string $expires The time the cookie expires |
|
51 * @param string $path The path on the server in which the cookie will be available on |
|
52 * @param string $domain The domain that the cookie is available |
|
53 * @param Boolean $secure Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client |
|
54 * @param Boolean $httponly The cookie httponly flag |
|
55 * @param Boolean $encodedValue Whether the value is encoded or not |
|
56 * |
|
57 * @api |
|
58 */ |
|
59 public function __construct($name, $value, $expires = null, $path = null, $domain = '', $secure = false, $httponly = true, $encodedValue = false) |
|
60 { |
|
61 if ($encodedValue) { |
|
62 $this->value = urldecode($value); |
|
63 $this->rawValue = $value; |
|
64 } else { |
|
65 $this->value = $value; |
|
66 $this->rawValue = urlencode($value); |
|
67 } |
|
68 $this->name = $name; |
|
69 $this->expires = null === $expires ? null : (integer) $expires; |
|
70 $this->path = empty($path) ? null : $path; |
|
71 $this->domain = $domain; |
|
72 $this->secure = (Boolean) $secure; |
|
73 $this->httponly = (Boolean) $httponly; |
|
74 } |
|
75 |
|
76 /** |
|
77 * Returns the HTTP representation of the Cookie. |
|
78 * |
|
79 * @return string The HTTP representation of the Cookie |
|
80 * |
|
81 * @api |
|
82 */ |
|
83 public function __toString() |
|
84 { |
|
85 $cookie = sprintf('%s=%s', $this->name, $this->rawValue); |
|
86 |
|
87 if (null !== $this->expires) { |
|
88 $cookie .= '; expires='.substr(\DateTime::createFromFormat('U', $this->expires, new \DateTimeZone('GMT'))->format(self::$dateFormats[0]), 0, -5); |
|
89 } |
|
90 |
|
91 if ('' !== $this->domain) { |
|
92 $cookie .= '; domain='.$this->domain; |
|
93 } |
|
94 |
|
95 if (null !== $this->path) { |
|
96 $cookie .= '; path='.$this->path; |
|
97 } |
|
98 |
|
99 if ($this->secure) { |
|
100 $cookie .= '; secure'; |
|
101 } |
|
102 |
|
103 if ($this->httponly) { |
|
104 $cookie .= '; httponly'; |
|
105 } |
|
106 |
|
107 return $cookie; |
|
108 } |
|
109 |
|
110 /** |
|
111 * Creates a Cookie instance from a Set-Cookie header value. |
|
112 * |
|
113 * @param string $cookie A Set-Cookie header value |
|
114 * @param string $url The base URL |
|
115 * |
|
116 * @return Cookie A Cookie instance |
|
117 * |
|
118 * @api |
|
119 */ |
|
120 static public function fromString($cookie, $url = null) |
|
121 { |
|
122 $parts = explode(';', $cookie); |
|
123 |
|
124 if (false === strpos($parts[0], '=')) { |
|
125 throw new \InvalidArgumentException('The cookie string "%s" is not valid.'); |
|
126 } |
|
127 |
|
128 list($name, $value) = explode('=', array_shift($parts), 2); |
|
129 |
|
130 $values = array( |
|
131 'name' => trim($name), |
|
132 'value' => trim($value), |
|
133 'expires' => null, |
|
134 'path' => null, |
|
135 'domain' => '', |
|
136 'secure' => false, |
|
137 'httponly' => false, |
|
138 'passedRawValue' => true, |
|
139 ); |
|
140 |
|
141 if (null !== $url) { |
|
142 if ((false === $urlParts = parse_url($url)) || !isset($urlParts['host']) || !isset($urlParts['path'])) { |
|
143 throw new \InvalidArgumentException(sprintf('The URL "%s" is not valid.', $url)); |
|
144 } |
|
145 $parts = array_merge($urlParts, $parts); |
|
146 |
|
147 $values['domain'] = $parts['host']; |
|
148 $values['path'] = substr($parts['path'], 0, strrpos($parts['path'], '/')); |
|
149 } |
|
150 |
|
151 foreach ($parts as $part) { |
|
152 $part = trim($part); |
|
153 |
|
154 if ('secure' === strtolower($part)) { |
|
155 // Ignore the secure flag if the original URI is not given or is not HTTPS |
|
156 if (!$url || !isset($urlParts['scheme']) || 'https' != $urlParts['scheme']) { |
|
157 continue; |
|
158 } |
|
159 |
|
160 $values['secure'] = true; |
|
161 |
|
162 continue; |
|
163 } |
|
164 |
|
165 if ('httponly' === strtolower($part)) { |
|
166 $values['httponly'] = true; |
|
167 |
|
168 continue; |
|
169 } |
|
170 |
|
171 if (2 === count($elements = explode('=', $part, 2))) { |
|
172 if ('expires' === $elements[0]) { |
|
173 $elements[1] = self::parseDate($elements[1]); |
|
174 } |
|
175 |
|
176 $values[strtolower($elements[0])] = $elements[1]; |
|
177 } |
|
178 } |
|
179 |
|
180 return new static( |
|
181 $values['name'], |
|
182 $values['value'], |
|
183 $values['expires'], |
|
184 $values['path'], |
|
185 $values['domain'], |
|
186 $values['secure'], |
|
187 $values['httponly'], |
|
188 $values['passedRawValue'] |
|
189 ); |
|
190 } |
|
191 |
|
192 private static function parseDate($dateValue) |
|
193 { |
|
194 // trim single quotes around date if present |
|
195 if (($length = strlen($dateValue)) > 1 && "'" === $dateValue[0] && "'" === $dateValue[$length-1]) { |
|
196 $dateValue = substr($dateValue, 1, -1); |
|
197 } |
|
198 |
|
199 foreach (self::$dateFormats as $dateFormat) { |
|
200 if (false !== $date = \DateTime::createFromFormat($dateFormat, $dateValue, new \DateTimeZone('GMT'))) { |
|
201 return $date->getTimestamp(); |
|
202 } |
|
203 } |
|
204 |
|
205 throw new \InvalidArgumentException(sprintf('Could not parse date "%s".', $dateValue)); |
|
206 } |
|
207 |
|
208 /** |
|
209 * Gets the name of the cookie. |
|
210 * |
|
211 * @return string The cookie name |
|
212 * |
|
213 * @api |
|
214 */ |
|
215 public function getName() |
|
216 { |
|
217 return $this->name; |
|
218 } |
|
219 |
|
220 /** |
|
221 * Gets the value of the cookie. |
|
222 * |
|
223 * @return string The cookie value |
|
224 * |
|
225 * @api |
|
226 */ |
|
227 public function getValue() |
|
228 { |
|
229 return $this->value; |
|
230 } |
|
231 |
|
232 /** |
|
233 * Gets the raw value of the cookie. |
|
234 * |
|
235 * @return string The cookie value |
|
236 * |
|
237 * @api |
|
238 */ |
|
239 public function getRawValue() |
|
240 { |
|
241 return $this->rawValue; |
|
242 } |
|
243 |
|
244 /** |
|
245 * Gets the expires time of the cookie. |
|
246 * |
|
247 * @return string The cookie expires time |
|
248 * |
|
249 * @api |
|
250 */ |
|
251 public function getExpiresTime() |
|
252 { |
|
253 return $this->expires; |
|
254 } |
|
255 |
|
256 /** |
|
257 * Gets the path of the cookie. |
|
258 * |
|
259 * @return string The cookie path |
|
260 * |
|
261 * @api |
|
262 */ |
|
263 public function getPath() |
|
264 { |
|
265 return null !== $this->path ? $this->path : '/'; |
|
266 } |
|
267 |
|
268 /** |
|
269 * Gets the domain of the cookie. |
|
270 * |
|
271 * @return string The cookie domain |
|
272 * |
|
273 * @api |
|
274 */ |
|
275 public function getDomain() |
|
276 { |
|
277 return $this->domain; |
|
278 } |
|
279 |
|
280 /** |
|
281 * Returns the secure flag of the cookie. |
|
282 * |
|
283 * @return Boolean The cookie secure flag |
|
284 * |
|
285 * @api |
|
286 */ |
|
287 public function isSecure() |
|
288 { |
|
289 return $this->secure; |
|
290 } |
|
291 |
|
292 /** |
|
293 * Returns the httponly flag of the cookie. |
|
294 * |
|
295 * @return Boolean The cookie httponly flag |
|
296 * |
|
297 * @api |
|
298 */ |
|
299 public function isHttpOnly() |
|
300 { |
|
301 return $this->httponly; |
|
302 } |
|
303 |
|
304 /** |
|
305 * Returns true if the cookie has expired. |
|
306 * |
|
307 * @return Boolean true if the cookie has expired, false otherwise |
|
308 * |
|
309 * @api |
|
310 */ |
|
311 public function isExpired() |
|
312 { |
|
313 return null !== $this->expires && 0 !== $this->expires && $this->expires < time(); |
|
314 } |
|
315 } |