parent
c0695eca2f
commit
49055e4671
@ -0,0 +1,174 @@ |
|||||||
|
<?php |
||||||
|
// SPDX-License-Identifier: EUPL-1.2 |
||||||
|
// Authors: see README.md |
||||||
|
|
||||||
|
namespace SeaCMS\Api; |
||||||
|
|
||||||
|
use DateTimeInterface; |
||||||
|
use Exception; |
||||||
|
use Throwable; |
||||||
|
|
||||||
|
/** |
||||||
|
* Exception for bad http method |
||||||
|
*/ |
||||||
|
class Cookies |
||||||
|
{ |
||||||
|
/** |
||||||
|
* list of reserved chars in cookies name |
||||||
|
* @var array |
||||||
|
*/ |
||||||
|
private const RESERVED_CHARS_FROM = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"]; |
||||||
|
/** |
||||||
|
* list of replacements in cookies name |
||||||
|
* @var array |
||||||
|
*/ |
||||||
|
private const RESERVED_CHARS_TO = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C']; |
||||||
|
|
||||||
|
/** |
||||||
|
* structured data for cookies |
||||||
|
* first level 'domain' |
||||||
|
* second level 'path' |
||||||
|
* third level 'name' |
||||||
|
* @var array |
||||||
|
*/ |
||||||
|
protected $data; |
||||||
|
|
||||||
|
/** |
||||||
|
* cookies already sent |
||||||
|
* @var bool |
||||||
|
*/ |
||||||
|
protected $sent; |
||||||
|
|
||||||
|
public function __construct() { |
||||||
|
$this->data = []; |
||||||
|
$this->sent = false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* add a cookie, if existing, overwrite it |
||||||
|
* @param string $name The name of the cookie |
||||||
|
* @param string $value The value of the cookie |
||||||
|
* @param int|string|DateTimeInterface $expire The time the cookie expires |
||||||
|
* @param string $path The path on the server in which the cookie will be available on |
||||||
|
* @param string $domain The domain that the cookie is available to |
||||||
|
* @param bool $secure Whether the client should send back the cookie only over HTTPS or null to auto-enable this when the request is already using HTTPS |
||||||
|
* @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol |
||||||
|
* @param string $sameSite Whether the cookie will be available for cross-site requests |
||||||
|
* @throws Exception |
||||||
|
*/ |
||||||
|
public function addCookie( |
||||||
|
string $name, |
||||||
|
string $value = '', |
||||||
|
$expire = 0, |
||||||
|
string $path = '/', |
||||||
|
string $domain = '', |
||||||
|
bool $secure = true, |
||||||
|
bool $httpOnly = true, |
||||||
|
string $sameSite = 'Lax') |
||||||
|
{ |
||||||
|
if ($this->sent){ |
||||||
|
throw new Exception('Cookies already sent ! Not possible to change cookies'); |
||||||
|
} |
||||||
|
if (empty($name)){ |
||||||
|
throw new Exception('\'$name\' should not be empty !', 1); |
||||||
|
} |
||||||
|
if (!in_array($sameSite,['None','Lax','Strict'])){ |
||||||
|
throw new Exception('\'$sameSite\' should be \'None\',\'Lax\' or \'Strict\' !', 1); |
||||||
|
} |
||||||
|
|
||||||
|
// convert expiration time to a Unix timestamp |
||||||
|
if ($expire instanceof DateTimeInterface) { |
||||||
|
$expire = $expire->format('U'); |
||||||
|
} elseif (is_string($expire)) { |
||||||
|
$expire = strtotime($expire); |
||||||
|
|
||||||
|
if (false === $expire) { |
||||||
|
throw new Exception('The cookie expiration time is not valid.'); |
||||||
|
} |
||||||
|
} elseif (!is_integer($expire)) { |
||||||
|
$expire = 0; |
||||||
|
} |
||||||
|
|
||||||
|
$expire = (0 < $expire) ? (int) $expire : 0; |
||||||
|
|
||||||
|
if (!array_key_exists($domain,$this->data)){ |
||||||
|
$this->data[$domain] = []; |
||||||
|
} |
||||||
|
if (!array_key_exists($path,$this->data[$domain])){ |
||||||
|
$this->data[$domain][$path] = []; |
||||||
|
} |
||||||
|
$this->data[$domain][$path][$name] = [ |
||||||
|
'value' => $value, |
||||||
|
'expire' => $expire, |
||||||
|
'secure' => $secure, |
||||||
|
'httpOnly' => $httpOnly, |
||||||
|
'sameSite' => $sameSite |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* delete a cookie, if existing |
||||||
|
* @param string $name The name of the cookie |
||||||
|
* @param string $path The path on the server in which the cookie will be available on |
||||||
|
* @param string $domain The domain that the cookie is available to |
||||||
|
* @throws Exception |
||||||
|
*/ |
||||||
|
public function deleteCookie( |
||||||
|
string $name, |
||||||
|
string $path = '/', |
||||||
|
string $domain = '') |
||||||
|
{ |
||||||
|
if ($this->sent){ |
||||||
|
throw new Exception('Cookies already sent ! Not possible to change cookies'); |
||||||
|
} |
||||||
|
if (empty($name)){ |
||||||
|
throw new Exception('\'$name\' should not be empty !', 1); |
||||||
|
} |
||||||
|
if (array_key_exists($domain,$this->data)){ |
||||||
|
if (array_key_exists($path,$this->data[$domain])){ |
||||||
|
if (array_key_exists($name,$this->data[$domain][$path])){ |
||||||
|
unset($this->data[$domain][$path][$name]); |
||||||
|
} |
||||||
|
if (empty($this->data[$domain][$path])){ |
||||||
|
unset($this->data[$domain][$path]); |
||||||
|
} |
||||||
|
} |
||||||
|
if (empty($this->data[$domain])){ |
||||||
|
unset($this->data[$domain]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* send cookies if not already sent |
||||||
|
*/ |
||||||
|
public function sendCookiesOnce() |
||||||
|
{ |
||||||
|
if (!$this->sent){ |
||||||
|
$this->sent = true; |
||||||
|
foreach($this->data as $domain => $domainCookies){ |
||||||
|
foreach ($domainCookies as $path => $pathCookies) { |
||||||
|
foreach ($pathCookies as $name => $values) { |
||||||
|
try { |
||||||
|
setcookie( |
||||||
|
str_replace(self::RESERVED_CHARS_FROM, self::RESERVED_CHARS_TO, $name), |
||||||
|
$values['value'], |
||||||
|
[ |
||||||
|
'expires' => $values['expire'], |
||||||
|
'path' => $path, |
||||||
|
'domain' => $domain, |
||||||
|
'secure' => $values['secure'], |
||||||
|
'httponly' => $values['httpOnly'], |
||||||
|
'samesite' => $values['sameSite'] |
||||||
|
] |
||||||
|
); |
||||||
|
} catch (Throwable $th) { |
||||||
|
echo json_encode(['error'=>$th->__toString()]); |
||||||
|
exit(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,61 @@ |
|||||||
|
<?php |
||||||
|
// SPDX-License-Identifier: EUPL-1.2 |
||||||
|
// Authors: see README.md |
||||||
|
|
||||||
|
namespace SeaCMS\Api; |
||||||
|
|
||||||
|
use Exception; |
||||||
|
use SeaCMS\Api\JsonResponse; |
||||||
|
use Throwable; |
||||||
|
|
||||||
|
/** |
||||||
|
* Exception for bad http method |
||||||
|
*/ |
||||||
|
class SpecialOutputException extends Exception |
||||||
|
{ |
||||||
|
/** |
||||||
|
* jsonResponse of the Exception |
||||||
|
* @var JsonResponse |
||||||
|
*/ |
||||||
|
protected $jsonResponse; |
||||||
|
|
||||||
|
// Redefine the exception to be able to define $jsonResponse |
||||||
|
public function __construct($message = "", $code = 0, ?Throwable $previous = null, ?JsonResponse $jsonResponse = null) { |
||||||
|
if ($message instanceof JsonResponse){ |
||||||
|
$this->jsonResponse = $message; |
||||||
|
$message = "Forced output with JsonResponse"; |
||||||
|
if (!is_integer($code)){ |
||||||
|
$int = 0; |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (!is_string($message)){ |
||||||
|
$message = ""; |
||||||
|
} |
||||||
|
if ($code instanceof JsonResponse){ |
||||||
|
$this->jsonResponse = $code; |
||||||
|
$code = 0; |
||||||
|
} else { |
||||||
|
if (!is_integer($code)){ |
||||||
|
$int = 0; |
||||||
|
} |
||||||
|
if (is_null($jsonResponse)){ |
||||||
|
throw new Exception("It is not possible to instanciate a SpecialOutputException because \$jsonResponse is null !"); |
||||||
|
} else { |
||||||
|
$this->jsonResponse = $jsonResponse; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// make sure everything is assigned properly |
||||||
|
parent::__construct($message, $code, $previous); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get JsonResponse |
||||||
|
* @return JsonResponse |
||||||
|
*/ |
||||||
|
public function getJsonResponse(): JsonResponse |
||||||
|
{ |
||||||
|
return $this->jsonResponse; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue