This commit is contained in:
Bastian Allgeier
2020-07-07 12:40:13 +02:00
parent 5f025ac2c2
commit f79d2e960c
176 changed files with 10532 additions and 5343 deletions

View File

@@ -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);
}
}
/**

View File

@@ -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');

View File

@@ -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;

View File

@@ -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
*

View File

@@ -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)
]
]);
}

View File

@@ -158,7 +158,7 @@ class Route
*
* @return void
*/
public function next(): void
public static function next(): void
{
throw new Exceptions\NextRouteException('next');
}

View File

@@ -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);
}
}

View File

@@ -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;
}
/**