1<?php
2
3namespace Facebook\WebDriver;
4
5use InvalidArgumentException;
6
7/**
8 * Set values of an cookie.
9 *
10 * Implements ArrayAccess for backwards compatibility.
11 *
12 * @see https://w3c.github.io/webdriver/webdriver-spec.html#cookies
13 */
14class Cookie implements \ArrayAccess
15{
16    /** @var array */
17    protected $cookie = [];
18
19    /**
20     * @param string $name The name of the cookie; may not be null or an empty string.
21     * @param string $value The cookie value; may not be null.
22     */
23    public function __construct($name, $value)
24    {
25        $this->validateCookieName($name);
26        $this->validateCookieValue($value);
27
28        $this->cookie['name'] = $name;
29        $this->cookie['value'] = $value;
30    }
31
32    /**
33     * @param array $cookieArray The cookie fields; must contain name and value.
34     * @return Cookie
35     */
36    public static function createFromArray(array $cookieArray)
37    {
38        if (!isset($cookieArray['name'])) {
39            throw new InvalidArgumentException('Cookie name should be set');
40        }
41        if (!isset($cookieArray['value'])) {
42            throw new InvalidArgumentException('Cookie value should be set');
43        }
44        $cookie = new self($cookieArray['name'], $cookieArray['value']);
45
46        if (isset($cookieArray['path'])) {
47            $cookie->setPath($cookieArray['path']);
48        }
49        if (isset($cookieArray['domain'])) {
50            $cookie->setDomain($cookieArray['domain']);
51        }
52        if (isset($cookieArray['expiry'])) {
53            $cookie->setExpiry($cookieArray['expiry']);
54        }
55        if (isset($cookieArray['secure'])) {
56            $cookie->setSecure($cookieArray['secure']);
57        }
58        if (isset($cookieArray['httpOnly'])) {
59            $cookie->setHttpOnly($cookieArray['httpOnly']);
60        }
61        if (isset($cookieArray['sameSite'])) {
62            $cookie->setSameSite($cookieArray['sameSite']);
63        }
64
65        return $cookie;
66    }
67
68    /**
69     * @return string
70     */
71    public function getName()
72    {
73        return $this->offsetGet('name');
74    }
75
76    /**
77     * @return string
78     */
79    public function getValue()
80    {
81        return $this->offsetGet('value');
82    }
83
84    /**
85     * The path the cookie is visible to. Defaults to "/" if omitted.
86     *
87     * @param string $path
88     */
89    public function setPath($path)
90    {
91        $this->offsetSet('path', $path);
92    }
93
94    /**
95     * @return string|null
96     */
97    public function getPath()
98    {
99        return $this->offsetGet('path');
100    }
101
102    /**
103     * The domain the cookie is visible to. Defaults to the current browsing context's document's URL domain if omitted.
104     *
105     * @param string $domain
106     */
107    public function setDomain($domain)
108    {
109        if (mb_strpos($domain, ':') !== false) {
110            throw new InvalidArgumentException(sprintf('Cookie domain "%s" should not contain a port', $domain));
111        }
112
113        $this->offsetSet('domain', $domain);
114    }
115
116    /**
117     * @return string|null
118     */
119    public function getDomain()
120    {
121        return $this->offsetGet('domain');
122    }
123
124    /**
125     * The cookie's expiration date, specified in seconds since Unix Epoch.
126     *
127     * @param int $expiry
128     */
129    public function setExpiry($expiry)
130    {
131        $this->offsetSet('expiry', (int) $expiry);
132    }
133
134    /**
135     * @return int|null
136     */
137    public function getExpiry()
138    {
139        return $this->offsetGet('expiry');
140    }
141
142    /**
143     * Whether this cookie requires a secure connection (https). Defaults to false if omitted.
144     *
145     * @param bool $secure
146     */
147    public function setSecure($secure)
148    {
149        $this->offsetSet('secure', $secure);
150    }
151
152    /**
153     * @return bool|null
154     */
155    public function isSecure()
156    {
157        return $this->offsetGet('secure');
158    }
159
160    /**
161     * Whether the cookie is an HTTP only cookie. Defaults to false if omitted.
162     *
163     * @param bool $httpOnly
164     */
165    public function setHttpOnly($httpOnly)
166    {
167        $this->offsetSet('httpOnly', $httpOnly);
168    }
169
170    /**
171     * @return bool|null
172     */
173    public function isHttpOnly()
174    {
175        return $this->offsetGet('httpOnly');
176    }
177
178    /**
179     * The cookie's same-site value.
180     *
181     * @param string $sameSite
182     */
183    public function setSameSite($sameSite)
184    {
185        $this->offsetSet('sameSite', $sameSite);
186    }
187
188    /**
189     * @return string|null
190     */
191    public function getSameSite()
192    {
193        return $this->offsetGet('sameSite');
194    }
195
196    /**
197     * @return array
198     */
199    public function toArray()
200    {
201        $cookie = $this->cookie;
202        if (!isset($cookie['secure'])) {
203            // Passing a boolean value for the "secure" flag is mandatory when using geckodriver
204            $cookie['secure'] = false;
205        }
206
207        return $cookie;
208    }
209
210    /**
211     * @param mixed $offset
212     * @return bool
213     */
214    #[\ReturnTypeWillChange]
215    public function offsetExists($offset)
216    {
217        return isset($this->cookie[$offset]);
218    }
219
220    /**
221     * @param mixed $offset
222     * @return mixed
223     */
224    #[\ReturnTypeWillChange]
225    public function offsetGet($offset)
226    {
227        return $this->offsetExists($offset) ? $this->cookie[$offset] : null;
228    }
229
230    /**
231     * @param mixed $offset
232     * @param mixed $value
233     * @return void
234     */
235    #[\ReturnTypeWillChange]
236    public function offsetSet($offset, $value)
237    {
238        if ($value === null) {
239            unset($this->cookie[$offset]);
240        } else {
241            $this->cookie[$offset] = $value;
242        }
243    }
244
245    /**
246     * @param mixed $offset
247     * @return void
248     */
249    #[\ReturnTypeWillChange]
250    public function offsetUnset($offset)
251    {
252        unset($this->cookie[$offset]);
253    }
254
255    /**
256     * @param string $name
257     */
258    protected function validateCookieName($name)
259    {
260        if ($name === null || $name === '') {
261            throw new InvalidArgumentException('Cookie name should be non-empty');
262        }
263
264        if (mb_strpos($name, ';') !== false) {
265            throw new InvalidArgumentException('Cookie name should not contain a ";"');
266        }
267    }
268
269    /**
270     * @param string $value
271     */
272    protected function validateCookieValue($value)
273    {
274        if ($value === null) {
275            throw new InvalidArgumentException('Cookie value is required when setting a cookie');
276        }
277    }
278}
279