1<?php
2
3/**
4 * Zend Framework
5 *
6 * LICENSE
7 *
8 * This source file is subject to the new BSD license that is bundled
9 * with this package in the file LICENSE.txt.
10 * It is also available through the world-wide-web at this URL:
11 * http://framework.zend.com/license/new-bsd
12 * If you did not receive a copy of the license and are unable to
13 * obtain it through the world-wide-web, please send an email
14 * to license@zend.com so we can send you a copy immediately.
15 *
16 * @category   Zend
17 * @package    Zend_Http
18 * @subpackage Header
19 * @version    $Id$
20 * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
21 * @license    http://framework.zend.com/license/new-bsd     New BSD License
22 */
23
24/**
25 * @see Zend_Http_Header_Exception_InvalidArgumentException
26 */
27require_once "Zend/Http/Header/Exception/InvalidArgumentException.php";
28
29/**
30 * @see Zend_Http_Header_Exception_RuntimeException
31 */
32require_once "Zend/Http/Header/Exception/RuntimeException.php";
33
34/**
35 * @see Zend_Http_Header_HeaderValue
36 */
37require_once "Zend/Http/Header/HeaderValue.php";
38
39/**
40 * Zend_Http_Client is an implementation of an HTTP client in PHP. The client
41 * supports basic features like sending different HTTP requests and handling
42 * redirections, as well as more advanced features like proxy settings, HTTP
43 * authentication and cookie persistence (using a Zend_Http_CookieJar object)
44 *
45 * @todo Implement proxy settings
46 * @category   Zend
47 * @package    Zend_Http
48 * @subpackage Header
49 * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
50 * @license    http://framework.zend.com/license/new-bsd     New BSD License
51 */
52class Zend_Http_Header_SetCookie
53{
54
55    /**
56     * Cookie name
57     *
58     * @var string
59     */
60    protected $name = null;
61
62    /**
63     * Cookie value
64     *
65     * @var string
66     */
67    protected $value = null;
68
69    /**
70     * Version
71     *
72     * @var integer
73     */
74    protected $version = null;
75
76    /**
77     * Max Age
78     *
79     * @var integer
80     */
81    protected $maxAge = null;
82
83    /**
84     * Cookie expiry date
85     *
86     * @var int
87     */
88    protected $expires = null;
89
90    /**
91     * Cookie domain
92     *
93     * @var string
94     */
95    protected $domain = null;
96
97    /**
98     * Cookie path
99     *
100     * @var string
101     */
102    protected $path = null;
103
104    /**
105     * Whether the cookie is secure or not
106     *
107     * @var boolean
108     */
109    protected $secure = null;
110
111    /**
112     * @var true
113     */
114    protected $httponly = null;
115
116    /**
117     * Generate a new Cookie object from a cookie string
118     * (for example the value of the Set-Cookie HTTP header)
119     *
120     * @static
121     * @throws Zend_Http_Header_Exception_InvalidArgumentException
122     * @param  $headerLine
123     * @param  bool $bypassHeaderFieldName
124     * @return array|SetCookie
125     */
126    public static function fromString($headerLine, $bypassHeaderFieldName = false)
127    {
128        list($name, $value) = explode(': ', $headerLine, 2);
129
130        // check to ensure proper header type for this factory
131        if (strtolower($name) !== 'set-cookie') {
132            throw new Zend_Http_Header_Exception_InvalidArgumentException('Invalid header line for Set-Cookie string: "' . $name . '"');
133        }
134
135        $multipleHeaders = preg_split('#(?<!Sun|Mon|Tue|Wed|Thu|Fri|Sat),\s*#', $value);
136        $headers = array();
137        foreach ($multipleHeaders as $headerLine) {
138            $header = new self();
139            $keyValuePairs = preg_split('#;\s*#', $headerLine);
140            foreach ($keyValuePairs as $keyValue) {
141                if (strpos($keyValue, '=')) {
142                    list($headerKey, $headerValue) = preg_split('#=\s*#', $keyValue, 2);
143                } else {
144                    $headerKey = $keyValue;
145                    $headerValue = null;
146                }
147
148                // First K=V pair is always the cookie name and value
149                if ($header->getName() === NULL) {
150                    $header->setName($headerKey);
151                    $header->setValue($headerValue);
152                    continue;
153                }
154
155                // Process the remanining elements
156                switch (str_replace(array('-', '_'), '', strtolower($headerKey))) {
157                    case 'expires' : $header->setExpires($headerValue); break;
158                    case 'domain'  : $header->setDomain($headerValue); break;
159                    case 'path'    : $header->setPath($headerValue); break;
160                    case 'secure'  : $header->setSecure(true); break;
161                    case 'httponly': $header->setHttponly(true); break;
162                    case 'version' : $header->setVersion((int) $headerValue); break;
163                    case 'maxage'  : $header->setMaxAge((int) $headerValue); break;
164                    default:
165                        // Intentionally omitted
166                }
167            }
168            $headers[] = $header;
169        }
170        return count($headers) == 1 ? array_pop($headers) : $headers;
171    }
172
173    /**
174     * Cookie object constructor
175     *
176     * @todo Add validation of each one of the parameters (legal domain, etc.)
177     *
178     * @param string $name
179     * @param string $value
180     * @param int $expires
181     * @param string $path
182     * @param string $domain
183     * @param bool $secure
184     * @param bool $httponly
185     * @param string $maxAge
186     * @param int $version
187     * @return SetCookie
188     */
189    public function __construct($name = null, $value = null, $expires = null, $path = null, $domain = null, $secure = false, $httponly = false, $maxAge = null, $version = null)
190    {
191        $this->type = 'Cookie';
192
193        if ($name) {
194            $this->setName($name);
195        }
196
197        if ($value) {
198            $this->setValue($value); // in parent
199        }
200
201        if ($version) {
202            $this->setVersion($version);
203        }
204
205        if ($maxAge) {
206            $this->setMaxAge($maxAge);
207        }
208
209        if ($domain) {
210            $this->setDomain($domain);
211        }
212
213        if ($expires) {
214            $this->setExpires($expires);
215        }
216
217        if ($path) {
218            $this->setPath($path);
219        }
220
221        if ($secure) {
222            $this->setSecure($secure);
223        }
224
225        if ($httponly) {
226            $this->setHttponly($httponly);
227        }
228    }
229
230    /**
231     * @return string 'Set-Cookie'
232     */
233    public function getFieldName()
234    {
235        return 'Set-Cookie';
236    }
237
238    /**
239     * @throws Zend_Http_Header_Exception_RuntimeException
240     * @return string
241     */
242    public function getFieldValue()
243    {
244        if ($this->getName() == '') {
245            throw new Zend_Http_Header_Exception_RuntimeException('A cookie name is required to generate a field value for this cookie');
246        }
247
248        $value = $this->getValue();
249        if (strpos($value,'"')!==false) {
250            $value = '"'.urlencode(str_replace('"', '', $value)).'"';
251        } else {
252            $value = urlencode($value);
253        }
254        $fieldValue = $this->getName() . '=' . $value;
255
256        $version = $this->getVersion();
257        if ($version!==null) {
258            $fieldValue .= '; Version=' . $version;
259        }
260
261        $maxAge = $this->getMaxAge();
262        if ($maxAge!==null) {
263            $fieldValue .= '; Max-Age=' . $maxAge;
264        }
265
266        $expires = $this->getExpires();
267        if ($expires) {
268            $fieldValue .= '; Expires=' . $expires;
269        }
270
271        $domain = $this->getDomain();
272        if ($domain) {
273            $fieldValue .= '; Domain=' . $domain;
274        }
275
276        $path = $this->getPath();
277        if ($path) {
278            $fieldValue .= '; Path=' . $path;
279        }
280
281        if ($this->isSecure()) {
282            $fieldValue .= '; Secure';
283        }
284
285        if ($this->isHttponly()) {
286            $fieldValue .= '; HttpOnly';
287        }
288
289        return $fieldValue;
290    }
291
292    /**
293     * @param string $name
294     * @return SetCookie
295     */
296    public function setName($name)
297    {
298        if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
299            throw new Zend_Http_Header_Exception_InvalidArgumentException("Cookie name cannot contain these characters: =,; \\t\\r\\n\\013\\014 ({$name})");
300        }
301
302        $this->name = $name;
303        return $this;
304    }
305
306    /**
307     * @return string
308     */
309    public function getName()
310    {
311        return $this->name;
312    }
313
314    /**
315     * @param string $value
316     */
317    public function setValue($value)
318    {
319        Zend_Http_Header_HeaderValue::assertValid($value);
320        $this->value = $value;
321        return $this;
322    }
323
324    /**
325     * @return string
326     */
327    public function getValue()
328    {
329        return $this->value;
330    }
331
332    /**
333     * Set version
334     *
335     * @param integer $version
336     */
337    public function setVersion($version)
338    {
339        if (!is_int($version)) {
340            throw new Zend_Http_Header_Exception_InvalidArgumentException('Invalid Version number specified');
341        }
342        $this->version = $version;
343    }
344
345    /**
346     * Get version
347     *
348     * @return integer
349     */
350    public function getVersion()
351    {
352        return $this->version;
353    }
354
355    /**
356     * Set Max-Age
357     *
358     * @param integer $maxAge
359     */
360    public function setMaxAge($maxAge)
361    {
362        if (!is_int($maxAge) || ($maxAge<0)) {
363            throw new Zend_Http_Header_Exception_InvalidArgumentException('Invalid Max-Age number specified');
364        }
365        $this->maxAge = $maxAge;
366    }
367
368    /**
369     * Get Max-Age
370     *
371     * @return integer
372     */
373    public function getMaxAge()
374    {
375        return $this->maxAge;
376    }
377
378    /**
379     * @param int $expires
380     * @return SetCookie
381     */
382    public function setExpires($expires)
383    {
384        if (!empty($expires)) {
385            if (is_string($expires)) {
386                $expires = strtotime($expires);
387            } elseif (!is_int($expires)) {
388                throw new Zend_Http_Header_Exception_InvalidArgumentException('Invalid expires time specified');
389            }
390            $this->expires = (int) $expires;
391        }
392        return $this;
393    }
394
395    /**
396     * @return int
397     */
398    public function getExpires($inSeconds = false)
399    {
400        if ($this->expires == null) {
401            return;
402        }
403        if ($inSeconds) {
404            return $this->expires;
405        }
406        return gmdate('D, d-M-Y H:i:s', $this->expires) . ' GMT';
407    }
408
409    /**
410     * @param string $domain
411     */
412    public function setDomain($domain)
413    {
414        Zend_Http_Header_HeaderValue::assertValid($domain);
415        $this->domain = $domain;
416        return $this;
417    }
418
419    /**
420     * @return string
421     */
422    public function getDomain()
423    {
424        return $this->domain;
425    }
426
427    /**
428     * @param string $path
429     */
430    public function setPath($path)
431    {
432        Zend_Http_Header_HeaderValue::assertValid($path);
433        $this->path = $path;
434        return $this;
435    }
436
437    /**
438     * @return string
439     */
440    public function getPath()
441    {
442        return $this->path;
443    }
444
445    /**
446     * @param boolean $secure
447     */
448    public function setSecure($secure)
449    {
450        $this->secure = $secure;
451        return $this;
452    }
453
454    /**
455     * @return boolean
456     */
457    public function isSecure()
458    {
459        return $this->secure;
460    }
461
462    /**
463     * @param bool $httponly
464     */
465    public function setHttponly($httponly)
466    {
467        $this->httponly = $httponly;
468        return $this;
469    }
470
471    /**
472     * @return bool
473     */
474    public function isHttponly()
475    {
476        return $this->httponly;
477    }
478
479    /**
480     * Check whether the cookie has expired
481     *
482     * Always returns false if the cookie is a session cookie (has no expiry time)
483     *
484     * @param int $now Timestamp to consider as "now"
485     * @return boolean
486     */
487    public function isExpired($now = null)
488    {
489        if ($now === null) {
490            $now = time();
491        }
492
493        if (is_int($this->expires) && $this->expires < $now) {
494            return true;
495        } else {
496            return false;
497        }
498    }
499
500    /**
501     * Check whether the cookie is a session cookie (has no expiry time set)
502     *
503     * @return boolean
504     */
505    public function isSessionCookie()
506    {
507        return ($this->expires === null);
508    }
509
510    public function isValidForRequest($requestDomain, $path, $isSecure = false)
511    {
512        if ($this->getDomain() && (strrpos($requestDomain, $this->getDomain()) !== false)) {
513            return false;
514        }
515
516        if ($this->getPath() && (strpos($path, $this->getPath()) !== 0)) {
517            return false;
518        }
519
520        if ($this->secure && $this->isSecure()!==$isSecure) {
521            return false;
522        }
523
524        return true;
525
526    }
527
528    public function toString()
529    {
530        return $this->getFieldName() . ': ' . $this->getFieldValue();
531    }
532
533    public function __toString()
534    {
535        return $this->toString();
536    }
537
538    public function toStringMultipleHeaders(array $headers)
539    {
540        $headerLine = $this->toString();
541        /* @var $header SetCookie */
542        foreach ($headers as $header) {
543            if (!$header instanceof Zend_Http_Header_SetCookie) {
544                throw new Zend_Http_Header_Exception_RuntimeException(
545                    'The SetCookie multiple header implementation can only accept an array of SetCookie headers'
546                );
547            }
548            $headerLine .= ', ' . $header->getFieldValue();
549        }
550        return $headerLine;
551    }
552
553
554}
555