mirror of
https://github.com/getgrav/grav.git
synced 2026-05-07 01:46:47 +02:00
Merge remote-tracking branch 'security/advisory-fix-1' into develop
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
# v1.7.11
|
||||
## mm/dd/2021
|
||||
|
||||
1. [](#new)
|
||||
* Added configuration options to allow PHP methods to be used in Twig functions (`system.twig.safe_functions`) and filters (`system.twig.safe_filters`)
|
||||
* Deprecated using PHP methods in Twig without them being in the safe lists
|
||||
* Prevent dangerous PHP methods from being used as Twig functions and filters
|
||||
* Restrict filesystem Twig functions to accept only local filesystem and grav streams
|
||||
1. [](#improved)
|
||||
* Better GPM detection of unauthorized installations
|
||||
1. [](#bugfix)
|
||||
|
||||
@@ -113,6 +113,8 @@ twig:
|
||||
autoescape: true # Autoescape Twig vars (DEPRECATED, always enabled in strict mode)
|
||||
undefined_functions: true # Allow undefined functions
|
||||
undefined_filters: true # Allow undefined filters
|
||||
safe_functions: [] # List of PHP functions which are allowed to be used as Twig functions
|
||||
safe_filters: [] # List of PHP functions which are allowed to be used as Twig filters
|
||||
umask_fix: false # By default Twig creates cached files as 755, fix switches this to 775
|
||||
|
||||
assets: # Configuration for Assets Manager (JS, CSS)
|
||||
|
||||
351
system/src/Grav/Common/Twig/Extension/FilesystemExtension.php
Normal file
351
system/src/Grav/Common/Twig/Extension/FilesystemExtension.php
Normal file
@@ -0,0 +1,351 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\Extension;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Utils;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
/**
|
||||
* Class TwigExtension
|
||||
* @package Grav\Common\Twig
|
||||
*/
|
||||
class FilesystemExtension extends AbstractExtension
|
||||
{
|
||||
/** @var UniformResourceLocator */
|
||||
private $locator;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->locator = Grav::instance()['locator'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TwigFunction[]
|
||||
*/
|
||||
public function getFilters()
|
||||
{
|
||||
return $this->getFunctions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of all functions.
|
||||
*
|
||||
* @return TwigFunction[]
|
||||
*/
|
||||
public function getFunctions()
|
||||
{
|
||||
return [
|
||||
new TwigFunction('file_exists', [$this, 'file_exists']),
|
||||
new TwigFunction('fileatime', [$this, 'fileatime']),
|
||||
new TwigFunction('filectime', [$this, 'filectime']),
|
||||
new TwigFunction('filemtime', [$this, 'filemtime']),
|
||||
new TwigFunction('filesize', [$this, 'filesize']),
|
||||
new TwigFunction('filetype', [$this, 'filetype']),
|
||||
new TwigFunction('is_dir', [$this, 'is_dir']),
|
||||
new TwigFunction('is_file', [$this, 'is_file']),
|
||||
new TwigFunction('is_link', [$this, 'is_link']),
|
||||
new TwigFunction('is_readable', [$this, 'is_readable']),
|
||||
new TwigFunction('is_writable', [$this, 'is_writable']),
|
||||
new TwigFunction('is_writeable', [$this, 'is_writable']),
|
||||
new TwigFunction('lstat', [$this, 'lstat']),
|
||||
new TwigFunction('getimagesize', [$this, 'getimagesize']),
|
||||
new TwigFunction('exif_read_data', [$this, 'exif_read_data']),
|
||||
new TwigFunction('read_exif_data', [$this, 'exif_read_data']),
|
||||
new TwigFunction('exif_imagetype', [$this, 'exif_imagetype']),
|
||||
new TwigFunction('hash_file', [$this, 'hash_file']),
|
||||
new TwigFunction('hash_hmac_file', [$this, 'hash_hmac_file']),
|
||||
new TwigFunction('md5_file', [$this, 'md5_file']),
|
||||
new TwigFunction('sha1_file', [$this, 'sha1_file']),
|
||||
new TwigFunction('get_meta_tags', [$this, 'get_meta_tags']),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
public function file_exists($filename): bool
|
||||
{
|
||||
if (!$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return file_exists($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return int|false
|
||||
*/
|
||||
public function fileatime($filename)
|
||||
{
|
||||
if (!$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return fileatime($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return int|false
|
||||
*/
|
||||
public function filectime($filename)
|
||||
{
|
||||
if (!$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return filectime($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return int|false
|
||||
*/
|
||||
public function filemtime($filename)
|
||||
{
|
||||
if (!$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return filemtime($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return int|false
|
||||
*/
|
||||
public function filesize($filename)
|
||||
{
|
||||
if (!$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return filesize($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return string|false
|
||||
*/
|
||||
public function filetype($filename)
|
||||
{
|
||||
if (!$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return filetype($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
public function is_dir($filename): bool
|
||||
{
|
||||
if (!$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_dir($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
public function is_file($filename): bool
|
||||
{
|
||||
if (!$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_file($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
public function is_link($filename): bool
|
||||
{
|
||||
if (!$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_link($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
public function is_readable($filename): bool
|
||||
{
|
||||
if (!$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_readable($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
public function is_writable($filename): bool
|
||||
{
|
||||
if (!$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_writable($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return array|false
|
||||
*/
|
||||
public function lstat($filename)
|
||||
{
|
||||
if (!$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return lstat($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return array|false
|
||||
*/
|
||||
public function getimagesize($filename)
|
||||
{
|
||||
if (!$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return getimagesize($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @param string|null $required_sections
|
||||
* @param bool $as_arrays
|
||||
* @param bool $read_thumbnail
|
||||
* @return array|false
|
||||
*/
|
||||
public function exif_read_data($file, ?string $required_sections, bool $as_arrays = false, bool $read_thumbnail = false)
|
||||
{
|
||||
if (!Utils::functionExists('exif_read_data') || !$this->checkFilename($file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return exif_read_data($file, $required_sections, $as_arrays, $read_thumbnail);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return string|false
|
||||
*/
|
||||
public function exif_imagetype($filename)
|
||||
{
|
||||
if (!Utils::functionExists('exif_imagetype') || !$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return @exif_imagetype();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $algo
|
||||
* @param string $filename
|
||||
* @param bool $binary
|
||||
* @return string|false
|
||||
*/
|
||||
public function hash_file(string $algo, string $filename, bool $binary = false)
|
||||
{
|
||||
if (!$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hash_file($algo, $filename, $binary);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $algo
|
||||
* @param string $data
|
||||
* @param string $key
|
||||
* @param bool $binary
|
||||
* @return string|false
|
||||
*/
|
||||
public function hash_hmac_file(string $algo, string $data, string $key, bool $binary = false)
|
||||
{
|
||||
if (!$this->checkFilename($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hash_hmac_file($algo, $data, $key, $binary);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param bool $binary
|
||||
* @return string|false
|
||||
*/
|
||||
public function md5_file($filename, bool $binary = false)
|
||||
{
|
||||
if (!$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return md5_file($filename, $binary);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param bool $binary
|
||||
* @return string|false
|
||||
*/
|
||||
public function sha1_file($filename, bool $binary = false)
|
||||
{
|
||||
if (!$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return sha1_file($filename, $binary);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return array|false
|
||||
*/
|
||||
public function get_meta_tags($filename)
|
||||
{
|
||||
if (!$this->checkFilename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return get_meta_tags($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
private function checkFilename($filename): bool
|
||||
{
|
||||
return is_string($filename) && (!str_contains($filename, '://') || $this->locator->isStream($filename));
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig;
|
||||
namespace Grav\Common\Twig\Extension;
|
||||
|
||||
use Cron\CronExpression;
|
||||
use Grav\Common\Config\Config;
|
||||
@@ -60,14 +60,13 @@ use function is_numeric;
|
||||
use function is_object;
|
||||
use function is_scalar;
|
||||
use function is_string;
|
||||
use function ord;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Class TwigExtension
|
||||
* @package Grav\Common\Twig
|
||||
*/
|
||||
class TwigExtension extends AbstractExtension implements GlobalsInterface
|
||||
class GravExtension extends AbstractExtension implements GlobalsInterface
|
||||
{
|
||||
/** @var Grav */
|
||||
protected $grav;
|
||||
@@ -214,7 +213,6 @@ class TwigExtension extends AbstractExtension implements GlobalsInterface
|
||||
new TwigFunction('svg_image', [$this, 'svgImageFunction']),
|
||||
new TwigFunction('xss', [$this, 'xssFunc']),
|
||||
|
||||
|
||||
// Translations
|
||||
new TwigFunction('t', [$this, 'translate'], ['needs_environment' => true]),
|
||||
new TwigFunction('tl', [$this, 'translateLanguage']),
|
||||
@@ -16,6 +16,9 @@ use Grav\Common\Language\Language;
|
||||
use Grav\Common\Language\LanguageCodes;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Twig\Extension\FilesystemExtension;
|
||||
use Grav\Common\Twig\Extension\GravExtension;
|
||||
use Grav\Common\Utils;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use Phive\Twig\Extensions\Deferred\DeferredExtension;
|
||||
@@ -34,6 +37,8 @@ use Twig\Profiler\Profile;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
use function function_exists;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Class Twig
|
||||
@@ -154,27 +159,53 @@ class Twig
|
||||
|
||||
$this->twig = new TwigEnvironment($loader_chain, $params);
|
||||
|
||||
if ($config->get('system.twig.undefined_functions')) {
|
||||
$this->twig->registerUndefinedFunctionCallback(function ($name) {
|
||||
$this->twig->registerUndefinedFunctionCallback(function ($name) use ($config) {
|
||||
$allowed = $config->get('system.twig.safe_functions');
|
||||
if (is_array($allowed) && in_array($name, $allowed, true) && function_exists($name)) {
|
||||
return new TwigFunction($name, $name);
|
||||
}
|
||||
if ($config->get('system.twig.undefined_functions')) {
|
||||
if (function_exists($name)) {
|
||||
return new TwigFunction($name, $name);
|
||||
if (!Utils::isDangerousFunction($name)) {
|
||||
user_error("PHP function {$name}() was used as Twig function. This is deprecated in Grav 1.7. Please add it to system configuration: `system.twig.safe_functions`", E_USER_DEPRECATED);
|
||||
|
||||
return new TwigFunction($name, $name);
|
||||
}
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->grav['debugger'];
|
||||
$debugger->addException(new RuntimeException("Blocked potentially dangerous PHP function {$name}() being used as Twig function. If you really want to use it, please add it to system configuration: `system.twig.safe_functions`"));
|
||||
}
|
||||
|
||||
return new TwigFunction($name, static function () {
|
||||
});
|
||||
});
|
||||
}
|
||||
return new TwigFunction($name, static function () {});
|
||||
}
|
||||
|
||||
if ($config->get('system.twig.undefined_filters')) {
|
||||
$this->twig->registerUndefinedFilterCallback(function ($name) {
|
||||
return false;
|
||||
});
|
||||
|
||||
$this->twig->registerUndefinedFilterCallback(function ($name) use ($config) {
|
||||
$allowed = $config->get('system.twig.safe_filters');
|
||||
if (is_array($allowed) && in_array($name, $allowed, true) && function_exists($name)) {
|
||||
return new TwigFilter($name, $name);
|
||||
}
|
||||
if ($config->get('system.twig.undefined_filters')) {
|
||||
if (function_exists($name)) {
|
||||
return new TwigFilter($name, $name);
|
||||
if (!Utils::isDangerousFunction($name)) {
|
||||
user_error("PHP function {$name}() used as Twig filter. This is deprecated in Grav 1.7. Please add it to system configuration: `system.twig.safe_filters`", E_USER_DEPRECATED);
|
||||
|
||||
return new TwigFilter($name, $name);
|
||||
}
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->grav['debugger'];
|
||||
$debugger->addException(new RuntimeException("Blocked potentially dangerous PHP function {$name}() being used as Twig filter. If you really want to use it, please add it to system configuration: `system.twig.safe_filters`"));
|
||||
}
|
||||
|
||||
return new TwigFilter($name, static function () {
|
||||
});
|
||||
});
|
||||
}
|
||||
return new TwigFilter($name, static function () {});
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$this->grav->fireEvent('onTwigInitialized');
|
||||
|
||||
@@ -188,7 +219,8 @@ class Twig
|
||||
if ($config->get('system.twig.debug')) {
|
||||
$this->twig->addExtension(new DebugExtension());
|
||||
}
|
||||
$this->twig->addExtension(new TwigExtension());
|
||||
$this->twig->addExtension(new GravExtension());
|
||||
$this->twig->addExtension(new FilesystemExtension());
|
||||
$this->twig->addExtension(new DeferredExtension());
|
||||
$this->twig->addExtension(new StringLoaderExtension());
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ use function strlen;
|
||||
*/
|
||||
abstract class Utils
|
||||
{
|
||||
/** @var array */
|
||||
/** @var array */
|
||||
protected static $nonces = [];
|
||||
|
||||
protected const ROOTURL_REGEX = '{^((?:http[s]?:\/\/[^\/]+)|(?:\/\/[^\/]+))(.*)}';
|
||||
@@ -178,8 +178,8 @@ abstract class Utils
|
||||
/**
|
||||
* Check if the $haystack string starts with the substring $needle
|
||||
*
|
||||
* @param string $haystack
|
||||
* @param string|string[] $needle
|
||||
* @param string $haystack
|
||||
* @param string|string[] $needle
|
||||
* @param bool $case_sensitive
|
||||
* @return bool
|
||||
*/
|
||||
@@ -202,8 +202,8 @@ abstract class Utils
|
||||
/**
|
||||
* Check if the $haystack string ends with the substring $needle
|
||||
*
|
||||
* @param string $haystack
|
||||
* @param string|string[] $needle
|
||||
* @param string $haystack
|
||||
* @param string|string[] $needle
|
||||
* @param bool $case_sensitive
|
||||
* @return bool
|
||||
*/
|
||||
@@ -227,9 +227,9 @@ abstract class Utils
|
||||
/**
|
||||
* Check if the $haystack string contains the substring $needle
|
||||
*
|
||||
* @param string $haystack
|
||||
* @param string|string[] $needle
|
||||
* @param bool $case_sensitive
|
||||
* @param string $haystack
|
||||
* @param string|string[] $needle
|
||||
* @param bool $case_sensitive
|
||||
* @return bool
|
||||
*/
|
||||
public static function contains($haystack, $needle, $case_sensitive = true)
|
||||
@@ -266,19 +266,19 @@ abstract class Utils
|
||||
{
|
||||
$regex = str_replace(
|
||||
array("\*", "\?"), // wildcard chars
|
||||
array('.*','.'), // regexp chars
|
||||
array('.*', '.'), // regexp chars
|
||||
preg_quote($wildcard_pattern, '/')
|
||||
);
|
||||
|
||||
return preg_match('/^'.$regex.'$/is', $haystack);
|
||||
return preg_match('/^' . $regex . '$/is', $haystack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render simple template filling up the variables in it. If value is not defined, leave it as it was.
|
||||
*
|
||||
* @param string $template Template string
|
||||
* @param array $variables Variables with values
|
||||
* @param array $brackets Optional array of opening and closing brackets or symbols
|
||||
* @param string $template Template string
|
||||
* @param array $variables Variables with values
|
||||
* @param array $brackets Optional array of opening and closing brackets or symbols
|
||||
* @return string Final string filled with values
|
||||
*/
|
||||
public static function simpleTemplate(string $template, array $variables, array $brackets = ['{', '}']): string
|
||||
@@ -376,8 +376,8 @@ abstract class Utils
|
||||
/**
|
||||
* Merge two objects into one.
|
||||
*
|
||||
* @param object $obj1
|
||||
* @param object $obj2
|
||||
* @param object $obj1
|
||||
* @param object $obj2
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
@@ -415,7 +415,7 @@ abstract class Utils
|
||||
*/
|
||||
public static function arrayRemoveValue(array $search, $value)
|
||||
{
|
||||
foreach ((array) $value as $val) {
|
||||
foreach ((array)$value as $val) {
|
||||
$key = array_search($val, $search);
|
||||
if ($key !== false) {
|
||||
unset($search[$key]);
|
||||
@@ -481,8 +481,8 @@ abstract class Utils
|
||||
/**
|
||||
* Array combine but supports different array lengths
|
||||
*
|
||||
* @param array $arr1
|
||||
* @param array $arr2
|
||||
* @param array $arr1
|
||||
* @param array $arr2
|
||||
* @return array|false
|
||||
*/
|
||||
public static function arrayCombine($arr1, $arr2)
|
||||
@@ -495,7 +495,7 @@ abstract class Utils
|
||||
/**
|
||||
* Array is associative or not
|
||||
*
|
||||
* @param array $arr
|
||||
* @param array $arr
|
||||
* @return bool
|
||||
*/
|
||||
public static function arrayIsAssociative($arr)
|
||||
@@ -517,15 +517,15 @@ abstract class Utils
|
||||
$now = new DateTime();
|
||||
|
||||
$date_formats = [
|
||||
'd-m-Y H:i' => 'd-m-Y H:i (e.g. '.$now->format('d-m-Y H:i').')',
|
||||
'Y-m-d H:i' => 'Y-m-d H:i (e.g. '.$now->format('Y-m-d H:i').')',
|
||||
'm/d/Y h:i a' => 'm/d/Y h:i a (e.g. '.$now->format('m/d/Y h:i a').')',
|
||||
'H:i d-m-Y' => 'H:i d-m-Y (e.g. '.$now->format('H:i d-m-Y').')',
|
||||
'h:i a m/d/Y' => 'h:i a m/d/Y (e.g. '.$now->format('h:i a m/d/Y').')',
|
||||
];
|
||||
'd-m-Y H:i' => 'd-m-Y H:i (e.g. ' . $now->format('d-m-Y H:i') . ')',
|
||||
'Y-m-d H:i' => 'Y-m-d H:i (e.g. ' . $now->format('Y-m-d H:i') . ')',
|
||||
'm/d/Y h:i a' => 'm/d/Y h:i a (e.g. ' . $now->format('m/d/Y h:i a') . ')',
|
||||
'H:i d-m-Y' => 'H:i d-m-Y (e.g. ' . $now->format('H:i d-m-Y') . ')',
|
||||
'h:i a m/d/Y' => 'h:i a m/d/Y (e.g. ' . $now->format('h:i a m/d/Y') . ')',
|
||||
];
|
||||
$default_format = Grav::instance()['config']->get('system.pages.dateformat.default');
|
||||
if ($default_format) {
|
||||
$date_formats = array_merge([$default_format => $default_format.' (e.g. '.$now->format($default_format).')'], $date_formats);
|
||||
$date_formats = array_merge([$default_format => $default_format . ' (e.g. ' . $now->format($default_format) . ')'], $date_formats);
|
||||
}
|
||||
|
||||
return $date_formats;
|
||||
@@ -552,11 +552,11 @@ abstract class Utils
|
||||
/**
|
||||
* Truncate text by number of characters but can cut off words.
|
||||
*
|
||||
* @param string $string
|
||||
* @param int $limit Max number of characters.
|
||||
* @param bool $up_to_break truncate up to breakpoint after char count
|
||||
* @param string $break Break point.
|
||||
* @param string $pad Appended padding to the end of the string.
|
||||
* @param string $string
|
||||
* @param int $limit Max number of characters.
|
||||
* @param bool $up_to_break truncate up to breakpoint after char count
|
||||
* @param string $break Break point.
|
||||
* @param string $pad Appended padding to the end of the string.
|
||||
* @return string
|
||||
*/
|
||||
public static function truncate($string, $limit = 150, $up_to_break = false, $break = ' ', $pad = '…')
|
||||
@@ -582,7 +582,7 @@ abstract class Utils
|
||||
* Truncate text by number of characters in a "word-safe" manor.
|
||||
*
|
||||
* @param string $string
|
||||
* @param int $limit
|
||||
* @param int $limit
|
||||
* @return string
|
||||
*/
|
||||
public static function safeTruncate($string, $limit = 150)
|
||||
@@ -594,9 +594,9 @@ abstract class Utils
|
||||
/**
|
||||
* Truncate HTML by number of characters. not "word-safe"!
|
||||
*
|
||||
* @param string $text
|
||||
* @param int $length in characters
|
||||
* @param string $ellipsis
|
||||
* @param string $text
|
||||
* @param int $length in characters
|
||||
* @param string $ellipsis
|
||||
* @return string
|
||||
*/
|
||||
public static function truncateHtml($text, $length = 100, $ellipsis = '...')
|
||||
@@ -607,9 +607,9 @@ abstract class Utils
|
||||
/**
|
||||
* Truncate HTML by number of characters in a "word-safe" manor.
|
||||
*
|
||||
* @param string $text
|
||||
* @param int $length in words
|
||||
* @param string $ellipsis
|
||||
* @param string $text
|
||||
* @param int $length in words
|
||||
* @param string $ellipsis
|
||||
* @return string
|
||||
*/
|
||||
public static function safeTruncateHtml($text, $length = 25, $ellipsis = '...')
|
||||
@@ -633,8 +633,8 @@ abstract class Utils
|
||||
*
|
||||
* @param string $file the full path to the file to be downloaded
|
||||
* @param bool $force_download as opposed to letting browser choose if to download or render
|
||||
* @param int $sec Throttling, try 0.1 for some speed throttling of downloads
|
||||
* @param int $bytes Size of chunks to send in bytes. Default is 1024
|
||||
* @param int $sec Throttling, try 0.1 for some speed throttling of downloads
|
||||
* @param int $bytes Size of chunks to send in bytes. Default is 1024
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function download($file, $force_download = true, $sec = 0, $bytes = 1024)
|
||||
@@ -645,7 +645,7 @@ abstract class Utils
|
||||
|
||||
$file_parts = pathinfo($file);
|
||||
$mimetype = static::getMimeByExtension($file_parts['extension']);
|
||||
$size = filesize($file); // File size
|
||||
$size = filesize($file); // File size
|
||||
|
||||
// clean all buffers
|
||||
while (ob_get_level()) {
|
||||
@@ -742,7 +742,7 @@ abstract class Utils
|
||||
// Set from uri extension
|
||||
$uri_extension = $uri->extension();
|
||||
if (is_string($uri_extension) && $uri->isValidExtension($uri_extension)) {
|
||||
return($uri_extension);
|
||||
return ($uri_extension);
|
||||
}
|
||||
|
||||
// Use content negotiation via the `accept:` header
|
||||
@@ -1060,7 +1060,7 @@ abstract class Utils
|
||||
|
||||
$pretty_offset = "UTC${offset_prefix}${offset_formatted}";
|
||||
|
||||
$timezone_list[$timezone] = "(${pretty_offset}) ".str_replace('_', ' ', $timezone);
|
||||
$timezone_list[$timezone] = "(${pretty_offset}) " . str_replace('_', ' ', $timezone);
|
||||
}
|
||||
|
||||
return $timezone_list;
|
||||
@@ -1069,11 +1069,11 @@ abstract class Utils
|
||||
/**
|
||||
* Recursively filter an array, filtering values by processing them through the $fn function argument
|
||||
*
|
||||
* @param array $source the Array to filter
|
||||
* @param callable $fn the function to pass through each array item
|
||||
* @param array $source the Array to filter
|
||||
* @param callable $fn the function to pass through each array item
|
||||
* @return array
|
||||
*/
|
||||
public static function arrayFilterRecursive(Array $source, $fn)
|
||||
public static function arrayFilterRecursive(array $source, $fn)
|
||||
{
|
||||
$result = [];
|
||||
foreach ($source as $key => $value) {
|
||||
@@ -1093,15 +1093,15 @@ abstract class Utils
|
||||
/**
|
||||
* Flatten a multi-dimensional associative array into query params.
|
||||
*
|
||||
* @param array $array
|
||||
* @param string $prepend
|
||||
* @param array $array
|
||||
* @param string $prepend
|
||||
* @return array
|
||||
*/
|
||||
public static function arrayToQueryParams($array, $prepend = '')
|
||||
{
|
||||
$results = [];
|
||||
foreach ($array as $key => $value) {
|
||||
$name = $prepend ? $prepend . '[' . $key . ']' : $key;
|
||||
$name = $prepend ? $prepend . '[' . $key . ']' : $key;
|
||||
|
||||
if (is_array($value)) {
|
||||
$results = array_merge($results, static::arrayToQueryParams($value, $name));
|
||||
@@ -1138,8 +1138,8 @@ abstract class Utils
|
||||
/**
|
||||
* Flatten a multi-dimensional associative array into dot notation
|
||||
*
|
||||
* @param array $array
|
||||
* @param string $prepend
|
||||
* @param array $array
|
||||
* @param string $prepend
|
||||
* @return array
|
||||
*/
|
||||
public static function arrayFlattenDotNotation($array, $prepend = '')
|
||||
@@ -1147,9 +1147,9 @@ abstract class Utils
|
||||
$results = array();
|
||||
foreach ($array as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$results = array_merge($results, static::arrayFlattenDotNotation($value, $prepend.$key.'.'));
|
||||
$results = array_merge($results, static::arrayFlattenDotNotation($value, $prepend . $key . '.'));
|
||||
} else {
|
||||
$results[$prepend.$key] = $value;
|
||||
$results[$prepend . $key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1297,7 +1297,7 @@ abstract class Utils
|
||||
* with reverse proxy setups.
|
||||
*
|
||||
* @param string $action
|
||||
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
|
||||
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
|
||||
* @return string the nonce string
|
||||
*/
|
||||
private static function generateNonceString($action, $previousTick = false)
|
||||
@@ -1334,8 +1334,8 @@ abstract class Utils
|
||||
* Creates a hashed nonce tied to the passed action. Tied to the current user and time. The nonce for a given
|
||||
* action is the same for 12 hours.
|
||||
*
|
||||
* @param string $action the action the nonce is tied to (e.g. save-user-admin or move-page-homepage)
|
||||
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
|
||||
* @param string $action the action the nonce is tied to (e.g. save-user-admin or move-page-homepage)
|
||||
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
|
||||
* @return string the nonce
|
||||
*/
|
||||
public static function getNonce($action, $previousTick = false)
|
||||
@@ -1353,7 +1353,7 @@ abstract class Utils
|
||||
/**
|
||||
* Verify the passed nonce for the give action
|
||||
*
|
||||
* @param string|string[] $nonce the nonce to verify
|
||||
* @param string|string[] $nonce the nonce to verify
|
||||
* @param string $action the action to verify the nonce to
|
||||
* @return boolean verified or not
|
||||
*/
|
||||
@@ -1434,7 +1434,7 @@ abstract class Utils
|
||||
while (count($keys) > 1) {
|
||||
$key = array_shift($keys);
|
||||
|
||||
if (! isset($array[$key]) || ! is_array($array[$key])) {
|
||||
if (!isset($array[$key]) || !is_array($array[$key])) {
|
||||
$array[$key] = array();
|
||||
}
|
||||
|
||||
@@ -1725,7 +1725,7 @@ abstract class Utils
|
||||
$size *= 1024 ** stripos('bkmgtpezy', $unit[0]);
|
||||
}
|
||||
|
||||
return (int) abs(round($size));
|
||||
return (int)abs(round($size));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1770,7 +1770,7 @@ abstract class Utils
|
||||
public static function processMarkdown($string, $block = true, $page = null)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$page = $page ?? $grav['page'] ?? null;
|
||||
$page = $page ?? $grav['page'] ?? null;
|
||||
$defaults = [
|
||||
'markdown' => $grav['config']->get('system.pages.markdown', []),
|
||||
'images' => $grav['config']->get('system.images', [])
|
||||
@@ -1812,12 +1812,12 @@ abstract class Utils
|
||||
$ip = (string)inet_pton($ip);
|
||||
|
||||
// Maximum netmask length = same as packed address
|
||||
$len = 8*strlen($ip);
|
||||
$len = 8 * strlen($ip);
|
||||
if ($prefix > $len) {
|
||||
$prefix = $len;
|
||||
}
|
||||
|
||||
$mask = str_repeat('f', $prefix>>2);
|
||||
$mask = str_repeat('f', $prefix >> 2);
|
||||
|
||||
switch ($prefix & 3) {
|
||||
case 3:
|
||||
@@ -1830,7 +1830,7 @@ abstract class Utils
|
||||
$mask .= '8';
|
||||
break;
|
||||
}
|
||||
$mask = str_pad($mask, $len>>2, '0');
|
||||
$mask = str_pad($mask, $len >> 2, '0');
|
||||
|
||||
// Packed representation of netmask
|
||||
$mask = pack('H*', $mask);
|
||||
@@ -1861,4 +1861,244 @@ abstract class Utils
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public static function isDangerousFunction(string $name): bool
|
||||
{
|
||||
static $commandExecutionFunctions = [
|
||||
'exec',
|
||||
'passthru',
|
||||
'system',
|
||||
'shell_exec',
|
||||
'popen',
|
||||
'proc_open',
|
||||
'pcntl_exec',
|
||||
];
|
||||
|
||||
static $codeExecutionFunctions = [
|
||||
'assert',
|
||||
'preg_replace',
|
||||
'create_function',
|
||||
'include',
|
||||
'include_once',
|
||||
'require',
|
||||
'require_once'
|
||||
];
|
||||
|
||||
static $callbackFunctions = [
|
||||
'ob_start' => 0,
|
||||
'array_diff_uassoc' => -1,
|
||||
'array_diff_ukey' => -1,
|
||||
'array_filter' => 1,
|
||||
'array_intersect_uassoc' => -1,
|
||||
'array_intersect_ukey' => -1,
|
||||
'array_map' => 0,
|
||||
'array_reduce' => 1,
|
||||
'array_udiff_assoc' => -1,
|
||||
'array_udiff_uassoc' => [-1, -2],
|
||||
'array_udiff' => -1,
|
||||
'array_uintersect_assoc' => -1,
|
||||
'array_uintersect_uassoc' => [-1, -2],
|
||||
'array_uintersect' => -1,
|
||||
'array_walk_recursive' => 1,
|
||||
'array_walk' => 1,
|
||||
'assert_options' => 1,
|
||||
'uasort' => 1,
|
||||
'uksort' => 1,
|
||||
'usort' => 1,
|
||||
'preg_replace_callback' => 1,
|
||||
'spl_autoload_register' => 0,
|
||||
'iterator_apply' => 1,
|
||||
'call_user_func' => 0,
|
||||
'call_user_func_array' => 0,
|
||||
'register_shutdown_function' => 0,
|
||||
'register_tick_function' => 0,
|
||||
'set_error_handler' => 0,
|
||||
'set_exception_handler' => 0,
|
||||
'session_set_save_handler' => [0, 1, 2, 3, 4, 5],
|
||||
'sqlite_create_aggregate' => [2, 3],
|
||||
'sqlite_create_function' => 2,
|
||||
];
|
||||
|
||||
static $informationDiscosureFunctions = [
|
||||
'phpinfo',
|
||||
'posix_mkfifo',
|
||||
'posix_getlogin',
|
||||
'posix_ttyname',
|
||||
'getenv',
|
||||
'get_current_user',
|
||||
'proc_get_status',
|
||||
'get_cfg_var',
|
||||
'disk_free_space',
|
||||
'disk_total_space',
|
||||
'diskfreespace',
|
||||
'getcwd',
|
||||
'getlastmo',
|
||||
'getmygid',
|
||||
'getmyinode',
|
||||
'getmypid',
|
||||
'getmyuid'
|
||||
];
|
||||
|
||||
static $otherFunctions = [
|
||||
'extract',
|
||||
'parse_str',
|
||||
'putenv',
|
||||
'ini_set',
|
||||
'mail',
|
||||
'header',
|
||||
'proc_nice',
|
||||
'proc_terminate',
|
||||
'proc_close',
|
||||
'pfsockopen',
|
||||
'fsockopen',
|
||||
'apache_child_terminate',
|
||||
'posix_kill',
|
||||
'posix_mkfifo',
|
||||
'posix_setpgid',
|
||||
'posix_setsid',
|
||||
'posix_setuid',
|
||||
];
|
||||
|
||||
if (in_array($name, $commandExecutionFunctions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($name, $codeExecutionFunctions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isset($callbackFunctions[$name])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($name, $informationDiscosureFunctions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($name, $otherFunctions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return static::isFilesystemFunction($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public static function isFilesystemFunction(string $name): bool
|
||||
{
|
||||
static $fileWriteFunctions = [
|
||||
'fopen',
|
||||
'tmpfile',
|
||||
'bzopen',
|
||||
'gzopen',
|
||||
// write to filesystem (partially in combination with reading)
|
||||
'chgrp',
|
||||
'chmod',
|
||||
'chown',
|
||||
'copy',
|
||||
'file_put_contents',
|
||||
'lchgrp',
|
||||
'lchown',
|
||||
'link',
|
||||
'mkdir',
|
||||
'move_uploaded_file',
|
||||
'rename',
|
||||
'rmdir',
|
||||
'symlink',
|
||||
'tempnam',
|
||||
'touch',
|
||||
'unlink',
|
||||
'imagepng',
|
||||
'imagewbmp',
|
||||
'image2wbmp',
|
||||
'imagejpeg',
|
||||
'imagexbm',
|
||||
'imagegif',
|
||||
'imagegd',
|
||||
'imagegd2',
|
||||
'iptcembed',
|
||||
'ftp_get',
|
||||
'ftp_nb_get',
|
||||
];
|
||||
|
||||
static $fileContentFunctions = [
|
||||
'file_get_contents',
|
||||
'file',
|
||||
'filegroup',
|
||||
'fileinode',
|
||||
'fileowner',
|
||||
'fileperms',
|
||||
'glob',
|
||||
'is_executable',
|
||||
'is_uploaded_file',
|
||||
'parse_ini_file',
|
||||
'readfile',
|
||||
'readlink',
|
||||
'realpath',
|
||||
'gzfile',
|
||||
'readgzfile',
|
||||
'stat',
|
||||
'imagecreatefromgif',
|
||||
'imagecreatefromjpeg',
|
||||
'imagecreatefrompng',
|
||||
'imagecreatefromwbmp',
|
||||
'imagecreatefromxbm',
|
||||
'imagecreatefromxpm',
|
||||
'ftp_put',
|
||||
'ftp_nb_put',
|
||||
'hash_update_file',
|
||||
'highlight_file',
|
||||
'show_source',
|
||||
'php_strip_whitespace',
|
||||
];
|
||||
|
||||
static $filesystemFunctions = [
|
||||
// read from filesystem
|
||||
'file_exists',
|
||||
'fileatime',
|
||||
'filectime',
|
||||
'filemtime',
|
||||
'filesize',
|
||||
'filetype',
|
||||
'is_dir',
|
||||
'is_file',
|
||||
'is_link',
|
||||
'is_readable',
|
||||
'is_writable',
|
||||
'is_writeable',
|
||||
'linkinfo',
|
||||
'lstat',
|
||||
//'pathinfo',
|
||||
'getimagesize',
|
||||
'exif_read_data',
|
||||
'read_exif_data',
|
||||
'exif_thumbnail',
|
||||
'exif_imagetype',
|
||||
'hash_file',
|
||||
'hash_hmac_file',
|
||||
'md5_file',
|
||||
'sha1_file',
|
||||
'get_meta_tags',
|
||||
];
|
||||
|
||||
if (in_array($name, $fileWriteFunctions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($name, $fileContentFunctions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($name, $filesystemFunctions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user