3.4.0
This commit is contained in:
@@ -35,18 +35,19 @@ class Cookie
|
||||
* @param string $key The name of the cookie
|
||||
* @param string $value The cookie content
|
||||
* @param array $options Array of options:
|
||||
* lifetime, path, domain, secure, httpOnly
|
||||
* lifetime, path, domain, secure, httpOnly, sameSite
|
||||
* @return bool true: cookie was created,
|
||||
* false: cookie creation failed
|
||||
*/
|
||||
public static function set(string $key, string $value, array $options = []): bool
|
||||
{
|
||||
// extract options
|
||||
$lifetime = $options['lifetime'] ?? 0;
|
||||
$expires = static::lifetime($options['lifetime'] ?? 0);
|
||||
$path = $options['path'] ?? '/';
|
||||
$domain = $options['domain'] ?? null;
|
||||
$secure = $options['secure'] ?? false;
|
||||
$httpOnly = $options['httpOnly'] ?? true;
|
||||
$httponly = $options['httpOnly'] ?? true;
|
||||
$samesite = $options['sameSite'] ?? 'Lax';
|
||||
|
||||
// add an HMAC signature of the value
|
||||
$value = static::hmac($value) . '+' . $value;
|
||||
@@ -55,7 +56,14 @@ class Cookie
|
||||
$_COOKIE[$key] = $value;
|
||||
|
||||
// store the cookie
|
||||
return setcookie($key, $value, static::lifetime($lifetime), $path, $domain, $secure, $httpOnly);
|
||||
// the array syntax is only supported by PHP 7.3+
|
||||
// TODO: Always use the first alternative when support for PHP 7.2 is dropped
|
||||
if (version_compare(PHP_VERSION, '7.3.0', '>=') === true) {
|
||||
$options = compact('expires', 'path', 'domain', 'secure', 'httponly', 'samesite');
|
||||
return setcookie($key, $value, $options);
|
||||
} else {
|
||||
return setcookie($key, $value, $expires, $path, $domain, $secure, $httponly);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -300,7 +300,7 @@ class Header
|
||||
$options = array_merge($defaults, $params);
|
||||
|
||||
header('Pragma: public');
|
||||
header('Expires: 0');
|
||||
header('Cache-Control: no-cache, no-store, must-revalidate');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $options['modified']) . ' GMT');
|
||||
header('Content-Disposition: attachment; filename="' . $options['name'] . '"');
|
||||
header('Content-Transfer-Encoding: binary');
|
||||
|
@@ -3,6 +3,8 @@
|
||||
namespace Kirby\Http;
|
||||
|
||||
use Exception;
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Toolkit\F;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
@@ -18,6 +20,9 @@ use Kirby\Toolkit\Str;
|
||||
*/
|
||||
class Remote
|
||||
{
|
||||
const CA_INTERNAL = 1;
|
||||
const CA_SYSTEM = 2;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
@@ -25,6 +30,7 @@ class Remote
|
||||
'agent' => null,
|
||||
'basicAuth' => null,
|
||||
'body' => true,
|
||||
'ca' => self::CA_INTERNAL,
|
||||
'data' => [],
|
||||
'encoding' => 'utf-8',
|
||||
'file' => null,
|
||||
@@ -96,8 +102,17 @@ class Remote
|
||||
*/
|
||||
public function __construct(string $url, array $options = [])
|
||||
{
|
||||
$defaults = static::$defaults;
|
||||
|
||||
// update the defaults with App config if set;
|
||||
// request the App instance lazily
|
||||
$app = App::instance(null, true);
|
||||
if ($app !== null) {
|
||||
$defaults = array_merge($defaults, $app->option('remote', []));
|
||||
}
|
||||
|
||||
// set all options
|
||||
$this->options = array_merge(static::$defaults, $options);
|
||||
$this->options = array_merge($defaults, $options);
|
||||
|
||||
// add the url
|
||||
$this->options['url'] = $url;
|
||||
@@ -138,7 +153,6 @@ class Remote
|
||||
*/
|
||||
public function fetch()
|
||||
{
|
||||
|
||||
// curl options
|
||||
$this->curlopt = [
|
||||
CURLOPT_URL => $this->options['url'],
|
||||
@@ -149,7 +163,6 @@ class Remote
|
||||
CURLOPT_RETURNTRANSFER => $this->options['body'],
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 10,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_HEADER => false,
|
||||
CURLOPT_HEADERFUNCTION => function ($curl, $header) {
|
||||
$parts = Str::split($header, ':');
|
||||
@@ -163,6 +176,24 @@ class Remote
|
||||
}
|
||||
];
|
||||
|
||||
// determine the TLS CA to use
|
||||
if (is_file($this->options['ca']) === true) {
|
||||
$this->curlopt[CURLOPT_SSL_VERIFYPEER] = true;
|
||||
$this->curlopt[CURLOPT_CAINFO] = $this->options['ca'];
|
||||
} elseif (is_dir($this->options['ca']) === true) {
|
||||
$this->curlopt[CURLOPT_SSL_VERIFYPEER] = true;
|
||||
$this->curlopt[CURLOPT_CAPATH] = $this->options['ca'];
|
||||
} elseif ($this->options['ca'] === self::CA_INTERNAL) {
|
||||
$this->curlopt[CURLOPT_SSL_VERIFYPEER] = true;
|
||||
$this->curlopt[CURLOPT_CAINFO] = dirname(__DIR__, 2) . '/cacert.pem';
|
||||
} elseif ($this->options['ca'] === self::CA_SYSTEM) {
|
||||
$this->curlopt[CURLOPT_SSL_VERIFYPEER] = true;
|
||||
} elseif ($this->options['ca'] === false) {
|
||||
$this->curlopt[CURLOPT_SSL_VERIFYPEER] = false;
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid "ca" option for the Remote class');
|
||||
}
|
||||
|
||||
// add the progress
|
||||
if (is_callable($this->options['progress']) === true) {
|
||||
$this->curlopt[CURLOPT_NOPROGRESS] = false;
|
||||
|
@@ -107,7 +107,7 @@ class Request
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->options = $options;
|
||||
$this->method = $options['method'] ?? $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
||||
$this->method = $this->detectRequestMethod($options['method'] ?? null);
|
||||
|
||||
if (isset($options['body']) === true) {
|
||||
$this->body = new Body($options['body']);
|
||||
@@ -208,6 +208,39 @@ class Request
|
||||
return array_merge($this->body()->toArray(), $this->query()->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the request method from various
|
||||
* options: given method, query string, server vars
|
||||
*
|
||||
* @param string $method
|
||||
* @return string
|
||||
*/
|
||||
public function detectRequestMethod(string $method = null): string
|
||||
{
|
||||
// all possible methods
|
||||
$methods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH'];
|
||||
|
||||
// the request method can be overwritten with a header
|
||||
$methodOverride = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ?? null);
|
||||
|
||||
if ($method === null && in_array($methodOverride, $methods) === true) {
|
||||
$method = $methodOverride;
|
||||
}
|
||||
|
||||
// final chain of options to detect the method
|
||||
$method = $method ?? $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
||||
|
||||
// uppercase the shit out of it
|
||||
$method = strtoupper($method);
|
||||
|
||||
// sanitize the method
|
||||
if (in_array($method, $methods) === false) {
|
||||
$method = 'GET';
|
||||
}
|
||||
|
||||
return $method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the domain
|
||||
*
|
||||
|
@@ -151,9 +151,10 @@ class Response
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $filename
|
||||
* @param array $props Custom overrides for response props (e.g. headers)
|
||||
* @return self
|
||||
*/
|
||||
public static function download(string $file, string $filename = null)
|
||||
public static function download(string $file, string $filename = null, array $props = [])
|
||||
{
|
||||
if (file_exists($file) === false) {
|
||||
throw new Exception('The file could not be found');
|
||||
@@ -164,19 +165,21 @@ class Response
|
||||
$body = file_get_contents($file);
|
||||
$size = strlen($body);
|
||||
|
||||
return new static([
|
||||
$props = array_replace_recursive([
|
||||
'body' => $body,
|
||||
'type' => 'application/force-download',
|
||||
'headers' => [
|
||||
'Pragma' => 'public',
|
||||
'Expires' => '0',
|
||||
'Cache-Control' => 'no-cache, no-store, must-revalidate',
|
||||
'Last-Modified' => gmdate('D, d M Y H:i:s', $modified) . ' GMT',
|
||||
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
|
||||
'Content-Transfer-Encoding' => 'binary',
|
||||
'Content-Length' => $size,
|
||||
'Connection' => 'close'
|
||||
]
|
||||
]);
|
||||
], $props);
|
||||
|
||||
return new static($props);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,11 +187,17 @@ class Response
|
||||
* sends the file content to the browser
|
||||
*
|
||||
* @param string $file
|
||||
* @param array $props Custom overrides for response props (e.g. headers)
|
||||
* @return self
|
||||
*/
|
||||
public static function file(string $file)
|
||||
public static function file(string $file, array $props = [])
|
||||
{
|
||||
return new static(F::read($file), F::extensionToMime(F::extension($file)));
|
||||
$props = array_merge([
|
||||
'body' => F::read($file),
|
||||
'type' => F::extensionToMime(F::extension($file))
|
||||
], $props);
|
||||
|
||||
return new static($props);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,12 +254,12 @@ class Response
|
||||
* @param int $code
|
||||
* @return self
|
||||
*/
|
||||
public static function redirect(?string $location = null, ?int $code = null)
|
||||
public static function redirect(string $location = '/', int $code = 302)
|
||||
{
|
||||
return new static([
|
||||
'code' => $code ?? 302,
|
||||
'code' => $code,
|
||||
'headers' => [
|
||||
'Location' => Url::unIdn($location ?? '/')
|
||||
'Location' => Url::unIdn($location)
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
@@ -158,7 +158,7 @@ class Route
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function next(): void
|
||||
public static function next(): void
|
||||
{
|
||||
throw new Exceptions\NextRouteException('next');
|
||||
}
|
||||
|
@@ -107,13 +107,14 @@ class Router
|
||||
$result = $route->action()->call($route, ...$route->arguments());
|
||||
}
|
||||
|
||||
$loop = false;
|
||||
$loop = false;
|
||||
} catch (Exceptions\NextRouteException $e) {
|
||||
$ignore[] = $route;
|
||||
}
|
||||
|
||||
if (is_a(static::$afterEach, 'Closure') === true) {
|
||||
$result = (static::$afterEach)($route, $path, $method, $result);
|
||||
$final = $loop === false;
|
||||
$result = (static::$afterEach)($route, $path, $method, $result, $final);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -119,8 +119,8 @@ class Url
|
||||
// matches the following groups of URLs:
|
||||
// //example.com/uri
|
||||
// http://example.com/uri, https://example.com/uri, ftp://example.com/uri
|
||||
// mailto:example@example.com
|
||||
return preg_match('!^(//|[a-z0-9+-.]+://|mailto:|tel:)!i', $url) === 1;
|
||||
// mailto:example@example.com, geo:49.0158,8.3239?z=11
|
||||
return preg_match('!^(//|[a-z0-9+-.]+://|mailto:|tel:|geo:)!i', $url) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user