PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 1.3.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v1.3.1
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / Cookie.php
matomo / app / core Last commit date
API 6 years ago Access 6 years ago Application 6 years ago Archive 6 years ago ArchiveProcessor 6 years ago Archiver 6 years ago AssetManager 6 years ago Auth 6 years ago Category 6 years ago CliMulti 6 years ago Columns 6 years ago Composer 6 years ago Concurrency 6 years ago Config 6 years ago Container 6 years ago CronArchive 6 years ago DataAccess 5 years ago DataFiles 6 years ago DataTable 6 years ago Db 6 years ago DeviceDetector 5 years ago Email 6 years ago Exception 6 years ago Http 6 years ago Intl 6 years ago Mail 6 years ago Measurable 6 years ago Menu 6 years ago Metrics 6 years ago Notification 6 years ago Period 6 years ago Plugin 6 years ago ProfessionalServices 6 years ago Report 6 years ago ReportRenderer 6 years ago Scheduler 6 years ago Segment 6 years ago Session 6 years ago Settings 6 years ago Tracker 5 years ago Translation 6 years ago UpdateCheck 6 years ago Updater 6 years ago Updates 6 years ago Validators 6 years ago View 6 years ago ViewDataTable 6 years ago Visualization 6 years ago Widget 6 years ago .htaccess 6 years ago Access.php 6 years ago Archive.php 6 years ago ArchiveProcessor.php 6 years ago AssetManager.php 6 years ago Auth.php 6 years ago BaseFactory.php 6 years ago Cache.php 6 years ago CacheId.php 6 years ago CliMulti.php 6 years ago Common.php 6 years ago Config.php 6 years ago Console.php 6 years ago Context.php 6 years ago Cookie.php 5 years ago CronArchive.php 5 years ago DataArray.php 6 years ago DataTable.php 6 years ago Date.php 6 years ago Db.php 6 years ago DbHelper.php 6 years ago Development.php 6 years ago DeviceDetectorFactory.php 6 years ago ErrorHandler.php 6 years ago EventDispatcher.php 6 years ago ExceptionHandler.php 6 years ago FileIntegrity.php 6 years ago Filechecks.php 6 years ago Filesystem.php 6 years ago FrontController.php 6 years ago Http.php 6 years ago IP.php 6 years ago Log.php 6 years ago LogDeleter.php 6 years ago Mail.php 6 years ago Metrics.php 6 years ago MetricsFormatter.php 6 years ago Nonce.php 5 years ago Notification.php 6 years ago NumberFormatter.php 6 years ago Option.php 5 years ago Period.php 6 years ago Piwik.php 6 years ago Plugin.php 6 years ago Profiler.php 6 years ago ProxyHeaders.php 6 years ago ProxyHttp.php 6 years ago QuickForm2.php 6 years ago RankingQuery.php 6 years ago Registry.php 6 years ago ReportRenderer.php 6 years ago ScheduledTask.php 6 years ago Segment.php 6 years ago Sequence.php 6 years ago Session.php 6 years ago SettingsPiwik.php 6 years ago SettingsServer.php 6 years ago Singleton.php 6 years ago Site.php 6 years ago TCPDF.php 6 years ago TaskScheduler.php 6 years ago Theme.php 6 years ago Timer.php 6 years ago Tracker.php 6 years ago Translate.php 6 years ago Twig.php 6 years ago Unzip.php 6 years ago UpdateCheck.php 6 years ago Updater.php 6 years ago Updates.php 6 years ago Url.php 6 years ago UrlHelper.php 6 years ago Version.php 5 years ago View.php 6 years ago bootstrap.php 6 years ago dispatch.php 6 years ago testMinimumPhpVersion.php 6 years ago
Cookie.php
462 lines
1 <?php
2 /**
3 * Piwik - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 *
8 */
9 namespace Piwik;
10
11 use Piwik\Container\StaticContainer;
12
13 /**
14 * Simple class to handle the cookies:
15 * - read a cookie values
16 * - edit an existing cookie and save it
17 * - create a new cookie, set values, expiration date, etc. and save it
18 *
19 */
20 class Cookie
21 {
22 /**
23 * Don't create a cookie bigger than 1k
24 */
25 const MAX_COOKIE_SIZE = 1024;
26
27 /**
28 * The name of the cookie
29 * @var string
30 */
31 protected $name = null;
32
33 /**
34 * The expire time for the cookie (expressed in UNIX Timestamp)
35 * @var int
36 */
37 protected $expire = null;
38
39 /**
40 * Restrict cookie path
41 * @var string
42 */
43 protected $path = '';
44
45 /**
46 * Restrict cookie to a domain (or subdomains)
47 * @var string
48 */
49 protected $domain = '';
50
51 /**
52 * If true, cookie should only be transmitted over secure HTTPS
53 * @var bool
54 */
55 protected $secure = false;
56
57 /**
58 * If true, cookie will only be made available via the HTTP protocol.
59 * Note: not well supported by browsers.
60 * @var bool
61 */
62 protected $httponly = false;
63
64 /**
65 * The content of the cookie
66 * @var array
67 */
68 protected $value = array();
69
70 /**
71 * The character used to separate the tuple name=value in the cookie
72 */
73 const VALUE_SEPARATOR = ':';
74
75 /**
76 * Instantiate a new Cookie object and tries to load the cookie content if the cookie
77 * exists already.
78 *
79 * @param string $cookieName cookie Name
80 * @param int $expire The timestamp after which the cookie will expire, eg time() + 86400;
81 * use 0 (int zero) to expire cookie at end of browser session
82 * @param string $path The path on the server in which the cookie will be available on.
83 * @param bool|string $keyStore Will be used to store several bits of data (eg. one array per website)
84 */
85 public function __construct($cookieName, $expire = null, $path = null, $keyStore = false)
86 {
87 $this->name = $cookieName;
88 $this->path = $path;
89 $this->expire = $expire;
90 if (is_null($expire)
91 || !is_numeric($expire)
92 || $expire < 0
93 ) {
94 $this->expire = $this->getDefaultExpire();
95 }
96
97 $this->keyStore = $keyStore;
98 if ($this->isCookieFound()) {
99 $this->loadContentFromCookie();
100 }
101 }
102
103 /**
104 * Returns true if the visitor already has the cookie.
105 *
106 * @return bool
107 */
108 public function isCookieFound()
109 {
110 return self::isCookieInRequest($this->name);
111 }
112
113 /**
114 * Returns the default expiry time, 2 years
115 *
116 * @return int Timestamp in 2 years
117 */
118 protected function getDefaultExpire()
119 {
120 return time() + 86400 * 365 * 2;
121 }
122
123 /**
124 * setcookie() replacement -- we don't use the built-in function because
125 * it is buggy for some PHP versions.
126 *
127 * @link http://php.net/setcookie
128 *
129 * @param string $Name Name of cookie
130 * @param string $Value Value of cookie
131 * @param int $Expires Time the cookie expires
132 * @param string $Path
133 * @param string $Domain
134 * @param bool $Secure
135 * @param bool $HTTPOnly
136 * @param string $sameSite
137 */
138 protected function setCookie($Name, $Value, $Expires, $Path = '', $Domain = '', $Secure = false, $HTTPOnly = false, $sameSite = false)
139 {
140 if (!empty($Domain)) {
141 // Fix the domain to accept domains with and without 'www.'.
142 if (!strncasecmp($Domain, 'www.', 4)) {
143 $Domain = substr($Domain, 4);
144 }
145 $Domain = '.' . $Domain;
146
147 // Remove port information.
148 $Port = strpos($Domain, ':');
149 if ($Port !== false) {
150 $Domain = substr($Domain, 0, $Port);
151 }
152 }
153
154 $header = 'Set-Cookie: ' . rawurlencode($Name) . '=' . rawurlencode($Value)
155 . (empty($Expires) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', $Expires) . ' GMT')
156 . (empty($Path) ? '' : '; path=' . rawurlencode($Path))
157 . (empty($Domain) ? '' : '; domain=' . $Domain)
158 . (!$Secure ? '' : '; secure')
159 . (!$HTTPOnly ? '' : '; HttpOnly')
160 . (!$sameSite ? '' : '; SameSite=' . rawurlencode($sameSite));
161
162 Common::sendHeader($header, false);
163 }
164
165 /**
166 * We set the privacy policy header
167 */
168 protected function setP3PHeader()
169 {
170 Common::sendHeader("P3P: CP='OTI DSP COR NID STP UNI OTPa OUR'");
171 }
172
173 /**
174 * Delete the cookie
175 */
176 public function delete()
177 {
178 $this->setP3PHeader();
179 $this->setCookie($this->name, 'deleted', time() - 31536001, $this->path, $this->domain);
180 }
181
182 /**
183 * Saves the cookie (set the Cookie header).
184 * You have to call this method before sending any text to the browser or you would get the
185 * "Header already sent" error.
186 * @param string $sameSite Value for SameSite cookie property
187 */
188 public function save($sameSite = null)
189 {
190 if ($sameSite) {
191 $sameSite = self::getSameSiteValueForBrowser($sameSite);
192 }
193 $cookieString = $this->generateContentString();
194 if (strlen($cookieString) > self::MAX_COOKIE_SIZE) {
195 // If the cookie was going to be too large, instead, delete existing cookie and start afresh
196 $this->delete();
197 return;
198 }
199
200 $this->setP3PHeader();
201 $this->setCookie($this->name, $cookieString, $this->expire, $this->path, $this->domain, $this->secure, $this->httponly, $sameSite);
202 }
203
204 /**
205 * Extract signed content from string: content VALUE_SEPARATOR '_=' signature
206 *
207 * @param string $content
208 * @return string|bool Content or false if unsigned
209 */
210 private function extractSignedContent($content)
211 {
212 $signature = substr($content, -40);
213
214 if (substr($content, -43, 3) == self::VALUE_SEPARATOR . '_=' &&
215 $signature === sha1(substr($content, 0, -40) . SettingsPiwik::getSalt())
216 ) {
217 // strip trailing: VALUE_SEPARATOR '_=' signature"
218 return substr($content, 0, -43);
219 }
220
221 return false;
222 }
223
224 /**
225 * Load the cookie content into a php array.
226 * Parses the cookie string to extract the different variables.
227 * Unserialize the array when necessary.
228 * Decode the non numeric values that were base64 encoded.
229 */
230 protected function loadContentFromCookie()
231 {
232 $cookieStr = $this->extractSignedContent($_COOKIE[$this->name]);
233
234 if ($cookieStr === false) {
235 return;
236 }
237
238 $values = explode(self::VALUE_SEPARATOR, $cookieStr);
239 foreach ($values as $nameValue) {
240 $equalPos = strpos($nameValue, '=');
241 $varName = substr($nameValue, 0, $equalPos);
242 $varValue = substr($nameValue, $equalPos + 1);
243
244 // no numeric value are base64 encoded so we need to decode them
245 if (!is_numeric($varValue)) {
246 $tmpValue = base64_decode($varValue);
247 $varValue = safe_unserialize($tmpValue);
248
249 // discard entire cookie
250 // note: this assumes we never serialize a boolean
251 if ($varValue === false && $tmpValue !== 'b:0;') {
252 $this->value = array();
253 unset($_COOKIE[$this->name]);
254 break;
255 }
256 }
257
258 $this->value[$varName] = $varValue;
259 }
260 }
261
262 /**
263 * Returns the string to save in the cookie from the $this->value array of values.
264 * It goes through the array and generates the cookie content string.
265 *
266 * @return string Cookie content
267 */
268 public function generateContentString()
269 {
270 $cookieStr = '';
271
272 foreach ($this->value as $name => $value) {
273 if (!is_numeric($value)) {
274 $value = base64_encode(safe_serialize($value));
275 }
276
277 $cookieStr .= "$name=$value" . self::VALUE_SEPARATOR;
278 }
279
280 if (!empty($cookieStr)) {
281 $cookieStr .= '_=';
282
283 // sign cookie
284 $signature = sha1($cookieStr . SettingsPiwik::getSalt());
285 return $cookieStr . $signature;
286 }
287
288 return '';
289 }
290
291 /**
292 * Set cookie domain
293 *
294 * @param string $domain
295 */
296 public function setDomain($domain)
297 {
298 $this->domain = $domain;
299 }
300
301 /**
302 * Set secure flag
303 *
304 * @param bool $secure
305 */
306 public function setSecure($secure)
307 {
308 $this->secure = $secure;
309 }
310
311 /**
312 * Set HTTP only
313 *
314 * @param bool $httponly
315 */
316 public function setHttpOnly($httponly)
317 {
318 $this->httponly = $httponly;
319 }
320
321 /**
322 * Registers a new name => value association in the cookie.
323 *
324 * Registering new values is optimal if the value is a numeric value.
325 * If the value is a string, it will be saved as a base64 encoded string.
326 * If the value is an array, it will be saved as a serialized and base64 encoded
327 * string which is not very good in terms of bytes usage.
328 * You should save arrays only when you are sure about their maximum data size.
329 * A cookie has to stay small and its size shouldn't increase over time!
330 *
331 * @param string $name Name of the value to save; the name will be used to retrieve this value
332 * @param string|array|number $value Value to save. If null, entry will be deleted from cookie.
333 */
334 public function set($name, $value)
335 {
336 $name = self::escapeValue($name);
337
338 // Delete value if $value === null
339 if (is_null($value)) {
340 if ($this->keyStore === false) {
341 unset($this->value[$name]);
342 return;
343 }
344 unset($this->value[$this->keyStore][$name]);
345 return;
346 }
347
348 if ($this->keyStore === false) {
349 $this->value[$name] = $value;
350 return;
351 }
352
353 $this->value[$this->keyStore][$name] = $value;
354 }
355
356 /**
357 * Returns the value defined by $name from the cookie.
358 *
359 * @param string|integer Index name of the value to return
360 * @return mixed The value if found, false if the value is not found
361 */
362 public function get($name)
363 {
364 $name = self::escapeValue($name);
365 if (false === $this->keyStore) {
366 if (isset($this->value[$name])) {
367 return self::escapeValue($this->value[$name]);
368 }
369
370 return false;
371 }
372
373 if (isset($this->value[$this->keyStore][$name])) {
374 return self::escapeValue($this->value[$this->keyStore][$name]);
375 }
376
377 return false;
378 }
379
380 /**
381 * Removes all values from the cookie.
382 */
383 public function clear()
384 {
385 $this->value = [];
386 }
387
388 /**
389 * Returns an easy to read cookie dump
390 *
391 * @return string The cookie dump
392 */
393 public function __toString()
394 {
395 $str = 'COOKIE ' . $this->name . ', rows count: ' . count($this->value) . ', cookie size = ' . strlen($this->generateContentString()) . " bytes, ";
396 $str .= 'path: ' . $this->path. ', expire: ' . $this->expire . "\n";
397 $str .= var_export($this->value, $return = true);
398
399 return $str;
400 }
401
402 /**
403 * Escape values from the cookie before sending them back to the client
404 * (when using the get() method).
405 *
406 * @param string $value Value to be escaped
407 * @return mixed The value once cleaned.
408 */
409 protected static function escapeValue($value)
410 {
411 return Common::sanitizeInputValues($value);
412 }
413
414 /**
415 * Returns true if a cookie named '$name' is in the current HTTP request,
416 * false if otherwise.
417 *
418 * @param string $name the name of the cookie
419 * @return boolean
420 */
421 public static function isCookieInRequest($name)
422 {
423 return isset($_COOKIE[$name]);
424 }
425
426 /**
427 * Find the most suitable value for a cookie SameSite attribute, given environmental restrictions which
428 * may make the most "correct" value impractical:
429 * - On Chrome, the "None" value means that the cookie will not be present on third-party sites (e.g. the site
430 * that is being tracked) when the site is loaded over HTTP. This means that important cookies which should always
431 * be present (e.g. the opt-out cookie) won't be there at all. Using "Lax" means that at least they will be there
432 * for some requests which are deemed CSRF-safe, although other requests may have broken functionality.
433 * - On Safari, the "None" value is interpreted as "Strict". In order to set a cookie which will be available
434 * in all third-party contexts, we have to omit the SameSite attribute altogether.
435 * @param string $default The desired SameSite value that we should use if it won't cause any problems.
436 * @return string SameSite attribute value that should be set on the cookie. Empty string indicates that no value
437 * should be set.
438 */
439 private static function getSameSiteValueForBrowser($default)
440 {
441 $sameSite = ucfirst(strtolower($default));
442
443 if ($sameSite == 'None') {
444 if ((!ProxyHttp::isHttps())) {
445 $sameSite = 'Lax'; // None can be only used when secure flag will be set
446 } else {
447 $userAgent = Http::getUserAgent();
448 $ddFactory = StaticContainer::get(\Piwik\DeviceDetector\DeviceDetectorFactory::class);
449 $deviceDetector = $ddFactory->makeInstance($userAgent);
450 $deviceDetector->parse();
451
452 $browserFamily = \DeviceDetector\Parser\Client\Browser::getBrowserFamily($deviceDetector->getClient('short_name'));
453 if ($browserFamily === 'Safari') {
454 $sameSite = '';
455 }
456 }
457 }
458
459 return $sameSite;
460 }
461 }
462