Completed
Push — master ( 75470e...1faee5 )
by David
03:25 queued 16s
created

CookiePlugin::createValueKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Http\Client\Common\Plugin;
6
7
use Http\Client\Common\Plugin;
8
use Http\Client\Exception\TransferException;
9
use Http\Message\Cookie;
10
use Http\Message\CookieJar;
11
use Http\Message\CookieUtil;
12
use Http\Message\Exception\UnexpectedValueException;
13
use Http\Promise\Promise;
14
use Psr\Http\Message\RequestInterface;
15
use Psr\Http\Message\ResponseInterface;
16
17
/**
18
 * Handle request cookies.
19
 *
20
 * @author Joel Wurtz <[email protected]>
21
 */
22
final class CookiePlugin implements Plugin
23
{
24
    /**
25
     * Cookie storage.
26
     *
27
     * @var CookieJar
28
     */
29
    private $cookieJar;
30
31 13
    public function __construct(CookieJar $cookieJar)
32
    {
33 13
        $this->cookieJar = $cookieJar;
34 13
    }
35
36
    /**
37
     * {@inheritdoc}
38
     */
39 11
    public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
40
    {
41 11
        $cookies = [];
42 11
        foreach ($this->cookieJar->getCookies() as $cookie) {
43 9
            if ($cookie->isExpired()) {
44 1
                continue;
45
            }
46
47 8
            if (!$cookie->matchDomain($request->getUri()->getHost())) {
48 2
                continue;
49
            }
50
51 6
            if (!$cookie->matchPath($request->getUri()->getPath())) {
52 1
                continue;
53
            }
54
55 5
            if ($cookie->isSecure() && ('https' !== $request->getUri()->getScheme())) {
56 1
                continue;
57
            }
58
59 4
            $cookies[] = sprintf('%s=%s', $cookie->getName(), $cookie->getValue());
60
        }
61
62 11
        if (!empty($cookies)) {
63 4
            $request = $request->withAddedHeader('Cookie', implode('; ', array_unique($cookies)));
64
        }
65
66
        return $next($request)->then(function (ResponseInterface $response) use ($request) {
67 11
            if ($response->hasHeader('Set-Cookie')) {
68 2
                $setCookies = $response->getHeader('Set-Cookie');
69
70 2
                foreach ($setCookies as $setCookie) {
71 2
                    $cookie = $this->createCookie($request, $setCookie);
72
73
                    // Cookie invalid do not use it
74 1
                    if (null === $cookie) {
75
                        continue;
76
                    }
77
78
                    // Restrict setting cookie from another domain
79 1
                    if (!preg_match("/\.{$cookie->getDomain()}$/", '.'.$request->getUri()->getHost())) {
80
                        continue;
81
                    }
82
83 1
                    $this->cookieJar->addCookie($cookie);
84
                }
85
            }
86
87 10
            return $response;
88 11
        });
89
    }
90
91
    /**
92
     * Creates a cookie from a string.
93
     *
94
     * @throws TransferException
95
     */
96 2
    private function createCookie(RequestInterface $request, string $setCookieHeader): ?Cookie
97
    {
98 2
        $parts = array_map('trim', explode(';', $setCookieHeader));
99
100 2
        if (empty($parts) || !strpos($parts[0], '=')) {
101
            return null;
102
        }
103
104 2
        list($name, $cookieValue) = $this->createValueKey(array_shift($parts));
105
106 2
        $maxAge = null;
107 2
        $expires = null;
108 2
        $domain = $request->getUri()->getHost();
109 2
        $path = $request->getUri()->getPath();
110 2
        $secure = false;
111 2
        $httpOnly = false;
112
113
        // Add the cookie pieces into the parsed data array
114 2
        foreach ($parts as $part) {
115 2
            list($key, $value) = $this->createValueKey($part);
116
117 2
            switch (strtolower($key)) {
118 2
                case 'expires':
119
                    try {
120 2
                        $expires = CookieUtil::parseDate($value);
121 1
                    } catch (UnexpectedValueException $e) {
122 1
                        throw new TransferException(
123 1
                            sprintf(
124 1
                                'Cookie header `%s` expires value `%s` could not be converted to date',
125
                                $name,
126
                                $value
127
                            ),
128 1
                            0,
129
                            $e
130
                        );
131
                    }
132
133 1
                    break;
134
135 1
                case 'max-age':
136 1
                    $maxAge = (int) $value;
137
138 1
                    break;
139
140 1
                case 'domain':
141 1
                    $domain = $value;
142
143 1
                    break;
144
145 1
                case 'path':
146 1
                    $path = $value;
147
148 1
                    break;
149
150 1
                case 'secure':
151 1
                    $secure = true;
152
153 1
                    break;
154
155 1
                case 'httponly':
156 1
                    $httpOnly = true;
157
158 1
                    break;
159
            }
160
        }
161
162 1
        return new Cookie($name, $cookieValue, $maxAge, $domain, $path, $secure, $httpOnly, $expires);
163
    }
164
165
    /**
166
     * Separates key/value pair from cookie.
167
     *
168
     * @param string $part A single cookie value in format key=value
169
     *
170
     * @return string[]
171
     */
172 2
    private function createValueKey(string $part): array
173
    {
174 2
        $parts = explode('=', $part, 2);
175 2
        $key = trim($parts[0]);
176 2
        $value = isset($parts[1]) ? trim($parts[1]) : true;
177
178 2
        return [$key, $value];
179
    }
180
}
181