Completed
Push — master ( 96c43b...fcf598 )
by Tobias
03:54
created

CookiePlugin   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 162
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 87.5%

Importance

Changes 0
Metric Value
wmc 25
lcom 1
cbo 7
dl 0
loc 162
ccs 63
cts 72
cp 0.875
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A createValueKey() 0 8 2
C handleRequest() 0 46 11
C createCookie() 0 68 11
1
<?php
2
3
namespace Http\Client\Common\Plugin;
4
5
use Http\Client\Common\Plugin;
6
use Http\Client\Exception\TransferException;
7
use Http\Message\Cookie;
8
use Http\Message\CookieJar;
9
use Http\Message\CookieUtil;
10
use Http\Message\Exception\UnexpectedValueException;
11
use Psr\Http\Message\RequestInterface;
12
use Psr\Http\Message\ResponseInterface;
13
14
/**
15
 * Handle request cookies.
16
 *
17
 * @author Joel Wurtz <[email protected]>
18
 */
19
final class CookiePlugin implements Plugin
20
{
21
    /**
22
     * Cookie storage.
23
     *
24
     * @var CookieJar
25
     */
26
    private $cookieJar;
27
28
    /**
29
     * @param CookieJar $cookieJar
30
     */
31 12
    public function __construct(CookieJar $cookieJar)
32
    {
33 12
        $this->cookieJar = $cookieJar;
34 12
    }
35
36
    /**
37
     * {@inheritdoc}
38
     */
39 10
    public function handleRequest(RequestInterface $request, callable $next, callable $first)
40
    {
41 10
        foreach ($this->cookieJar->getCookies() as $cookie) {
42 8
            if ($cookie->isExpired()) {
43 1
                continue;
44
            }
45
46 7
            if (!$cookie->matchDomain($request->getUri()->getHost())) {
47 2
                continue;
48
            }
49
50 5
            if (!$cookie->matchPath($request->getUri()->getPath())) {
51 1
                continue;
52
            }
53
54 4
            if ($cookie->isSecure() && ('https' !== $request->getUri()->getScheme())) {
55 1
                continue;
56
            }
57
58 3
            $request = $request->withAddedHeader('Cookie', sprintf('%s=%s', $cookie->getName(), $cookie->getValue()));
59
        }
60
61 10
        return $next($request)->then(function (ResponseInterface $response) use ($request) {
62 2
            if ($response->hasHeader('Set-Cookie')) {
63 2
                $setCookies = $response->getHeader('Set-Cookie');
64
65 2
                foreach ($setCookies as $setCookie) {
66 2
                    $cookie = $this->createCookie($request, $setCookie);
67
68
                    // Cookie invalid do not use it
69 1
                    if (null === $cookie) {
70
                        continue;
71
                    }
72
73
                    // Restrict setting cookie from another domain
74 1
                    if (!preg_match("/\.{$cookie->getDomain()}$/", '.'.$request->getUri()->getHost())) {
75
                        continue;
76
                    }
77
78 1
                    $this->cookieJar->addCookie($cookie);
79
                }
80
            }
81
82 1
            return $response;
83 10
        });
84
    }
85
86
    /**
87
     * Creates a cookie from a string.
88
     *
89
     * @param RequestInterface $request
90
     * @param $setCookie
91
     *
92
     * @return Cookie|null
93
     *
94
     * @throws TransferException
95
     */
96 2
    private function createCookie(RequestInterface $request, $setCookie)
97
    {
98 2
        $parts = array_map('trim', explode(';', $setCookie));
99
100 2
        if (empty($parts) || !strpos($parts[0], '=')) {
101
            return;
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
                case 'expires':
119
                    try {
120 2
                        $expires = CookieUtil::parseDate($value);
0 ignored issues
show
Bug introduced by
It seems like $value defined by $this->createValueKey($part) on line 115 can also be of type boolean; however, Http\Message\CookieUtil::parseDate() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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 1
                                $name,
126 1
                                $value
127
                            ),
128 1
                            null,
129 1
                            $e
130
                        );
131
                    }
132
133 1
                    break;
134
135
                case 'max-age':
136 1
                    $maxAge = (int) $value;
137
138 1
                    break;
139
140
                case 'domain':
141 1
                    $domain = $value;
142
143 1
                    break;
144
145
                case 'path':
146 1
                    $path = $value;
147
148 1
                    break;
149
150
                case 'secure':
151 1
                    $secure = true;
152
153 1
                    break;
154
155
                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);
0 ignored issues
show
Bug introduced by
It seems like $cookieValue defined by $this->createValueKey(array_shift($parts)) on line 104 can also be of type boolean; however, Http\Message\Cookie::__construct() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $domain defined by $value on line 141 can also be of type boolean; however, Http\Message\Cookie::__construct() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $path defined by $value on line 146 can also be of type boolean; however, Http\Message\Cookie::__construct() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
163
    }
164
165
    /**
166
     * Separates key/value pair from cookie.
167
     *
168
     * @param $part
169
     *
170
     * @return array
171
     */
172 2
    private function createValueKey($part)
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