Passed
Push — master ( 199170...103751 )
by Alexander
02:29
created

Cookie::addToResponse()   B

Complexity

Conditions 7
Paths 64

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 14
c 1
b 0
f 0
nc 64
nop 1
dl 0
loc 24
rs 8.8333
1
<?php
2
namespace Yiisoft\Yii\Web;
3
4
use Psr\Http\Message\ResponseInterface;
5
6
/**
7
 * Cookie helps adding Set-Cookie header response in order to set cookie
8
 */
9
class Cookie
10
{
11
    /**
12
     * SameSite policy Lax will prevent the cookie from being sent by the browser in all cross-site browsing context
13
     * during CSRF-prone request methods (e.g. POST, PUT, PATCH etc).
14
     * E.g. a POST request from https://otherdomain.com to https://yourdomain.com will not include the cookie, however a GET request will.
15
     * When a user follows a link from https://otherdomain.com to https://yourdomain.com it will include the cookie
16
     * @see $sameSite
17
     */
18
    public const SAME_SITE_LAX = 'Lax';
19
20
    /**
21
     * SameSite policy Strict will prevent the cookie from being sent by the browser in all cross-site browsing context
22
     * regardless of the request method and even when following a regular link.
23
     * E.g. a GET request from https://otherdomain.com to https://yourdomain.com or a user following a link from
24
     * https://otherdomain.com to https://yourdomain.com will not include the cookie.
25
     * @see $sameSite
26
     */
27
    public const SAME_SITE_STRICT = 'Strict';
28
29
    public const SAME_SITE_NONE = '';
30
31
    /**
32
     * @var string name of the cookie
33
     */
34
    private $name;
35
36
    /**
37
     * @var string value of the cookie
38
     */
39
    private $value;
40
41
    /**
42
     * @var string domain of the cookie
43
     */
44
    private $domain;
45
46
    /**
47
     * @var int the timestamp at which the cookie expires. This is the server timestamp.
48
     * Defaults to null, meaning "until the browser is closed".
49
     */
50
    private $expire;
51
52
    /**
53
     * @var string the path on the server in which the cookie will be available on. The default is '/'.
54
     */
55
    private $path = '/';
56
57
    /**
58
     * @var bool whether cookie should be sent via secure connection
59
     */
60
    private $secure = true;
61
62
    /**
63
     * @var bool whether the cookie should be accessible only through the HTTP protocol.
64
     * By setting this property to true, the cookie will not be accessible by scripting languages,
65
     * such as JavaScript, which can effectively help to reduce identity theft through XSS attacks.
66
     */
67
    private $httpOnly = true;
68
69
    /**
70
     * @var string SameSite prevents the browser from sending this cookie along with cross-site requests.
71
     * Please note that this feature is only supported since PHP 7.3.0
72
     * For better security, an exception will be thrown if `sameSite` is set while using an unsupported version of PHP.
73
     * To use this feature across different PHP versions check the version first. E.g.
74
     * ```php
75
     * $cookie->sameSite = PHP_VERSION_ID >= 70300 ? yii\web\Cookie::SAME_SITE_LAX : null,
76
     * ```
77
     * @see https://www.owasp.org/index.php/SameSite for more information about sameSite.
78
     */
79
    private $sameSite = self::SAME_SITE_LAX;
80
81
    public function __construct(string $name, string $value)
82
    {
83
        // @see https://tools.ietf.org/html/rfc6265#section-4
84
        // @see https://tools.ietf.org/html/rfc2616#section-2.2
85
        if (!preg_match('~[a-z0-9._\-]+~i', $name)) {
86
            throw new \InvalidArgumentException("The cookie name \"$name\" contains invalid characters.");
87
        }
88
89
        $this->name = $name;
90
        $this->value = $value;
91
    }
92
93
    public function domain(string $domain): self
94
    {
95
        $new = clone $this;
96
        $new->domain = $domain;
97
        return $new;
98
    }
99
100
    public function expireAt(\DateTimeInterface $dateTime): self
101
    {
102
        $new = clone $this;
103
        $new->expire = (int)$dateTime->format('U');
104
        return $new;
105
    }
106
107
    public function expireWhenBrowserIsClosed(): self
108
    {
109
        $new = clone $this;
110
        $new->expire = null;
111
        return $new;
112
    }
113
114
    public function path(string $path): self
115
    {
116
        $new = clone $this;
117
        $new->path = $path;
118
        return $new;
119
    }
120
121
    public function secure(bool $secure): self
122
    {
123
        $new = clone $this;
124
        $new->secure = $secure;
125
        return $new;
126
    }
127
128
    public function httpOnly(bool $httpOnly): self
129
    {
130
        $new = clone $this;
131
        $new->httpOnly = $httpOnly;
132
        return $new;
133
    }
134
135
    public function sameSite(string $sameSite): self
136
    {
137
        if (!in_array($sameSite, [self::SAME_SITE_LAX, self::SAME_SITE_STRICT, self::SAME_SITE_NONE], true)) {
138
            throw new \InvalidArgumentException('sameSite should be either Lax or Strict');
139
        }
140
141
        $new = clone $this;
142
        $new->sameSite = $sameSite;
143
        return $new;
144
    }
145
146
    public function addToResponse(ResponseInterface $response): ResponseInterface
147
    {
148
        $headerValue = $this->name . '=' . urlencode($this->value);
149
150
        if ($this->expire !== 0) {
151
            $headerValue .= '; expires=' . gmdate('D, d-M-Y H:i:s T', $this->expire);
152
        }
153
        if (empty($this->path) === false) {
154
            $headerValue .= '; path=' . $this->path;
155
        }
156
        if (empty($this->domain) === false) {
157
            $headerValue .= '; domain=' . $this->domain;
158
        }
159
        if ($this->secure) {
160
            $headerValue .= '; secure';
161
        }
162
        if ($this->httpOnly) {
163
            $headerValue .= '; httponly';
164
        }
165
        if ($this->sameSite !== '') {
166
            $headerValue .= '; samesite=' . $this->sameSite;
167
        }
168
169
        return $response->withAddedHeader('Set-Cookie', $headerValue);
170
    }
171
}
172