-
-
Notifications
You must be signed in to change notification settings - Fork 168
/
Cookie.php
234 lines (208 loc) · 5.43 KB
/
Cookie.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
<?php
namespace Kirby\Http;
use Kirby\Cms\App;
use Kirby\Toolkit\Str;
/**
* The `Cookie` class helps you to
* handle cookies in your projects.
*
* @package Kirby Http
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://opensource.org/licenses/MIT
*/
class Cookie
{
/**
* Key to use for cookie signing
*/
public static string $key = 'KirbyHttpCookieKey';
/**
* Set a new cookie
*
* <code>
*
* cookie::set('mycookie', 'hello', ['lifetime' => 60]);
* // expires in 1 hour
*
* </code>
*
* @param string $key The name of the cookie
* @param string $value The cookie content
* @param array $options Array of options:
* lifetime, path, domain, secure, httpOnly, sameSite
* @return bool true: cookie was created,
* false: cookie creation failed
*/
public static function set(
string $key,
string $value,
array $options = []
): bool {
// modify CMS caching behavior
static::trackUsage($key);
// extract options
$expires = static::lifetime($options['lifetime'] ?? 0);
$path = $options['path'] ?? '/';
$domain = $options['domain'] ?? null;
$secure = $options['secure'] ?? false;
$httponly = $options['httpOnly'] ?? true;
$samesite = $options['sameSite'] ?? 'Lax';
// add an HMAC signature of the value
$value = static::hmac($value) . '+' . $value;
// store that thing in the cookie global
$_COOKIE[$key] = $value;
// store the cookie
return setcookie(
$key,
$value,
compact('expires', 'path', 'domain', 'secure', 'httponly', 'samesite')
);
}
/**
* Calculates the lifetime for a cookie
*
* @param int $minutes Number of minutes or timestamp
*/
public static function lifetime(int $minutes): int
{
// absolute timestamp
if ($minutes > 1000000000) {
return $minutes;
}
// minutes from now
if ($minutes > 0) {
return time() + ($minutes * 60);
}
return 0;
}
/**
* Stores a cookie forever
*
* <code>
*
* cookie::forever('mycookie', 'hello');
* // never expires
*
* </code>
*
* @param string $key The name of the cookie
* @param string $value The cookie content
* @param array $options Array of options:
* path, domain, secure, httpOnly
* @return bool true: cookie was created,
* false: cookie creation failed
*/
public static function forever(
string $key,
string $value,
array $options = []
): bool {
// 9999-12-31 if supported (lower on 32-bit servers)
$options['lifetime'] = min(253402214400, PHP_INT_MAX);
return static::set($key, $value, $options);
}
/**
* Get a cookie value
*
* <code>
* cookie::get('mycookie', 'peter');
* // sample output: 'hello' or if the cookie is not set 'peter'
* </code>
*
* @param string|null $key The name of the cookie
* @param string|null $default The default value, which should be returned
* if the cookie has not been found
* @return string|array|null The found value
*/
public static function get(
string|null $key = null,
string|null $default = null
): string|array|null {
if ($key === null) {
return $_COOKIE;
}
// modify CMS caching behavior
static::trackUsage($key);
if ($value = $_COOKIE[$key] ?? null) {
return static::parse($value);
}
return $default;
}
/**
* Checks if a cookie exists
*/
public static function exists(string $key): bool
{
return static::get($key) !== null;
}
/**
* Creates a HMAC for the cookie value
* Used as a cookie signature to prevent easy tampering with cookie data
*/
protected static function hmac(string $value): string
{
return hash_hmac('sha1', $value, static::$key);
}
/**
* Parses the hashed value from a cookie
* and tries to extract the value
*/
protected static function parse(string $string): string|null
{
// if no hash-value separator is present, we can't parse the value
if (strpos($string, '+') === false) {
return null;
}
// extract hash and value
$hash = Str::before($string, '+');
$value = Str::after($string, '+');
// if the hash or the value is missing at all return null
// $value can be an empty string, $hash can't be!
if ($hash === '') {
return null;
}
// compare the extracted hash with the hashed value
// don't accept value if the hash is invalid
if (hash_equals(static::hmac($value), $hash) !== true) {
return null;
}
return $value;
}
/**
* Remove a cookie
*
* <code>
*
* cookie::remove('mycookie');
* // mycookie is now gone
*
* </code>
*
* @param string $key The name of the cookie
* @return bool true: the cookie has been removed,
* false: the cookie could not be removed
*/
public static function remove(string $key): bool
{
if (isset($_COOKIE[$key]) === true) {
unset($_COOKIE[$key]);
return setcookie($key, '', 1, '/') && setcookie($key, false);
}
return false;
}
/**
* Tells the CMS responder that the response relies on a cookie and
* its value (even if the cookie isn't set in the current request);
* this ensures that the response is only cached for visitors who don't
* have this cookie set;
* https://github.com/getkirby/kirby/issues/4423#issuecomment-1166300526
*/
protected static function trackUsage(string $key): void
{
// lazily request the instance for non-CMS use cases
$kirby = App::instance(null, true);
$kirby?->response()->usesCookie($key);
}
}