Completed
Push — master ( 9c21b6...43c791 )
by David
04:48
created

CookiePlugin::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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
     * @return Cookie|null
95
     *
96
     * @throws TransferException
97
     */
98 2
    private function createCookie(RequestInterface $request, string $setCookieHeader)
99
    {
100 2
        $parts = array_map('trim', explode(';', $setCookieHeader));
101
102 2
        if (empty($parts) || !strpos($parts[0], '=')) {
103
            return null;
104
        }
105
106 2
        list($name, $cookieValue) = $this->createValueKey(array_shift($parts));
107
108 2
        $maxAge = null;
109 2
        $expires = null;
110 2
        $domain = $request->getUri()->getHost();
111 2
        $path = $request->getUri()->getPath();
112 2
        $secure = false;
113 2
        $httpOnly = false;
114
115
        // Add the cookie pieces into the parsed data array
116 2
        foreach ($parts as $part) {
117 2
            list($key, $value) = $this->createValueKey($part);
118
119 2
            switch (strtolower($key)) {
120 2
                case 'expires':
121
                    try {
122 2
                        $expires = CookieUtil::parseDate($value);
123 1
                    } catch (UnexpectedValueException $e) {
124 1
                        throw new TransferException(
125 1
                            sprintf(
126 1
                                'Cookie header `%s` expires value `%s` could not be converted to date',
127 1
                                $name,
128 1
                                $value
129
                            ),
130 1
                            0,
131 1
                            $e
132
                        );
133
                    }
134
135 1
                    break;
136
137 1
                case 'max-age':
138 1
                    $maxAge = (int) $value;
139
140 1
                    break;
141
142 1
                case 'domain':
143 1
                    $domain = $value;
144
145 1
                    break;
146
147 1
                case 'path':
148 1
                    $path = $value;
149
150 1
                    break;
151
152 1
                case 'secure':
153 1
                    $secure = true;
154
155 1
                    break;
156
157 1
                case 'httponly':
158 1
                    $httpOnly = true;
159
160 1
                    break;
161
            }
162
        }
163
164 1
        return new Cookie($name, $cookieValue, $maxAge, $domain, $path, $secure, $httpOnly, $expires);
165
    }
166
167
    /**
168
     * Separates key/value pair from cookie.
169
     *
170
     * @param string $part A single cookie value in format key=value
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