diff --git a/app/Config/Cache.php b/app/Config/Cache.php index 8c7900a84926..a3b3bbcf895d 100644 --- a/app/Config/Cache.php +++ b/app/Config/Cache.php @@ -46,6 +46,8 @@ class Cache extends BaseConfig * system. * * @var string + * + * @deprecated Use the driver-specific variant under $file */ public $storePath = WRITEPATH . 'cache/'; @@ -80,6 +82,20 @@ class Cache extends BaseConfig */ public $prefix = ''; + /** + * -------------------------------------------------------------------------- + * File settings + * -------------------------------------------------------------------------- + * Your file storage preferences can be specified below, if you are using + * the File driver. + * + * @var array + */ + public $file = [ + 'storePath' => WRITEPATH . 'cache/', + 'mode' => 0640, + ]; + /** * ------------------------------------------------------------------------- * Memcached settings diff --git a/app/Config/Mimes.php b/app/Config/Mimes.php index 9eb6e2e181e5..25c79791a76d 100644 --- a/app/Config/Mimes.php +++ b/app/Config/Mimes.php @@ -149,6 +149,7 @@ class Mimes 'multipart/x-zip', ], 'rar' => [ + 'application/vnd.rar', 'application/x-rar', 'application/rar', 'application/x-rar-compressed', diff --git a/system/Cache/Handlers/FileHandler.php b/system/Cache/Handlers/FileHandler.php index e99874e9d173..d4ed24aecd6f 100644 --- a/system/Cache/Handlers/FileHandler.php +++ b/system/Cache/Handlers/FileHandler.php @@ -34,6 +34,16 @@ class FileHandler implements CacheInterface */ protected $path; + /** + * Mode for the stored files. + * Must be chmod-safe (octal). + * + * @var integer + * + * @see https://www.php.net/manual/en/function.chmod.php + */ + protected $mode; + //-------------------------------------------------------------------- /** @@ -44,14 +54,24 @@ class FileHandler implements CacheInterface */ public function __construct(Cache $config) { - $path = ! empty($config->storePath) ? $config->storePath : WRITEPATH . 'cache'; - if (! is_really_writable($path)) + if (! property_exists($config, 'file')) + { + $config->file = [ + 'storePath' => $config->storePath ?? WRITEPATH . 'cache', + 'mode' => 0640, + ]; + } + + $this->path = ! empty($config->file['storePath']) ? $config->file['storePath'] : WRITEPATH . 'cache'; + $this->path = rtrim($this->path, '/') . '/'; + + if (! is_really_writable($this->path)) { - throw CacheException::forUnableToWrite($path); + throw CacheException::forUnableToWrite($this->path); } - $this->prefix = $config->prefix ?: ''; - $this->path = rtrim($path, '/') . '/'; + $this->mode = $config->file['mode'] ?? 0640; + $this->prefix = (string) $config->prefix; } //-------------------------------------------------------------------- @@ -105,7 +125,7 @@ public function save(string $key, $value, int $ttl = 60) if ($this->writeFile($this->path . $key, serialize($contents))) { - chmod($this->path . $key, 0640); + chmod($this->path . $key, $this->mode); return true; } diff --git a/system/Cache/Handlers/MemcachedHandler.php b/system/Cache/Handlers/MemcachedHandler.php index 84d6539bcc81..8a883e26823b 100644 --- a/system/Cache/Handlers/MemcachedHandler.php +++ b/system/Cache/Handlers/MemcachedHandler.php @@ -58,7 +58,7 @@ class MemcachedHandler implements CacheInterface */ public function __construct(Cache $config) { - $this->prefix = $config->prefix ?: ''; + $this->prefix = (string) $config->prefix; if (! empty($config)) { diff --git a/system/Cache/Handlers/PredisHandler.php b/system/Cache/Handlers/PredisHandler.php index 81d982dd5f1b..2a062e17f2fa 100644 --- a/system/Cache/Handlers/PredisHandler.php +++ b/system/Cache/Handlers/PredisHandler.php @@ -58,7 +58,7 @@ class PredisHandler implements CacheInterface */ public function __construct(Cache $config) { - $this->prefix = $config->prefix ?: ''; + $this->prefix = (string) $config->prefix; if (isset($config->redis)) { diff --git a/system/Cache/Handlers/RedisHandler.php b/system/Cache/Handlers/RedisHandler.php index 5af9f1f07fbf..d18efb1e93d5 100644 --- a/system/Cache/Handlers/RedisHandler.php +++ b/system/Cache/Handlers/RedisHandler.php @@ -58,7 +58,7 @@ class RedisHandler implements CacheInterface */ public function __construct(Cache $config) { - $this->prefix = $config->prefix ?: ''; + $this->prefix = (string) $config->prefix; if (! empty($config)) { diff --git a/system/Cache/Handlers/WincacheHandler.php b/system/Cache/Handlers/WincacheHandler.php index a37aba2556be..4c445d4f0076 100644 --- a/system/Cache/Handlers/WincacheHandler.php +++ b/system/Cache/Handlers/WincacheHandler.php @@ -37,7 +37,7 @@ class WincacheHandler implements CacheInterface */ public function __construct(Cache $config) { - $this->prefix = $config->prefix ?: ''; + $this->prefix = (string) $config->prefix; } //-------------------------------------------------------------------- diff --git a/system/Config/View.php b/system/Config/View.php index 6788b75b18ed..b57d94167422 100644 --- a/system/Config/View.php +++ b/system/Config/View.php @@ -16,6 +16,14 @@ */ class View extends BaseConfig { + /** + * When false, the view method will clear the data between each + * call. + * + * @var boolean + */ + public $saveData = true; + /** * Parser Filters map a filter name with any PHP callable. When the * Parser prepares a variable for display, it will chain it diff --git a/system/HTTP/Request.php b/system/HTTP/Request.php index f0bfb02c1d72..9b4c16df877e 100644 --- a/system/HTTP/Request.php +++ b/system/HTTP/Request.php @@ -43,8 +43,6 @@ class Request extends Message implements MessageInterface, RequestInterface */ protected $uri; - //-------------------------------------------------------------------- - /** * Constructor. * @@ -68,8 +66,6 @@ public function __construct($config = null) } } - //-------------------------------------------------------------------- - /** * Validate an IP address * @@ -85,8 +81,6 @@ public function isValidIP(string $ip = null, string $which = null): bool return (new FormatRules())->valid_ip($ip, $which); } - //-------------------------------------------------------------------- - /** * Get the request method. * @@ -101,8 +95,6 @@ public function getMethod(bool $upper = false): string return ($upper) ? strtoupper($this->method) : strtolower($this->method); } - //-------------------------------------------------------------------- - /** * Sets the request method. Used when spoofing the request. * @@ -124,16 +116,16 @@ public function setMethod(string $method) * * @param string $method * - * @return self + * @return static */ public function withMethod($method) { - $clone = clone $this; + $request = clone $this; - return $clone->setMethod($method); - } + $request->method = $method; - //-------------------------------------------------------------------- + return $request; + } /** * Retrieves the URI instance. diff --git a/system/Validation/FormatRules.php b/system/Validation/FormatRules.php index 57b211be5efe..cefe16469b5b 100644 --- a/system/Validation/FormatRules.php +++ b/system/Validation/FormatRules.php @@ -44,7 +44,8 @@ public function alpha_space(?string $value = null): bool return true; } - return (bool) preg_match('/^[A-Z ]+$/i', $value); + // @see https://regex101.com/r/LhqHPO/1 + return (bool) preg_match('/\A[A-Z ]+\z/i', $value); } /** @@ -56,7 +57,8 @@ public function alpha_space(?string $value = null): bool */ public function alpha_dash(?string $str = null): bool { - return (bool) preg_match('/^[a-z0-9_-]+$/i', $str); + // @see https://regex101.com/r/XfVY3d/1 + return (bool) preg_match('/\A[a-z0-9_-]+\z/i', $str); } /** @@ -72,7 +74,8 @@ public function alpha_dash(?string $str = null): bool */ public function alpha_numeric_punct($str) { - return (bool) preg_match('/^[A-Z0-9 ~!#$%\&\*\-_+=|:.]+$/i', $str); + // @see https://regex101.com/r/6N8dDY/1 + return (bool) preg_match('/\A[A-Z0-9 ~!#$%\&\*\-_+=|:.]+\z/i', $str); } /** @@ -96,7 +99,8 @@ public function alpha_numeric(?string $str = null): bool */ public function alpha_numeric_space(?string $str = null): bool { - return (bool) preg_match('/^[A-Z0-9 ]+$/i', $str); + // @see https://regex101.com/r/0AZDME/1 + return (bool) preg_match('/\A[A-Z0-9 ]+\z/i', $str); } /** @@ -123,7 +127,8 @@ public function string($str = null): bool */ public function decimal(?string $str = null): bool { - return (bool) preg_match('/^[-+]?[0-9]{0,}\.?[0-9]+$/', $str); + // @see https://regex101.com/r/HULifl/1/ + return (bool) preg_match('/\A[-+]?[0-9]{0,}\.?[0-9]+\z/', $str); } /** @@ -147,7 +152,7 @@ public function hex(?string $str = null): bool */ public function integer(?string $str = null): bool { - return (bool) preg_match('/^[\-+]?[0-9]+$/', $str); + return (bool) preg_match('/\A[\-+]?[0-9]+\z/', $str); } /** @@ -181,7 +186,8 @@ public function is_natural_no_zero(?string $str = null): bool */ public function numeric(?string $str = null): bool { - return (bool) preg_match('/^[\-+]?[0-9]*\.?[0-9]+$/', $str); + // @see https://regex101.com/r/bb9wtr/1 + return (bool) preg_match('/\A[\-+]?[0-9]*\.?[0-9]+\z/', $str); } /** @@ -253,6 +259,7 @@ public function valid_json(string $str = null): bool */ public function valid_email(string $str = null): bool { + // @see https://regex101.com/r/wlJG1t/1/ if (function_exists('idn_to_ascii') && defined('INTL_IDNA_VARIANT_UTS46') && preg_match('#\A([^@]+)@(.+)\z#', $str, $matches)) { $str = $matches[1] . '@' . idn_to_ascii($matches[2], 0, INTL_IDNA_VARIANT_UTS46); diff --git a/system/View/RendererInterface.php b/system/View/RendererInterface.php index 0128d1c8f696..c8a3b7a6ed1e 100644 --- a/system/View/RendererInterface.php +++ b/system/View/RendererInterface.php @@ -26,9 +26,7 @@ interface RendererInterface * @param array $options Reserved for 3rd-party uses since * it might be needed to pass additional info * to other template engines. - * @param boolean $saveData If true, will save data for use with any other calls, - * if false, will clean the data after displaying the view, - * if not specified, use the config setting. + * @param boolean $saveData Whether to save data for subsequent calls * * @return string */ @@ -44,9 +42,7 @@ public function render(string $view, array $options = null, bool $saveData = fal * @param array $options Reserved for 3rd-party uses since * it might be needed to pass additional info * to other template engines. - * @param boolean $saveData If true, will save data for use with any other calls, - * if false, will clean the data after displaying the view, - * if not specified, use the config setting. + * @param boolean $saveData Whether to save data for subsequent calls * * @return string */ diff --git a/system/View/View.php b/system/View/View.php index 3d98fae92662..a9f190afefdb 100644 --- a/system/View/View.php +++ b/system/View/View.php @@ -142,7 +142,7 @@ public function __construct(ViewConfig $config, string $viewPath = null, FileLoc $this->loader = $loader ?? Services::locator(); $this->logger = $logger ?? Services::logger(); $this->debug = $debug ?? CI_DEBUG; - $this->saveData = $config->saveData ?? null; + $this->saveData = (bool) $config->saveData; } //-------------------------------------------------------------------- @@ -152,12 +152,16 @@ public function __construct(ViewConfig $config, string $viewPath = null, FileLoc * data that has already been set. * * Valid $options: - * - cache number of seconds to cache for - * - cache_name Name to use for cache + * - cache Number of seconds to cache for + * - cache_name Name to use for cache * - * @param string $view - * @param array|null $options - * @param boolean|null $saveData + * @param string $view File name of the view source + * @param array|null $options Reserved for 3rd-party uses since + * it might be needed to pass additional info + * to other template engines. + * @param boolean|null $saveData If true, saves data for subsequent calls, + * if false, cleans the data after displaying, + * if null, uses the config setting. * * @return string */ @@ -172,7 +176,7 @@ public function render(string $view, array $options = null, bool $saveData = nul $fileExt = pathinfo($view, PATHINFO_EXTENSION); $realPath = empty($fileExt) ? $view . '.php' : $view; // allow Views as .html, .tpl, etc (from CI3) $this->renderVars['view'] = $realPath; - $this->renderVars['options'] = $options; + $this->renderVars['options'] = $options ?? []; // Was it cached? if (isset($this->renderVars['options']['cache'])) @@ -272,13 +276,13 @@ public function render(string $view, array $options = null, bool $saveData = nul * data that has already been set. * Cache does not apply, because there is no "key". * - * @param string $view The view contents - * @param array $options Reserved for 3rd-party uses since - * it might be needed to pass additional info - * to other template engines. - * @param boolean $saveData If true, will save data for use with any other calls, - * if false, will clean the data after displaying the view, - * if not specified, use the config setting. + * @param string $view The view contents + * @param array|null $options Reserved for 3rd-party uses since + * it might be needed to pass additional info + * to other template engines. + * @param boolean|null $saveData If true, saves data for subsequent calls, + * if false, cleans the data after displaying, + * if null, uses the config setting. * * @return string */ diff --git a/tests/system/Cache/Handlers/FileHandlerTest.php b/tests/system/Cache/Handlers/FileHandlerTest.php index bb77935abf10..232b3e71b5f9 100644 --- a/tests/system/Cache/Handlers/FileHandlerTest.php +++ b/tests/system/Cache/Handlers/FileHandlerTest.php @@ -26,13 +26,18 @@ protected function setUp(): void { parent::setUp(); - //Initialize path - $this->config = new \Config\Cache(); - $this->config->storePath .= self::$directory; + if (! function_exists('octal_permissions')) + { + helper('filesystem'); + } + + // Initialize path + $this->config = new \Config\Cache(); + $this->config->file['storePath'] .= self::$directory; - if (! is_dir($this->config->storePath)) + if (! is_dir($this->config->file['storePath'])) { - mkdir($this->config->storePath, 0777, true); + mkdir($this->config->file['storePath'], 0777, true); } $this->fileHandler = new FileHandler($this->config); @@ -41,20 +46,20 @@ protected function setUp(): void public function tearDown(): void { - if (is_dir($this->config->storePath)) + if (is_dir($this->config->file['storePath'])) { - chmod($this->config->storePath, 0777); + chmod($this->config->file['storePath'], 0777); foreach (self::getKeyArray() as $key) { - if (is_file($this->config->storePath . DIRECTORY_SEPARATOR . $key)) + if (is_file($this->config->file['storePath'] . DIRECTORY_SEPARATOR . $key)) { - chmod($this->config->storePath . DIRECTORY_SEPARATOR . $key, 0777); - unlink($this->config->storePath . DIRECTORY_SEPARATOR . $key); + chmod($this->config->file['storePath'] . DIRECTORY_SEPARATOR . $key, 0777); + unlink($this->config->file['storePath'] . DIRECTORY_SEPARATOR . $key); } } - rmdir($this->config->storePath); + rmdir($this->config->file['storePath']); } } @@ -67,15 +72,15 @@ public function testNewWithNonWritablePath() { $this->expectException('CodeIgniter\Cache\Exceptions\CacheException'); - chmod($this->config->storePath, 0444); + chmod($this->config->file['storePath'], 0444); new FileHandler($this->config); } public function testSetDefaultPath() { - //Initialize path - $config = new \Config\Cache(); - $config->storePath = null; + // Initialize path + $config = new \Config\Cache(); + $config->file['storePath'] = null; $this->fileHandler = new FileHandler($config); $this->fileHandler->initialize(); @@ -98,7 +103,7 @@ public function testSave() { $this->assertTrue($this->fileHandler->save(self::$key1, 'value')); - chmod($this->config->storePath, 0444); + chmod($this->config->file['storePath'], 0444); $this->assertFalse($this->fileHandler->save(self::$key2, 'value')); } @@ -173,6 +178,36 @@ public function testIsSupported() $this->assertTrue($this->fileHandler->isSupported()); } + /** + * @dataProvider modeProvider + */ + public function testSaveMode($int, $string) + { + // Initialize mode + $config = new \Config\Cache(); + $config->file['mode'] = $int; + + $this->fileHandler = new FileHandler($config); + $this->fileHandler->initialize(); + + $this->fileHandler->save(self::$key1, 'value'); + + $file = $config->file['storePath'] . DIRECTORY_SEPARATOR . self::$key1; + $mode = octal_permissions(fileperms($file)); + + $this->assertEquals($string, $mode); + } + + public function modeProvider() + { + return [ + [0640, '640'], + [0600, '600'], + [0660, '660'], + [0777, '777'], + ]; + } + //-------------------------------------------------------------------- public function testFileHandler() @@ -200,8 +235,8 @@ final class BaseTestFileHandler extends FileHandler public function __construct() { - $this->config = new \Config\Cache(); - $this->config->storePath .= self::$directory; + $this->config = new \Config\Cache(); + $this->config->file['storePath'] .= self::$directory; parent::__construct($this->config); } diff --git a/tests/system/Validation/FormatRulesTest.php b/tests/system/Validation/FormatRulesTest.php index d7b171fe098a..56145c703201 100644 --- a/tests/system/Validation/FormatRulesTest.php +++ b/tests/system/Validation/FormatRulesTest.php @@ -424,6 +424,10 @@ public function alphaSpaceProvider() FormatRulesTest::ALPHABET . ' ', true, ], + [ + FormatRulesTest::ALPHABET . "\n", + false, + ], [ FormatRulesTest::ALPHABET . '1', false, @@ -506,6 +510,10 @@ public function alphaNumericPunctProvider() FormatRulesTest::ALPHANUMERIC . '`', false, ], + [ + FormatRulesTest::ALPHANUMERIC . "\n", + false, + ], [ FormatRulesTest::ALPHANUMERIC . '@', false, @@ -599,6 +607,10 @@ public function alphaNumericSpaceProvider() ' ' . FormatRulesTest::ALPHANUMERIC . '-', false, ], + [ + ' ' . FormatRulesTest::ALPHANUMERIC . "\n", + false, + ], [ null, false, @@ -636,6 +648,10 @@ public function alphaDashProvider() FormatRulesTest::ALPHANUMERIC . '-\ ', false, ], + [ + FormatRulesTest::ALPHANUMERIC . "-\n", + false, + ], [ null, false, @@ -722,6 +738,10 @@ public function numericProvider() '+42', true, ], + [ + "+42\n", + false, + ], [ '123a', false, @@ -771,6 +791,10 @@ public function integerProvider() '-1', true, ], + [ + "+42\n", + false, + ], [ '123a', false, @@ -824,6 +848,10 @@ public function decimalProvider() '0', true, ], + [ + "0\n", + false, + ], [ '1.0a', false, diff --git a/user_guide_src/source/changelogs/v4.0.5.rst b/user_guide_src/source/changelogs/v4.0.5.rst index 9a55cf6d6e60..90a8ac7ab62e 100644 --- a/user_guide_src/source/changelogs/v4.0.5.rst +++ b/user_guide_src/source/changelogs/v4.0.5.rst @@ -14,6 +14,7 @@ Enhancements: - Support for setting SameSite attribute on Session and CSRF cookies has been added. For security and compatibility with latest browser versions, the default setting is ``Lax``. - Guessing file extensions from mime type in ``Config\Mimes::guessExtensionFromType`` now only reverse searches the ``$mimes`` array if no extension is proposed (i.e., usually not for uploaded files). - The getter functions for file extensions of uploaded files now have different fallback values (``$this->getClientExtension()`` for ``UploadedFile->getExtension()`` and ``''`` for ``UploadedFile->guessExtension()``). This is a security fix and makes the process less dependent on the client. +- The Cache ``FileHandler`` now allows setting the file permissions mode via ``Config\Cache`` Changes: @@ -44,3 +45,4 @@ Deprecations: - Deprecated ``CodeIgniter\Config\Config`` in favor of ``CodeIgniter\Config\Factories::config()`` - Deprecated ``CodeIgniter\HTTP\Message::getHeader`` in favor of ``header()`` to prepare for PSR-7 - Deprecated ``CodeIgniter\HTTP\Message::getHeaders`` in favor of ``headers()`` to prepare for PSR-7 +- Deprecated ``Config\Cache::$storePath`` in favor of ``Config\Cache::$file['storePath']`` diff --git a/user_guide_src/source/libraries/caching.rst b/user_guide_src/source/libraries/caching.rst index 368a34498c09..8e0f38d3a6e0 100644 --- a/user_guide_src/source/libraries/caching.rst +++ b/user_guide_src/source/libraries/caching.rst @@ -57,11 +57,11 @@ more complex, multi-server setups. **$prefix** If you have more than one application using the same cache storage, you can add a custom prefix -here that is prepended to all key names. +string here that is prepended to all key names. -**$path** +**$file** -This is used by the ``file`` handler to show where it should save the cache files to. +This is an array of settings specific to the ``File`` handler to determine how it should save the cache files. **$memcached** @@ -212,7 +212,8 @@ File-based Caching Unlike caching from the Output Class, the driver file-based caching allows for pieces of view files to be cached. Use this with care, and make sure to benchmark your application, as a point can come where disk -I/O will negate positive gains by caching. This requires a writable cache directory to be really writable (0777). +I/O will negate positive gains by caching. This requires a cache +directory to be really writable by the application. ================= Memcached Caching diff --git a/user_guide_src/source/outgoing/view_renderer.rst b/user_guide_src/source/outgoing/view_renderer.rst index a55d848accae..e77413cbb97a 100644 --- a/user_guide_src/source/outgoing/view_renderer.rst +++ b/user_guide_src/source/outgoing/view_renderer.rst @@ -102,6 +102,9 @@ Several options can be passed to the ``render()`` or ``renderString()`` methods: ignored for renderString() - ``saveData`` - true if the view data parameters should be retained for subsequent calls +.. note:: ``saveData`` as defined by the interface must be a boolean, but implementing + classes (like ``View`` below) may extend this to include ``null`` values. + Class Reference *************** @@ -110,9 +113,9 @@ Class Reference .. php:method:: render($view[, $options[, $saveData=false]]) :noindex: - :param string $view: File name of the view source - :param array $options: Array of options, as key/value pairs - :param boolean $saveData: If true, will save data for use with any other calls, if false, will clean the data after rendering the view. + :param string $view: File name of the view source + :param array $options: Array of options, as key/value pairs + :param boolean|null $saveData: If true, will save data for use with any other calls. If false, will clean the data after rendering the view. If null, uses the config setting. :returns: The rendered text for the chosen view :rtype: string @@ -123,9 +126,9 @@ Class Reference .. php:method:: renderString($view[, $options[, $saveData=false]]) :noindex: - :param string $view: Contents of the view to render, for instance content retrieved from a database - :param array $options: Array of options, as key/value pairs - :param boolean $saveData: If true, will save data for use with any other calls, if false, will clean the data after rendering the view. + :param string $view: Contents of the view to render, for instance content retrieved from a database + :param array $options: Array of options, as key/value pairs + :param boolean|null $saveData: If true, will save data for use with any other calls. If false, will clean the data after rendering the view. If null, uses the config setting. :returns: The rendered text for the chosen view :rtype: string