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