Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
namespace GuzzleHttp\Cookie;
4
 
5
use Psr\Http\Message\RequestInterface;
6
use Psr\Http\Message\ResponseInterface;
7
 
8
/**
9
 * Cookie jar that stores cookies as an array
10
 */
11
class CookieJar implements CookieJarInterface
12
{
13
    /**
14
     * @var SetCookie[] Loaded cookie data
15
     */
16
    private $cookies = [];
17
 
18
    /**
19
     * @var bool
20
     */
21
    private $strictMode;
22
 
23
    /**
24
     * @param bool  $strictMode  Set to true to throw exceptions when invalid
25
     *                           cookies are added to the cookie jar.
26
     * @param array $cookieArray Array of SetCookie objects or a hash of
27
     *                           arrays that can be used with the SetCookie
28
     *                           constructor
29
     */
30
    public function __construct(bool $strictMode = false, array $cookieArray = [])
31
    {
32
        $this->strictMode = $strictMode;
33
 
34
        foreach ($cookieArray as $cookie) {
35
            if (!($cookie instanceof SetCookie)) {
36
                $cookie = new SetCookie($cookie);
37
            }
38
            $this->setCookie($cookie);
39
        }
40
    }
41
 
42
    /**
43
     * Create a new Cookie jar from an associative array and domain.
44
     *
45
     * @param array  $cookies Cookies to create the jar from
46
     * @param string $domain  Domain to set the cookies to
47
     */
48
    public static function fromArray(array $cookies, string $domain): self
49
    {
50
        $cookieJar = new self();
51
        foreach ($cookies as $name => $value) {
52
            $cookieJar->setCookie(new SetCookie([
1441 ariadna 53
                'Domain' => $domain,
54
                'Name' => $name,
55
                'Value' => $value,
56
                'Discard' => true,
1 efrain 57
            ]));
58
        }
59
 
60
        return $cookieJar;
61
    }
62
 
63
    /**
64
     * Evaluate if this cookie should be persisted to storage
65
     * that survives between requests.
66
     *
67
     * @param SetCookie $cookie              Being evaluated.
68
     * @param bool      $allowSessionCookies If we should persist session cookies
69
     */
70
    public static function shouldPersist(SetCookie $cookie, bool $allowSessionCookies = false): bool
71
    {
72
        if ($cookie->getExpires() || $allowSessionCookies) {
73
            if (!$cookie->getDiscard()) {
74
                return true;
75
            }
76
        }
77
 
78
        return false;
79
    }
80
 
81
    /**
82
     * Finds and returns the cookie based on the name
83
     *
84
     * @param string $name cookie name to search for
85
     *
86
     * @return SetCookie|null cookie that was found or null if not found
87
     */
88
    public function getCookieByName(string $name): ?SetCookie
89
    {
90
        foreach ($this->cookies as $cookie) {
91
            if ($cookie->getName() !== null && \strcasecmp($cookie->getName(), $name) === 0) {
92
                return $cookie;
93
            }
94
        }
95
 
96
        return null;
97
    }
98
 
99
    public function toArray(): array
100
    {
101
        return \array_map(static function (SetCookie $cookie): array {
102
            return $cookie->toArray();
103
        }, $this->getIterator()->getArrayCopy());
104
    }
105
 
106
    public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void
107
    {
108
        if (!$domain) {
109
            $this->cookies = [];
1441 ariadna 110
 
1 efrain 111
            return;
112
        } elseif (!$path) {
113
            $this->cookies = \array_filter(
114
                $this->cookies,
115
                static function (SetCookie $cookie) use ($domain): bool {
116
                    return !$cookie->matchesDomain($domain);
117
                }
118
            );
119
        } elseif (!$name) {
120
            $this->cookies = \array_filter(
121
                $this->cookies,
122
                static function (SetCookie $cookie) use ($path, $domain): bool {
1441 ariadna 123
                    return !($cookie->matchesPath($path)
124
                        && $cookie->matchesDomain($domain));
1 efrain 125
                }
126
            );
127
        } else {
128
            $this->cookies = \array_filter(
129
                $this->cookies,
130
                static function (SetCookie $cookie) use ($path, $domain, $name) {
1441 ariadna 131
                    return !($cookie->getName() == $name
132
                        && $cookie->matchesPath($path)
133
                        && $cookie->matchesDomain($domain));
1 efrain 134
                }
135
            );
136
        }
137
    }
138
 
139
    public function clearSessionCookies(): void
140
    {
141
        $this->cookies = \array_filter(
142
            $this->cookies,
143
            static function (SetCookie $cookie): bool {
144
                return !$cookie->getDiscard() && $cookie->getExpires();
145
            }
146
        );
147
    }
148
 
149
    public function setCookie(SetCookie $cookie): bool
150
    {
151
        // If the name string is empty (but not 0), ignore the set-cookie
152
        // string entirely.
153
        $name = $cookie->getName();
154
        if (!$name && $name !== '0') {
155
            return false;
156
        }
157
 
158
        // Only allow cookies with set and valid domain, name, value
159
        $result = $cookie->validate();
160
        if ($result !== true) {
161
            if ($this->strictMode) {
1441 ariadna 162
                throw new \RuntimeException('Invalid cookie: '.$result);
1 efrain 163
            }
164
            $this->removeCookieIfEmpty($cookie);
1441 ariadna 165
 
1 efrain 166
            return false;
167
        }
168
 
169
        // Resolve conflicts with previously set cookies
170
        foreach ($this->cookies as $i => $c) {
171
            // Two cookies are identical, when their path, and domain are
172
            // identical.
1441 ariadna 173
            if ($c->getPath() != $cookie->getPath()
174
                || $c->getDomain() != $cookie->getDomain()
175
                || $c->getName() != $cookie->getName()
1 efrain 176
            ) {
177
                continue;
178
            }
179
 
180
            // The previously set cookie is a discard cookie and this one is
181
            // not so allow the new cookie to be set
182
            if (!$cookie->getDiscard() && $c->getDiscard()) {
183
                unset($this->cookies[$i]);
184
                continue;
185
            }
186
 
187
            // If the new cookie's expiration is further into the future, then
188
            // replace the old cookie
189
            if ($cookie->getExpires() > $c->getExpires()) {
190
                unset($this->cookies[$i]);
191
                continue;
192
            }
193
 
194
            // If the value has changed, we better change it
195
            if ($cookie->getValue() !== $c->getValue()) {
196
                unset($this->cookies[$i]);
197
                continue;
198
            }
199
 
200
            // The cookie exists, so no need to continue
201
            return false;
202
        }
203
 
204
        $this->cookies[] = $cookie;
205
 
206
        return true;
207
    }
208
 
209
    public function count(): int
210
    {
211
        return \count($this->cookies);
212
    }
213
 
214
    /**
215
     * @return \ArrayIterator<int, SetCookie>
216
     */
217
    public function getIterator(): \ArrayIterator
218
    {
219
        return new \ArrayIterator(\array_values($this->cookies));
220
    }
221
 
222
    public function extractCookies(RequestInterface $request, ResponseInterface $response): void
223
    {
224
        if ($cookieHeader = $response->getHeader('Set-Cookie')) {
225
            foreach ($cookieHeader as $cookie) {
226
                $sc = SetCookie::fromString($cookie);
227
                if (!$sc->getDomain()) {
228
                    $sc->setDomain($request->getUri()->getHost());
229
                }
230
                if (0 !== \strpos($sc->getPath(), '/')) {
231
                    $sc->setPath($this->getCookiePathFromRequest($request));
232
                }
233
                if (!$sc->matchesDomain($request->getUri()->getHost())) {
234
                    continue;
235
                }
236
                // Note: At this point `$sc->getDomain()` being a public suffix should
237
                // be rejected, but we don't want to pull in the full PSL dependency.
238
                $this->setCookie($sc);
239
            }
240
        }
241
    }
242
 
243
    /**
244
     * Computes cookie path following RFC 6265 section 5.1.4
245
     *
1441 ariadna 246
     * @see https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4
1 efrain 247
     */
248
    private function getCookiePathFromRequest(RequestInterface $request): string
249
    {
250
        $uriPath = $request->getUri()->getPath();
251
        if ('' === $uriPath) {
252
            return '/';
253
        }
254
        if (0 !== \strpos($uriPath, '/')) {
255
            return '/';
256
        }
257
        if ('/' === $uriPath) {
258
            return '/';
259
        }
260
        $lastSlashPos = \strrpos($uriPath, '/');
261
        if (0 === $lastSlashPos || false === $lastSlashPos) {
262
            return '/';
263
        }
264
 
265
        return \substr($uriPath, 0, $lastSlashPos);
266
    }
267
 
268
    public function withCookieHeader(RequestInterface $request): RequestInterface
269
    {
270
        $values = [];
271
        $uri = $request->getUri();
272
        $scheme = $uri->getScheme();
273
        $host = $uri->getHost();
274
        $path = $uri->getPath() ?: '/';
275
 
276
        foreach ($this->cookies as $cookie) {
1441 ariadna 277
            if ($cookie->matchesPath($path)
278
                && $cookie->matchesDomain($host)
279
                && !$cookie->isExpired()
280
                && (!$cookie->getSecure() || $scheme === 'https')
1 efrain 281
            ) {
1441 ariadna 282
                $values[] = $cookie->getName().'='
283
                    .$cookie->getValue();
1 efrain 284
            }
285
        }
286
 
287
        return $values
288
            ? $request->withHeader('Cookie', \implode('; ', $values))
289
            : $request;
290
    }
291
 
292
    /**
293
     * If a cookie already exists and the server asks to set it again with a
294
     * null value, the cookie must be deleted.
295
     */
296
    private function removeCookieIfEmpty(SetCookie $cookie): void
297
    {
298
        $cookieValue = $cookie->getValue();
299
        if ($cookieValue === null || $cookieValue === '') {
300
            $this->clear(
301
                $cookie->getDomain(),
302
                $cookie->getPath(),
303
                $cookie->getName()
304
            );
305
        }
306
    }
307
}