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