Edit PHP version constraint and update Composer dependencies
This commit is contained in:
parent
231e1bce63
commit
e5b51981ff
52 changed files with 806 additions and 613 deletions
|
@ -3,6 +3,7 @@
|
|||
namespace Kirby\Cms;
|
||||
|
||||
use Closure;
|
||||
use Exception as GlobalException;
|
||||
use Generator;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Email\Email as BaseEmail;
|
||||
|
@ -318,9 +319,18 @@ class App
|
|||
}
|
||||
}
|
||||
|
||||
foreach (glob($this->root('blueprints') . '/' . $type . '/*.yml') as $blueprint) {
|
||||
$name = F::name($blueprint);
|
||||
$blueprints[$name] = $name;
|
||||
try {
|
||||
// protect against path traversal attacks
|
||||
$root = $this->root('blueprints') . '/' . $type;
|
||||
$realpath = Dir::realpath($root, $this->root('blueprints'));
|
||||
|
||||
foreach (glob($realpath . '/*.yml') as $blueprint) {
|
||||
$name = F::name($blueprint);
|
||||
$blueprints[$name] = $name;
|
||||
}
|
||||
} catch (GlobalException) {
|
||||
// if the realpath operation failed, the following glob was skipped,
|
||||
// keeping just the blueprints from extensions
|
||||
}
|
||||
|
||||
ksort($blueprints);
|
||||
|
@ -478,7 +488,7 @@ class App
|
|||
}
|
||||
|
||||
// controller from site root
|
||||
$controller = Controller::load($this->root('controllers') . '/' . $name . '.php');
|
||||
$controller = Controller::load($this->root('controllers') . '/' . $name . '.php', $this->root('controllers'));
|
||||
// controller from extension
|
||||
$controller ??= $this->extension('controllers', $name);
|
||||
|
||||
|
@ -1184,7 +1194,7 @@ class App
|
|||
string|null $path = null,
|
||||
string|null $method = null
|
||||
): Response|null {
|
||||
if (($_ENV['KIRBY_RENDER'] ?? true) === false) {
|
||||
if ((filter_var($_ENV['KIRBY_RENDER'] ?? true, FILTER_VALIDATE_BOOLEAN)) === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1292,11 +1302,36 @@ class App
|
|||
|
||||
// try to resolve image urls for pages and drafts
|
||||
if ($page = $site->findPageOrDraft($id)) {
|
||||
return $page->file($filename);
|
||||
return $this->resolveFile($page->file($filename));
|
||||
}
|
||||
|
||||
// try to resolve site files at least
|
||||
return $site->file($filename);
|
||||
return $this->resolveFile($site->file($filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a resolved file object using the configuration
|
||||
* @internal
|
||||
*/
|
||||
public function resolveFile(File|null $file): File|null
|
||||
{
|
||||
// shortcut for files that don't exist
|
||||
if ($file === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$option = $this->option('content.fileRedirects', true);
|
||||
|
||||
if ($option === true) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
if ($option instanceof Closure) {
|
||||
return $option($file) === true ? $file : null;
|
||||
}
|
||||
|
||||
// option was set to `false` or an invalid value
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -79,7 +79,7 @@ trait AppCaches
|
|||
$prefix =
|
||||
str_replace(['/', ':'], '_', $this->system()->indexUrl()) .
|
||||
'/' .
|
||||
str_replace('.', '/', $key);
|
||||
str_replace(['/', '.'], ['_', '/'], $key);
|
||||
|
||||
$defaults = [
|
||||
'active' => true,
|
||||
|
|
|
@ -105,10 +105,11 @@ class Collections
|
|||
{
|
||||
$kirby = App::instance();
|
||||
|
||||
// first check for collection file
|
||||
$file = $kirby->root('collections') . '/' . $name . '.php';
|
||||
// first check for collection file in the `collections` root
|
||||
$root = $kirby->root('collections');
|
||||
$file = $root . '/' . $name . '.php';
|
||||
|
||||
if (is_file($file) === true) {
|
||||
if (F::exists($file, $root) === true) {
|
||||
$collection = F::load($file, allowOutput: false);
|
||||
|
||||
if ($collection instanceof Closure) {
|
||||
|
|
|
@ -617,12 +617,20 @@ class File extends ModelWithContent
|
|||
}
|
||||
|
||||
/**
|
||||
* Simplified File URL that uses the parent
|
||||
* Page URL and the filename as a more stable
|
||||
* alternative for the media URLs.
|
||||
* Clean file URL that uses the parent page URL
|
||||
* and the filename as a more stable alternative
|
||||
* for the media URLs if available. The `content.fileRedirects`
|
||||
* option is used to disable this behavior or enable it
|
||||
* on a per-file basis.
|
||||
*/
|
||||
public function previewUrl(): string|null
|
||||
{
|
||||
// check if the clean file URL is accessible,
|
||||
// otherwise we need to fall back to the media URL
|
||||
if ($this->kirby()->resolveFile($this) === null) {
|
||||
return $this->url();
|
||||
}
|
||||
|
||||
$parent = $this->parent();
|
||||
$url = Url::to($this->id());
|
||||
|
||||
|
@ -651,6 +659,7 @@ class File extends ModelWithContent
|
|||
|
||||
return $url;
|
||||
case 'user':
|
||||
// there are no clean URL routes for user files
|
||||
return $this->url();
|
||||
default:
|
||||
return $url;
|
||||
|
|
|
@ -57,7 +57,7 @@ class Language
|
|||
}
|
||||
|
||||
static::$kirby = $props['kirby'] ?? null;
|
||||
$this->code = trim($props['code']);
|
||||
$this->code = basename(trim($props['code'])); // prevent path traversal
|
||||
$this->default = ($props['default'] ?? false) === true;
|
||||
$this->direction = ($props['direction'] ?? null) === 'rtl' ? 'rtl' : 'ltr';
|
||||
$this->name = trim($props['name'] ?? $this->code);
|
||||
|
@ -325,6 +325,7 @@ class Language
|
|||
public static function loadRules(string $code): array
|
||||
{
|
||||
$kirby = App::instance();
|
||||
$code = basename($code); // prevent path traversal
|
||||
$code = Str::contains($code, '.') ? Str::before($code, '.') : $code;
|
||||
$file = $kirby->root('i18n:rules') . '/' . $code . '.json';
|
||||
|
||||
|
|
|
@ -95,11 +95,13 @@ class Media
|
|||
string $filename
|
||||
): Response|false {
|
||||
$kirby = App::instance();
|
||||
$index = $kirby->root('index');
|
||||
$media = $kirby->root('media');
|
||||
|
||||
$root = match (true) {
|
||||
// assets
|
||||
is_string($model)
|
||||
=> $kirby->root('media') . '/assets/' . $model . '/' . $hash,
|
||||
=> $media . '/assets/' . $model . '/' . $hash,
|
||||
// parent files for file model that already included hash
|
||||
$model instanceof File
|
||||
=> dirname($model->mediaRoot()),
|
||||
|
@ -108,10 +110,13 @@ class Media
|
|||
=> $model->mediaRoot() . '/' . $hash
|
||||
};
|
||||
|
||||
$thumb = $root . '/' . $filename;
|
||||
$job = $root . '/.jobs/' . $filename . '.json';
|
||||
|
||||
try {
|
||||
// prevent path traversal
|
||||
$root = Dir::realpath($root, $media);
|
||||
|
||||
$thumb = $root . '/' . $filename;
|
||||
$job = $root . '/.jobs/' . $filename . '.json';
|
||||
|
||||
$options = Data::read($job);
|
||||
} catch (Throwable) {
|
||||
// send a customized error message to make clearer what happened here
|
||||
|
@ -127,7 +132,12 @@ class Media
|
|||
// this adds support for custom assets
|
||||
$source = match (true) {
|
||||
is_string($model) === true
|
||||
=> $kirby->root('index') . '/' . $model . '/' . $options['filename'],
|
||||
=> F::realpath(
|
||||
$index . '/' . $model . '/' . $options['filename'],
|
||||
$index
|
||||
),
|
||||
$model instanceof File
|
||||
=> $model->root(),
|
||||
default
|
||||
=> $model->file($options['filename'])->root()
|
||||
};
|
||||
|
|
|
@ -361,7 +361,7 @@ class Page extends ModelWithContent
|
|||
}
|
||||
|
||||
/**
|
||||
* Sorting number + Slug
|
||||
* Returns the directory name (UID with optional sorting number)
|
||||
*/
|
||||
public function dirname(): string
|
||||
{
|
||||
|
@ -377,7 +377,8 @@ class Page extends ModelWithContent
|
|||
}
|
||||
|
||||
/**
|
||||
* Sorting number + Slug
|
||||
* Returns the directory path relative to the `content` root
|
||||
* (including optional sorting numbers and draft directories)
|
||||
*/
|
||||
public function diruri(): string
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Http\Url as BaseUrl;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
|
@ -63,10 +64,11 @@ class Url extends BaseUrl
|
|||
$kirby = App::instance();
|
||||
$page = $kirby->site()->page();
|
||||
$path = $assetPath . '/' . $page->template() . '.' . $extension;
|
||||
$file = $kirby->root('assets') . '/' . $path;
|
||||
$root = $kirby->root('assets');
|
||||
$file = $root . '/' . $path;
|
||||
$url = $kirby->url('assets') . '/' . $path;
|
||||
|
||||
return file_exists($file) === true ? $url : null;
|
||||
return F::exists($file, $root) === true ? $url : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -114,9 +114,14 @@ class Dir
|
|||
/**
|
||||
* Checks if the directory exists on disk
|
||||
*/
|
||||
public static function exists(string $dir): bool
|
||||
public static function exists(string $dir, string|null $in = null): bool
|
||||
{
|
||||
return is_dir($dir) === true;
|
||||
try {
|
||||
static::realpath($dir, $in);
|
||||
return true;
|
||||
} catch (Exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -523,6 +528,33 @@ class Dir
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute path to the directory if the directory can be found.
|
||||
* @since 4.7.1
|
||||
*/
|
||||
public static function realpath(string $dir, string|null $in = null): string
|
||||
{
|
||||
$realpath = realpath($dir);
|
||||
|
||||
if ($realpath === false || is_dir($realpath) === false) {
|
||||
throw new Exception(sprintf('The directory does not exist at the given path: "%s"', $dir));
|
||||
}
|
||||
|
||||
if ($in !== null) {
|
||||
$parent = realpath($in);
|
||||
|
||||
if ($parent === false || is_dir($parent) === false) {
|
||||
throw new Exception(sprintf('The parent directory does not exist: "%s"', $in));
|
||||
}
|
||||
|
||||
if (substr($realpath, 0, strlen($parent)) !== $parent) {
|
||||
throw new Exception('The directory is not within the parent directory');
|
||||
}
|
||||
}
|
||||
|
||||
return $realpath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a folder including all containing files and folders
|
||||
*/
|
||||
|
|
|
@ -810,18 +810,24 @@ class Environment
|
|||
}
|
||||
|
||||
// load the config for the host
|
||||
if (empty($host) === false) {
|
||||
if (
|
||||
empty($host) === false &&
|
||||
F::exists($path = $root . '/config.' . $host . '.php', $root) === true
|
||||
) {
|
||||
$configHost = F::load(
|
||||
file: $root . '/config.' . $host . '.php',
|
||||
file: $path,
|
||||
fallback: [],
|
||||
allowOutput: false
|
||||
);
|
||||
}
|
||||
|
||||
// load the config for the server IP
|
||||
if (empty($addr) === false) {
|
||||
if (
|
||||
empty($addr) === false &&
|
||||
F::exists($path = $root . '/config.' . $addr . '.php', $root) === true
|
||||
) {
|
||||
$configAddr = F::load(
|
||||
file: $root . '/config.' . $addr . '.php',
|
||||
file: $path,
|
||||
fallback: [],
|
||||
allowOutput: false
|
||||
);
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Kirby\Panel\Lab;
|
|||
|
||||
use Kirby\Cms\App;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
|
@ -32,7 +33,7 @@ class Category
|
|||
) {
|
||||
$this->root = $root ?? static::base() . '/' . $this->id;
|
||||
|
||||
if (file_exists($this->root . '/index.php') === true) {
|
||||
if (F::exists($this->root . '/index.php', static::base()) === true) {
|
||||
$this->props = array_merge(
|
||||
require $this->root . '/index.php',
|
||||
$this->props
|
||||
|
|
|
@ -30,6 +30,8 @@ class Docs
|
|||
public function __construct(
|
||||
protected string $name
|
||||
) {
|
||||
// protect against path traversal
|
||||
$this->name = basename($name);
|
||||
$this->kirby = App::instance();
|
||||
$this->json = $this->read();
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ class Example
|
|||
|
||||
public function exists(): bool
|
||||
{
|
||||
return is_dir($this->root) === true;
|
||||
return Dir::exists($this->root, $this->parent->root()) === true;
|
||||
}
|
||||
|
||||
public function file(string $filename): string
|
||||
|
|
|
@ -231,8 +231,12 @@ abstract class Model
|
|||
// for card layouts with `cover: true` provide
|
||||
// crops based on the card ratio
|
||||
if ($layout === 'cards') {
|
||||
$ratio = explode('/', $settings['ratio'] ?? '1/1');
|
||||
$ratio = $ratio[0] / $ratio[1];
|
||||
$ratio = $settings['ratio'] ?? '1/1';
|
||||
|
||||
if (is_numeric($ratio) === false) {
|
||||
$ratio = explode('/', $ratio);
|
||||
$ratio = $ratio[0] / $ratio[1];
|
||||
}
|
||||
|
||||
return $image->srcset([
|
||||
$sizes[0] . 'w' => [
|
||||
|
|
|
@ -389,7 +389,8 @@ class FileSessionStore extends SessionStore
|
|||
*/
|
||||
protected function name(int $expiryTime, string $id): string
|
||||
{
|
||||
return $expiryTime . '.' . $id;
|
||||
// protect against path traversal
|
||||
return $expiryTime . '.' . basename($id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Kirby\Template;
|
|||
use Kirby\Cms\App;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Tpl;
|
||||
|
||||
|
@ -187,7 +188,7 @@ class Snippet extends Tpl
|
|||
$name = (string)$name;
|
||||
$file = $root . '/' . $name . '.php';
|
||||
|
||||
if (file_exists($file) === false) {
|
||||
if (F::exists($file, $root) === false) {
|
||||
$file = $kirby->extensions('snippets')[$name] ?? null;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Kirby\Toolkit;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Kirby\Filesystem\F;
|
||||
use ReflectionFunction;
|
||||
|
||||
|
@ -60,12 +61,24 @@ class Controller
|
|||
return $this->function->call($bind, ...$args);
|
||||
}
|
||||
|
||||
public static function load(string $file): static|null
|
||||
public static function load(string $file, string|null $in = null): static|null
|
||||
{
|
||||
if (is_file($file) === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// restrict file paths to the provided root
|
||||
// to prevent path traversal
|
||||
if ($in !== null) {
|
||||
try {
|
||||
$file = F::realpath($file, $in);
|
||||
} catch (Exception) {
|
||||
// don't expose whether the file exists
|
||||
// (which would have returned `null` above)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$function = F::load($file);
|
||||
|
||||
if ($function instanceof Closure === false) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue