PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.3.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.3.0
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 5 years ago Access 5 years ago Application 5 years ago Archive 5 years ago ArchiveProcessor 5 years ago Archiver 5 years ago AssetManager 5 years ago Auth 5 years ago Category 5 years ago CliMulti 5 years ago Columns 5 years ago Composer 5 years ago Concurrency 5 years ago Config 5 years ago Container 5 years ago CronArchive 5 years ago DataAccess 5 years ago DataFiles 5 years ago DataTable 5 years ago Db 5 years ago DeviceDetector 5 years ago Email 5 years ago Exception 5 years ago Http 5 years ago Intl 5 years ago Mail 5 years ago Measurable 5 years ago Menu 5 years ago Metrics 5 years ago Notification 5 years ago Period 5 years ago Plugin 5 years ago ProfessionalServices 5 years ago Report 5 years ago ReportRenderer 5 years ago Scheduler 5 years ago Segment 5 years ago Session 5 years ago Settings 5 years ago Tracker 5 years ago Translation 5 years ago UpdateCheck 5 years ago Updater 5 years ago Updates 5 years ago Validators 5 years ago View 5 years ago ViewDataTable 5 years ago Visualization 5 years ago Widget 5 years ago .htaccess 6 years ago Access.php 5 years ago Archive.php 5 years ago ArchiveProcessor.php 5 years ago AssetManager.php 5 years ago Auth.php 5 years ago AuthResult.php 5 years ago BaseFactory.php 5 years ago Cache.php 5 years ago CacheId.php 5 years ago CliMulti.php 5 years ago Common.php 5 years ago Config.php 5 years ago Console.php 5 years ago Context.php 5 years ago Cookie.php 5 years ago CronArchive.php 5 years ago DataArray.php 5 years ago DataTable.php 5 years ago Date.php 5 years ago Db.php 5 years ago DbHelper.php 5 years ago Development.php 5 years ago ErrorHandler.php 5 years ago EventDispatcher.php 5 years ago ExceptionHandler.php 5 years ago FileIntegrity.php 5 years ago Filechecks.php 5 years ago Filesystem.php 5 years ago FrontController.php 5 years ago Http.php 5 years ago IP.php 5 years ago Log.php 5 years ago LogDeleter.php 5 years ago Mail.php 5 years ago Metrics.php 5 years ago NoAccessException.php 5 years ago Nonce.php 5 years ago Notification.php 5 years ago NumberFormatter.php 5 years ago Option.php 5 years ago Period.php 5 years ago Piwik.php 5 years ago Plugin.php 5 years ago Profiler.php 5 years ago ProxyHeaders.php 5 years ago ProxyHttp.php 5 years ago QuickForm2.php 5 years ago RankingQuery.php 5 years ago ReportRenderer.php 5 years ago Segment.php 5 years ago Sequence.php 5 years ago Session.php 5 years ago SettingsPiwik.php 5 years ago SettingsServer.php 5 years ago Singleton.php 5 years ago Site.php 5 years ago SupportedBrowser.php 5 years ago TCPDF.php 5 years ago Theme.php 5 years ago Timer.php 5 years ago Tracker.php 5 years ago Twig.php 5 years ago Unzip.php 5 years ago UpdateCheck.php 5 years ago Updater.php 5 years ago UpdaterErrorException.php 5 years ago Updates.php 5 years ago Url.php 5 years ago UrlHelper.php 5 years ago Version.php 5 years ago View.php 5 years ago bootstrap.php 5 years ago dispatch.php 5 years ago testMinimumPhpVersion.php 5 years ago
Cookie.php
472 lines
1 <?php
2 /**
3 * Matomo - 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', (int) $Expires) . ' GMT')
156 . (empty($Path) ? '' : '; path=' . $Path)
157 . (empty($Domain) ? '' : '; domain=' . rawurlencode($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
180 $this->setCookie($this->name, 'deleted', time() - 31536001, $this->path, $this->domain);
181 $this->setCookie($this->name, 'deleted', time() - 31536001, $this->path, $this->domain, TRUE, FALSE, 'None');
182
183 $this->clear();
184 }
185
186 /**
187 * Saves the cookie (set the Cookie header).
188 * You have to call this method before sending any text to the browser or you would get the
189 * "Header already sent" error.
190 * @param string $sameSite Value for SameSite cookie property
191 */
192 public function save($sameSite = null)
193 {
194 if ($sameSite) {
195 $sameSite = self::getSameSiteValueForBrowser($sameSite);
196 }
197 $cookieString = $this->generateContentString();
198 if (strlen($cookieString) > self::MAX_COOKIE_SIZE) {
199 // If the cookie was going to be too large, instead, delete existing cookie and start afresh
200 $this->delete();
201 return;
202 }
203
204 $this->setP3PHeader();
205 $this->setCookie($this->name, $cookieString, $this->expire, $this->path, $this->domain, $this->secure, $this->httponly, $sameSite);
206 }
207
208 /**
209 * Extract signed content from string: content VALUE_SEPARATOR '_=' signature
210 * Only needed for BC.
211 *
212 * @param string $content
213 * @return string|bool Content or false if unsigned
214 */
215 private function extractSignedContent($content)
216 {
217 $signature = substr($content, -40);
218
219 if (substr($content, -43, 3) === self::VALUE_SEPARATOR . '_=' &&
220 ($signature === sha1(substr($content, 0, -40) . SettingsPiwik::getSalt()))
221 ) {
222 // strip trailing: VALUE_SEPARATOR '_=' signature"
223 return substr($content, 0, -43);
224 }
225
226 return false;
227 }
228
229 /**
230 * Load the cookie content into a php array.
231 * Parses the cookie string to extract the different variables.
232 * Unserialize the array when necessary.
233 * Decode the non numeric values that were base64 encoded.
234 */
235 protected function loadContentFromCookie()
236 {
237 // we keep trying to read signed content for BC ... if it detects a correctly signed cookie then we read
238 // this value
239 $cookieStr = $this->extractSignedContent($_COOKIE[$this->name]);
240 $isSigned = !empty($cookieStr);
241
242 if ($cookieStr === false
243 && !empty($_COOKIE[$this->name])
244 && strpos($_COOKIE[$this->name], '=') !== false) {
245 // cookie was set since Matomo 4
246 $cookieStr = $_COOKIE[$this->name];
247 }
248
249 if ($cookieStr === false) {
250 return;
251 }
252
253 $values = explode(self::VALUE_SEPARATOR, $cookieStr);
254 foreach ($values as $nameValue) {
255 $equalPos = strpos($nameValue, '=');
256 $varName = substr($nameValue, 0, $equalPos);
257 $varValue = substr($nameValue, $equalPos + 1);
258
259 if (!is_numeric($varValue)) {
260 $tmpValue = base64_decode($varValue);
261 if ($isSigned) {
262 // only unserialise content if it was signed meaning the cookie was generated pre Matomo 4
263 $varValue = safe_unserialize($tmpValue);
264 } else {
265 $varValue = $tmpValue;
266 }
267
268 // discard entire cookie
269 // note: this assumes we never serialize a boolean
270 // can only happen when it was signed pre Matomo 4
271 if ($varValue === false && $tmpValue !== 'b:0;') {
272 $this->value = array();
273 unset($_COOKIE[$this->name]);
274 break;
275 }
276 }
277
278 $this->value[$varName] = $varValue;
279 }
280 }
281
282 /**
283 * Returns the string to save in the cookie from the $this->value array of values.
284 * It goes through the array and generates the cookie content string.
285 *
286 * @return string Cookie content
287 */
288 public function generateContentString()
289 {
290 $cookieStrArr = [];
291
292 foreach ($this->value as $name => $value) {
293 if (!is_numeric($value) && !is_string($value)) {
294 throw new \Exception('Only strings and numbers can be used in cookies. Value is of type ' . gettype($value));
295 } elseif (!is_numeric($value)) {
296 $value = base64_encode($value);
297 }
298 $cookieStrArr[] = "$name=$value";
299 }
300
301 return implode(self::VALUE_SEPARATOR, $cookieStrArr);
302 }
303
304 /**
305 * Set cookie domain
306 *
307 * @param string $domain
308 */
309 public function setDomain($domain)
310 {
311 $this->domain = $domain;
312 }
313
314 /**
315 * Set secure flag
316 *
317 * @param bool $secure
318 */
319 public function setSecure($secure)
320 {
321 $this->secure = $secure;
322 }
323
324 /**
325 * Set HTTP only
326 *
327 * @param bool $httponly
328 */
329 public function setHttpOnly($httponly)
330 {
331 $this->httponly = $httponly;
332 }
333
334 /**
335 * Registers a new name => value association in the cookie.
336 *
337 * Registering new values is optimal if the value is a numeric value.
338 * Only numbers and strings can be saved in the cookie.
339 * A cookie has to stay small and its size shouldn't increase over time!
340 *
341 * @param string $name Name of the value to save; the name will be used to retrieve this value
342 * @param string|number $value Value to save. If null, entry will be deleted from cookie.
343 */
344 public function set($name, $value)
345 {
346 $name = self::escapeValue($name);
347
348 // Delete value if $value === null
349 if (is_null($value)) {
350 if ($this->keyStore === false) {
351 unset($this->value[$name]);
352 return;
353 }
354 unset($this->value[$this->keyStore][$name]);
355 return;
356 }
357
358 if ($this->keyStore === false) {
359 $this->value[$name] = $value;
360 return;
361 }
362
363 $this->value[$this->keyStore][$name] = $value;
364 }
365
366 /**
367 * Returns the value defined by $name from the cookie.
368 *
369 * @param string|integer Index name of the value to return
370 * @return mixed The value if found, false if the value is not found
371 */
372 public function get($name)
373 {
374 $name = self::escapeValue($name);
375 if (false === $this->keyStore) {
376 if (isset($this->value[$name])) {
377 return self::escapeValue($this->value[$name]);
378 }
379
380 return false;
381 }
382
383 if (isset($this->value[$this->keyStore][$name])) {
384 return self::escapeValue($this->value[$this->keyStore][$name]);
385 }
386
387 return false;
388 }
389
390 /**
391 * Removes all values from the cookie.
392 */
393 public function clear()
394 {
395 $this->value = [];
396 }
397
398 /**
399 * Returns an easy to read cookie dump
400 *
401 * @return string The cookie dump
402 */
403 public function __toString()
404 {
405 $str = 'COOKIE ' . $this->name . ', rows count: ' . count($this->value) . ', cookie size = ' . strlen($this->generateContentString()) . " bytes, ";
406 $str .= 'path: ' . $this->path. ', expire: ' . $this->expire . "\n";
407 $str .= var_export($this->value, $return = true);
408
409 return $str;
410 }
411
412 /**
413 * Escape values from the cookie before sending them back to the client
414 * (when using the get() method).
415 *
416 * @param string $value Value to be escaped
417 * @return mixed The value once cleaned.
418 */
419 protected static function escapeValue($value)
420 {
421 return Common::sanitizeInputValues($value);
422 }
423
424 /**
425 * Returns true if a cookie named '$name' is in the current HTTP request,
426 * false if otherwise.
427 *
428 * @param string $name the name of the cookie
429 * @return boolean
430 */
431 public static function isCookieInRequest($name)
432 {
433 return isset($_COOKIE[$name]);
434 }
435
436 /**
437 * Find the most suitable value for a cookie SameSite attribute, given environmental restrictions which
438 * may make the most "correct" value impractical:
439 * - On Chrome, the "None" value means that the cookie will not be present on third-party sites (e.g. the site
440 * that is being tracked) when the site is loaded over HTTP. This means that important cookies which should always
441 * be present (e.g. the opt-out cookie) won't be there at all. Using "Lax" means that at least they will be there
442 * for some requests which are deemed CSRF-safe, although other requests may have broken functionality.
443 * - On Safari, the "None" value is interpreted as "Strict". In order to set a cookie which will be available
444 * in all third-party contexts, we have to omit the SameSite attribute altogether.
445 * @param string $default The desired SameSite value that we should use if it won't cause any problems.
446 * @return string SameSite attribute value that should be set on the cookie. Empty string indicates that no value
447 * should be set.
448 */
449 private static function getSameSiteValueForBrowser($default)
450 {
451 $sameSite = ucfirst(strtolower($default));
452
453 if ($sameSite === 'None') {
454 if ((!ProxyHttp::isHttps())) {
455 $sameSite = 'Lax'; // None can be only used when secure flag will be set
456 } else {
457 $userAgent = Http::getUserAgent();
458 $ddFactory = StaticContainer::get(\Piwik\DeviceDetector\DeviceDetectorFactory::class);
459 $deviceDetector = $ddFactory->makeInstance($userAgent);
460 $deviceDetector->parse();
461
462 $browserFamily = \DeviceDetector\Parser\Client\Browser::getBrowserFamily($deviceDetector->getClient('short_name'));
463 if ($browserFamily === 'Safari') {
464 $sameSite = '';
465 }
466 }
467 }
468
469 return $sameSite;
470 }
471 }
472