dflydev /
dflydev-fig-cookies
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | declare(strict_types=1); |
||
| 4 | |||
| 5 | namespace Dflydev\FigCookies; |
||
| 6 | |||
| 7 | use DateTime; |
||
| 8 | use DateTimeInterface; |
||
| 9 | use Dflydev\FigCookies\Modifier\SameSite; |
||
| 10 | use function array_shift; |
||
| 11 | use function count; |
||
| 12 | use function explode; |
||
| 13 | use function gmdate; |
||
| 14 | use function implode; |
||
| 15 | use function is_int; |
||
| 16 | use function is_numeric; |
||
| 17 | use function is_string; |
||
| 18 | use function sprintf; |
||
| 19 | 31 | use function strtolower; |
|
| 20 | use function strtotime; |
||
| 21 | 31 | use function urlencode; |
|
| 22 | 31 | ||
| 23 | 31 | class SetCookie |
|
| 24 | { |
||
| 25 | 19 | /** @var string */ |
|
| 26 | private $name; |
||
| 27 | 19 | /** @var string|null */ |
|
| 28 | private $value; |
||
| 29 | /** @var int */ |
||
| 30 | 3 | private $expires = 0; |
|
| 31 | /** @var int */ |
||
| 32 | 3 | private $maxAge = 0; |
|
| 33 | /** @var string|null */ |
||
| 34 | private $path; |
||
| 35 | 2 | /** @var string|null */ |
|
| 36 | private $domain; |
||
| 37 | 2 | /** @var bool */ |
|
| 38 | private $secure = false; |
||
| 39 | /** @var bool */ |
||
| 40 | private $httpOnly = false; |
||
| 41 | /** @var SameSite|null */ |
||
| 42 | private $sameSite; |
||
| 43 | |||
| 44 | private function __construct(string $name, ?string $value = null) |
||
| 45 | { |
||
| 46 | $this->name = $name; |
||
| 47 | $this->value = $value; |
||
| 48 | } |
||
| 49 | |||
| 50 | public function getName() : string |
||
| 51 | { |
||
| 52 | return $this->name; |
||
| 53 | } |
||
| 54 | |||
| 55 | public function getValue() : ?string |
||
| 56 | { |
||
| 57 | return $this->value; |
||
| 58 | } |
||
| 59 | |||
| 60 | public function getExpires() : int |
||
| 61 | { |
||
| 62 | return $this->expires; |
||
| 63 | } |
||
| 64 | |||
| 65 | 29 | public function getMaxAge() : int |
|
| 66 | { |
||
| 67 | 29 | return $this->maxAge; |
|
| 68 | } |
||
| 69 | 29 | ||
| 70 | public function getPath() : ?string |
||
| 71 | 29 | { |
|
| 72 | return $this->path; |
||
| 73 | } |
||
| 74 | 13 | ||
| 75 | public function getDomain() : ?string |
||
| 76 | 13 | { |
|
| 77 | return $this->domain; |
||
| 78 | } |
||
| 79 | |||
| 80 | 13 | public function getSecure() : bool |
|
| 81 | 2 | { |
|
| 82 | return $this->secure; |
||
| 83 | } |
||
| 84 | 11 | ||
| 85 | public function getHttpOnly() : bool |
||
| 86 | { |
||
| 87 | return $this->httpOnly; |
||
| 88 | 11 | } |
|
| 89 | |||
| 90 | public function getSameSite() : ?SameSite |
||
| 91 | 13 | { |
|
| 92 | return $this->sameSite; |
||
| 93 | 13 | } |
|
| 94 | |||
| 95 | 13 | public function withValue(?string $value = null) : self |
|
| 96 | { |
||
| 97 | 13 | $clone = clone($this); |
|
| 98 | |||
| 99 | 13 | $clone->value = $value; |
|
| 100 | |||
| 101 | return $clone; |
||
| 102 | 1 | } |
|
| 103 | |||
| 104 | 1 | /** @param int|\DateTimeInterface|string|null $expires */ |
|
| 105 | private function resolveExpires($expires = null) : int |
||
| 106 | { |
||
| 107 | 1 | if ($expires === null) { |
|
| 108 | return 0; |
||
| 109 | 1 | } |
|
| 110 | |||
| 111 | if ($expires instanceof DateTimeInterface) { |
||
| 112 | 4 | return $expires->getTimestamp(); |
|
| 113 | } |
||
| 114 | 4 | ||
| 115 | if (is_numeric($expires)) { |
||
| 116 | 4 | return (int) $expires; |
|
| 117 | } |
||
| 118 | 4 | ||
| 119 | $time = strtotime($expires); |
||
| 120 | |||
| 121 | 12 | if (! is_int($time)) { |
|
| 122 | throw new \InvalidArgumentException(sprintf('Invalid expires "%s" provided', $expires)); |
||
| 123 | 12 | } |
|
| 124 | |||
| 125 | 12 | return $time; |
|
| 126 | } |
||
| 127 | 12 | ||
| 128 | /** @param int|string|\DateTimeInterface|null $expires */ |
||
| 129 | public function withExpires($expires = null) : self |
||
| 130 | 7 | { |
|
| 131 | $expires = $this->resolveExpires($expires); |
||
| 132 | 7 | ||
| 133 | $clone = clone($this); |
||
| 134 | 7 | ||
| 135 | $clone->expires = $expires; |
||
| 136 | 7 | ||
| 137 | return $clone; |
||
| 138 | } |
||
| 139 | 10 | ||
| 140 | public function rememberForever() : self |
||
| 141 | 10 | { |
|
| 142 | return $this->withExpires(new DateTime('+5 years')); |
||
| 143 | 10 | } |
|
| 144 | |||
| 145 | 10 | public function expire() : self |
|
| 146 | { |
||
| 147 | return $this->withExpires(new DateTime('-5 years')); |
||
| 148 | 12 | } |
|
| 149 | |||
| 150 | 12 | public function withMaxAge(?int $maxAge = null) : self |
|
| 151 | { |
||
| 152 | 12 | $clone = clone($this); |
|
| 153 | |||
| 154 | 12 | $clone->maxAge = (int) $maxAge; |
|
| 155 | |||
| 156 | return $clone; |
||
| 157 | 17 | } |
|
| 158 | |||
| 159 | public function withPath(?string $path = null) : self |
||
| 160 | 17 | { |
|
| 161 | 17 | $clone = clone($this); |
|
| 162 | |||
| 163 | 17 | $clone->path = $path; |
|
| 164 | 17 | ||
| 165 | 17 | return $clone; |
|
| 166 | 17 | } |
|
| 167 | 17 | ||
| 168 | 17 | public function withDomain(?string $domain = null) : self |
|
| 169 | { |
||
| 170 | 17 | $clone = clone($this); |
|
| 171 | |||
| 172 | $clone->domain = $domain; |
||
| 173 | 8 | ||
| 174 | return $clone; |
||
| 175 | 8 | } |
|
| 176 | |||
| 177 | public function withSecure(bool $secure = true) : self |
||
| 178 | 1 | { |
|
| 179 | $clone = clone($this); |
||
| 180 | 1 | ||
| 181 | $clone->secure = $secure; |
||
| 182 | |||
| 183 | 1 | return $clone; |
|
| 184 | } |
||
| 185 | 1 | ||
| 186 | public function withHttpOnly(bool $httpOnly = true) : self |
||
| 187 | { |
||
| 188 | 29 | $clone = clone($this); |
|
| 189 | |||
| 190 | 29 | $clone->httpOnly = $httpOnly; |
|
| 191 | |||
| 192 | 29 | return $clone; |
|
| 193 | } |
||
| 194 | |||
| 195 | 29 | public function withSameSite(SameSite $sameSite) : self |
|
| 196 | { |
||
| 197 | 29 | $clone = clone($this); |
|
| 198 | 29 | ||
| 199 | 29 | $clone->sameSite = $sameSite; |
|
| 200 | |||
| 201 | 29 | return $clone; |
|
| 202 | 12 | } |
|
| 203 | |||
| 204 | 12 | public function withoutSameSite() : self |
|
| 205 | 12 | { |
|
| 206 | $clone = clone($this); |
||
| 207 | 12 | ||
| 208 | $clone->sameSite = null; |
||
| 209 | |||
| 210 | 12 | return $clone; |
|
| 211 | 11 | } |
|
| 212 | 11 | ||
| 213 | 12 | public function __toString() : string |
|
| 214 | 4 | { |
|
| 215 | 4 | $cookieStringParts = [ |
|
| 216 | 12 | urlencode($this->name) . '=' . urlencode((string) $this->value), |
|
| 217 | 7 | ]; |
|
| 218 | 7 | ||
| 219 | 12 | $cookieStringParts = $this->appendFormattedDomainPartIfSet($cookieStringParts); |
|
| 220 | 12 | $cookieStringParts = $this->appendFormattedPathPartIfSet($cookieStringParts); |
|
| 221 | 12 | $cookieStringParts = $this->appendFormattedExpiresPartIfSet($cookieStringParts); |
|
| 222 | 12 | $cookieStringParts = $this->appendFormattedMaxAgePartIfSet($cookieStringParts); |
|
| 223 | 10 | $cookieStringParts = $this->appendFormattedSecurePartIfSet($cookieStringParts); |
|
| 224 | 10 | $cookieStringParts = $this->appendFormattedHttpOnlyPartIfSet($cookieStringParts); |
|
| 225 | 12 | $cookieStringParts = $this->appendFormattedSameSitePartIfSet($cookieStringParts); |
|
| 226 | 12 | ||
| 227 | 12 | return implode('; ', $cookieStringParts); |
|
| 228 | } |
||
| 229 | |||
| 230 | 12 | public static function create(string $name, ?string $value = null) : self |
|
| 231 | { |
||
| 232 | 29 | return new static($name, $value); |
|
| 233 | } |
||
| 234 | 17 | ||
| 235 | public static function createRememberedForever(string $name, ?string $value = null) : self |
||
| 236 | 17 | { |
|
| 237 | 7 | return static::create($name, $value)->rememberForever(); |
|
| 238 | 7 | } |
|
| 239 | |||
| 240 | 17 | public static function createExpired(string $name) : self |
|
| 241 | { |
||
| 242 | return static::create($name)->expire(); |
||
| 243 | 17 | } |
|
| 244 | |||
| 245 | 17 | public static function fromSetCookieString(string $string) : self |
|
| 246 | 8 | { |
|
| 247 | 8 | $rawAttributes = StringUtil::splitOnAttributeDelimiter($string); |
|
| 248 | |||
| 249 | 17 | $rawAttribute = array_shift($rawAttributes); |
|
| 250 | |||
| 251 | if (! is_string($rawAttribute)) { |
||
| 252 | 17 | throw new \InvalidArgumentException(sprintf( |
|
| 253 | 'The provided cookie string "%s" must have at least one attribute', |
||
| 254 | 17 | $string |
|
| 255 | 7 | )); |
|
| 256 | 7 | } |
|
| 257 | |||
| 258 | 17 | list ($cookieName, $cookieValue) = StringUtil::splitCookiePair($rawAttribute); |
|
| 259 | |||
| 260 | /** @var SetCookie $setCookie */ |
||
| 261 | 17 | $setCookie = new static($cookieName); |
|
| 262 | |||
| 263 | 17 | if ($cookieValue !== null) { |
|
| 264 | 4 | $setCookie = $setCookie->withValue($cookieValue); |
|
| 265 | 4 | } |
|
| 266 | |||
| 267 | 17 | while ($rawAttribute = array_shift($rawAttributes)) { |
|
| 268 | $rawAttributePair = explode('=', $rawAttribute, 2); |
||
| 269 | |||
| 270 | 17 | $attributeKey = $rawAttributePair[0]; |
|
| 271 | $attributeValue = count($rawAttributePair) > 1 ? $rawAttributePair[1] : null; |
||
| 272 | 17 | ||
| 273 | 6 | $attributeKey = strtolower($attributeKey); |
|
| 274 | 6 | ||
| 275 | switch ($attributeKey) { |
||
| 276 | 17 | case 'expires': |
|
| 277 | $setCookie = $setCookie->withExpires($attributeValue); |
||
| 278 | break; |
||
| 279 | 17 | case 'max-age': |
|
| 280 | $setCookie = $setCookie->withMaxAge((int) $attributeValue); |
||
| 281 | 17 | break; |
|
| 282 | 8 | case 'domain': |
|
| 283 | 8 | $setCookie = $setCookie->withDomain($attributeValue); |
|
| 284 | break; |
||
| 285 | 17 | case 'path': |
|
| 286 | $setCookie = $setCookie->withPath($attributeValue); |
||
| 287 | break; |
||
| 288 | case 'secure': |
||
| 289 | $setCookie = $setCookie->withSecure(true); |
||
| 290 | break; |
||
| 291 | case 'httponly': |
||
| 292 | $setCookie = $setCookie->withHttpOnly(true); |
||
| 293 | break; |
||
| 294 | case 'samesite': |
||
| 295 | $setCookie = $setCookie->withSameSite(SameSite::fromString((string) $attributeValue)); |
||
| 296 | break; |
||
| 297 | } |
||
| 298 | } |
||
| 299 | |||
| 300 | return $setCookie; |
||
| 301 | } |
||
| 302 | |||
| 303 | /** |
||
| 304 | * @param string[] $cookieStringParts |
||
| 305 | * |
||
| 306 | * @return string[] |
||
| 307 | */ |
||
| 308 | private function appendFormattedDomainPartIfSet(array $cookieStringParts) : array |
||
| 309 | { |
||
| 310 | if ($this->domain) { |
||
|
0 ignored issues
–
show
|
|||
| 311 | $cookieStringParts[] = sprintf('Domain=%s', $this->domain); |
||
| 312 | } |
||
| 313 | |||
| 314 | return $cookieStringParts; |
||
| 315 | } |
||
| 316 | |||
| 317 | /** |
||
| 318 | * @param string[] $cookieStringParts |
||
| 319 | * |
||
| 320 | * @return string[] |
||
| 321 | */ |
||
| 322 | private function appendFormattedPathPartIfSet(array $cookieStringParts) : array |
||
| 323 | { |
||
| 324 | if ($this->path) { |
||
|
0 ignored issues
–
show
The expression
$this->path of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
Loading history...
|
|||
| 325 | $cookieStringParts[] = sprintf('Path=%s', $this->path); |
||
| 326 | } |
||
| 327 | |||
| 328 | return $cookieStringParts; |
||
| 329 | } |
||
| 330 | |||
| 331 | /** |
||
| 332 | * @param string[] $cookieStringParts |
||
| 333 | * |
||
| 334 | * @return string[] |
||
| 335 | */ |
||
| 336 | private function appendFormattedExpiresPartIfSet(array $cookieStringParts) : array |
||
| 337 | { |
||
| 338 | if ($this->expires) { |
||
| 339 | $cookieStringParts[] = sprintf('Expires=%s', gmdate('D, d M Y H:i:s T', $this->expires)); |
||
| 340 | } |
||
| 341 | |||
| 342 | return $cookieStringParts; |
||
| 343 | } |
||
| 344 | |||
| 345 | /** |
||
| 346 | * @param string[] $cookieStringParts |
||
| 347 | * |
||
| 348 | * @return string[] |
||
| 349 | */ |
||
| 350 | private function appendFormattedMaxAgePartIfSet(array $cookieStringParts) : array |
||
| 351 | { |
||
| 352 | if ($this->maxAge) { |
||
| 353 | $cookieStringParts[] = sprintf('Max-Age=%s', $this->maxAge); |
||
| 354 | } |
||
| 355 | |||
| 356 | return $cookieStringParts; |
||
| 357 | } |
||
| 358 | |||
| 359 | /** |
||
| 360 | * @param string[] $cookieStringParts |
||
| 361 | * |
||
| 362 | * @return string[] |
||
| 363 | */ |
||
| 364 | private function appendFormattedSecurePartIfSet(array $cookieStringParts) : array |
||
| 365 | { |
||
| 366 | if ($this->secure) { |
||
| 367 | $cookieStringParts[] = 'Secure'; |
||
| 368 | } |
||
| 369 | |||
| 370 | return $cookieStringParts; |
||
| 371 | } |
||
| 372 | |||
| 373 | /** |
||
| 374 | * @param string[] $cookieStringParts |
||
| 375 | * |
||
| 376 | * @return string[] |
||
| 377 | */ |
||
| 378 | private function appendFormattedHttpOnlyPartIfSet(array $cookieStringParts) : array |
||
| 379 | { |
||
| 380 | if ($this->httpOnly) { |
||
| 381 | $cookieStringParts[] = 'HttpOnly'; |
||
| 382 | } |
||
| 383 | |||
| 384 | return $cookieStringParts; |
||
| 385 | } |
||
| 386 | |||
| 387 | /** |
||
| 388 | * @param string[] $cookieStringParts |
||
| 389 | * |
||
| 390 | * @return string[] |
||
| 391 | */ |
||
| 392 | private function appendFormattedSameSitePartIfSet(array $cookieStringParts) : array |
||
| 393 | { |
||
| 394 | if ($this->sameSite === null) { |
||
| 395 | return $cookieStringParts; |
||
| 396 | } |
||
| 397 | |||
| 398 | $cookieStringParts[] = $this->sameSite->asString(); |
||
| 399 | |||
| 400 | return $cookieStringParts; |
||
| 401 | } |
||
| 402 | } |
||
| 403 |
In PHP, under loose comparison (like
==, or!=, orswitchconditions), values of different types might be equal.For
stringvalues, the empty string''is a special case, in particular the following results might be unexpected: