diff --git a/.gitignore b/.gitignore index 32c14d01b59b..f30743491a5a 100644 --- a/.gitignore +++ b/.gitignore @@ -126,4 +126,4 @@ nb-configuration.xml .vscode/ /results/ -/phpunit.xml +/phpunit*.xml diff --git a/application/Config/App.php b/application/Config/App.php index 94a2bab44abf..9dc72bf4d467 100644 --- a/application/Config/App.php +++ b/application/Config/App.php @@ -215,7 +215,7 @@ class App extends BaseConfig | Reverse Proxy IPs |-------------------------------------------------------------------------- | - | If your getServer is behind a reverse proxy, you must whitelist the proxy + | If your server is behind a reverse proxy, you must whitelist the proxy | IP addresses from which CodeIgniter should trust headers such as | HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify | the visitor's IP address. @@ -240,11 +240,13 @@ class App extends BaseConfig | CSRFCookieName = The cookie name | CSRFExpire = The number in seconds the token should expire. | CSRFRegenerate = Regenerate token on every submission + | CSRFRedirect = Redirect to previous page with error on failure */ public $CSRFTokenName = 'csrf_test_name'; public $CSRFCookieName = 'csrf_cookie_name'; public $CSRFExpire = 7200; public $CSRFRegenerate = true; + public $CSRFRedirect = true; /* |-------------------------------------------------------------------------- diff --git a/application/Config/DocTypes.php b/application/Config/DocTypes.php index 37830984c31f..38ceaedfc905 100755 --- a/application/Config/DocTypes.php +++ b/application/Config/DocTypes.php @@ -8,7 +8,7 @@ class DocTypes { - static $list = + public $list = [ 'xhtml11' => '', 'xhtml1-strict' => '', diff --git a/application/Controllers/Checks.php b/application/Controllers/Checks.php deleted file mode 100644 index 5fbd34cfb412..000000000000 --- a/application/Controllers/Checks.php +++ /dev/null @@ -1,457 +0,0 @@ -start(); - } - - public function forge() - { - echo '
'; - var_dump($this->response->getHeaderLine('content-type')); - } - - public function model() - { - $model = new class() extends Model { - protected $table = 'job'; - }; - - $results = $model->findAll(); - - $developer = $model->findWhere('name', 'Developer'); - - $politician = $model->find(3); - - dd($politician); - - } - - public function curl() - { - $client = Services::curlrequest([ - 'debug' => true, - 'follow_redirects' => true, - 'json' => ['foo' => 'bar'] - ]); - - echo ''; - $response = $client->request('PUT', 'http://ci4.dev/checks/catch'); - echo $response->getBody(); - } - - // Simply echos back what's given in the body. - public function catch() - { - $body = print_r($this->request->getRawInput(), true); - echo $body; - } - - public function redirect() - { - return redirect('/checks/model'); - } - - public function image() - { - $info = Services::image('imagick') - ->withFile("/Users/kilishan/Documents/BobHeader.jpg") - ->getFile() - ->getProperties(true); - - dd(ENVIRONMENT); - - $images = Services::image('imagick') - ->getVersion(); -// ->withFile("/Users/kilishan/Documents/BobHeader.jpg") -// ->resize(500, 100, true) -// ->crop(200, 75, 20, 0, false) -// ->rotate(90) -// ->save('/Users/kilishan/temp.jpg'); - -// $images = Services::image('imagick') -// ->withFile("/Users/kilishan/Documents/BobHeader.jpg") -// ->fit(500, 100, 'bottom-left') -// ->text('Bob is Back!', [ -// 'fontPath' => '/Users/kilishan/Downloads/Calibri.ttf', -// 'fontSize' => 40, -// 'padding' => 0, -// 'opacity' => 0.5, -// 'vAlign' => 'top', -// 'hAlign' => 'right', -// 'withShadow' => true, -// ]) -// ->save('/Users/kilishan/temp.jpg', 100); - - - ddd($images); - } - - public function time() - { - $time = new Time(); - - echo($time); - echo '
'; - echo Time::now(); - echo '
'; - echo Time::parse('First Monday of December'); - echo '
'; - - $time = new Time('Next Monday'); - die($time); - } - - public function csp() - { -// $this->response->CSP->reportOnly(true); - $this->response->CSP->setDefaultSrc(base_url()); - $this->response->CSP->addStyleSrc('unsafe-inline'); - $this->response->CSP->addStyleSrc('https://maxcdn.bootstrapcdn.com'); - - echo <<- - - - - - - - - -EOF; - - } - - public function upload() - { - if ($this->request->getMethod() == 'post') - { - $this->validate([ - 'avatar' => 'uploaded[avatar]|ext_in[avatar,png,jpg,jpeg,gif]' - ]); - - /** - * @var \CodeIgniter\HTTP\Files\UploadedFile - */ - $file = $this->request->getFile('avatar'); - - echo "Name: {$file->getName()}
"; - echo "Temp Name: {$file->getTempName()}
"; - echo "Original Name: {$file->getClientName()}
"; - echo "Random Name: {$file->getRandomName()}
"; - echo "Extension: {$file->getExtension()}
"; - echo "Client Extension: {$file->getClientExtension()}
"; - echo "Guessed Extension: {$file->guessExtension()}
"; - echo "MimeType: {$file->getMimeType()}
"; - echo "IsValid: {$file->isValid()}
"; - echo "Size (b): {$file->getSize()}
"; - echo "Size (kb): {$file->getSize('kb')}
"; - echo "Size (mb): {$file->getSize('mb')}
"; - echo "Size (mb): {$file->getSize('mb')}
"; - echo "Path: {$file->getPath()}
"; - echo "RealPath: {$file->getRealPath()}
"; - echo "Filename: {$file->getFilename()}
"; - echo "Basename: {$file->getBasename()}
"; - echo "Pathname: {$file->getPathname()}
"; - echo "Permissions: {$file->getPerms()}
"; - echo "Inode: {$file->getInode()}
"; - echo "Owner: {$file->getOwner()}
"; - echo "Group: {$file->getGroup()}
"; - echo "ATime: {$file->getATime()}
"; - echo "MTime: {$file->getMTime()}
"; - echo "CTime: {$file->getCTime()}
"; - - dd($file); - } - - echo <<- - - - - - - - -EOF; -; - } - - public function parser() - { - $this->parser = Services::parser(); - } - - public function error() - { - throw new \RuntimeException('Oops!', 403); - } - -} diff --git a/application/Filters/CSRF.php b/application/Filters/CSRF.php index 46f1582300a8..221ae1316497 100644 --- a/application/Filters/CSRF.php +++ b/application/Filters/CSRF.php @@ -3,6 +3,7 @@ use CodeIgniter\Filters\FilterInterface; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; +use CodeIgniter\Security\Exceptions\SecurityException; use Config\Services; class CSRF implements FilterInterface @@ -30,7 +31,19 @@ public function before(RequestInterface $request) $security = Services::security(); - $security->CSRFVerify($request); + try + { + $security->CSRFVerify($request); + } + catch (SecurityException $e) + { + if (config('App')->CSRFRedirect && ! $request->isAJAX()) + { + return redirect()->back()->with('error', $e->getMessage()); + } + + throw $e; + } } //-------------------------------------------------------------------- diff --git a/application/Filters/DebugToolbar.php b/application/Filters/DebugToolbar.php index 945108013c47..878a52387923 100644 --- a/application/Filters/DebugToolbar.php +++ b/application/Filters/DebugToolbar.php @@ -37,12 +37,11 @@ public function after(RequestInterface $request, ResponseInterface $response) { global $app; - $toolbar = Services::toolbar(new App()); + $toolbar = Services::toolbar(config(App::class)); $stats = $app->getPerformanceStats(); $data = $toolbar->run( $stats['startTime'], $stats['totalTime'], - $stats['startMemory'], $request, $response ); diff --git a/system/CLI/CLI.php b/system/CLI/CLI.php index 0178acff59b6..d27ccf1e03e6 100644 --- a/system/CLI/CLI.php +++ b/system/CLI/CLI.php @@ -50,14 +50,14 @@ * Some of the code in this class is Windows-specific, and not * possible to test using travis-ci. It has been phpunit-annotated * to prevent messing up code coverage. - * + * * Some of the methods require keyboard input, and are not unit-testable * as a result: input() and prompt(). * validate() is internal, and not testable if prompt() isn't. * The wait() method is mostly testable, as long as you don't give it - * an argument of "0". + * an argument of "0". * These have been flagged to ignore for code coverage purposes. - * + * * @package CodeIgniter\HTTP */ class CLI @@ -165,7 +165,7 @@ public static function init() * * @param string $prefix * @return string - * + * * @codeCoverageIgnore */ public static function input(string $prefix = null): string @@ -217,7 +217,7 @@ public static function prompt($field, $options = null, $validation = null): stri $default = $options; } - if (is_array($options) && count($options)) + if (is_array($options) && $options) { $opts = $options; $extra_output_default = static::color($opts[0], 'white'); @@ -379,7 +379,7 @@ public static function wait(int $seconds, bool $countdown = false) */ public static function isWindows() { - return 'win' === strtolower(substr(php_uname("s"), 0, 3)); + return stripos(PHP_OS, 'WIN') === 0; } //-------------------------------------------------------------------- @@ -652,7 +652,7 @@ protected static function parseCommandLine() $value = null; // if there is a following segment, and it doesn't start with a dash, it's a value. - if (isset($_SERVER['argv'][$i + 1]) && mb_substr($_SERVER['argv'][$i + 1], 0, 1) != '-') + if (isset($_SERVER['argv'][$i + 1]) && mb_strpos($_SERVER['argv'][$i + 1], '-') !== 0) { $value = $_SERVER['argv'][$i + 1]; $i ++; diff --git a/system/CLI/CommandRunner.php b/system/CLI/CommandRunner.php index e0b08c558f26..46c2e18f68ff 100644 --- a/system/CLI/CommandRunner.php +++ b/system/CLI/CommandRunner.php @@ -39,7 +39,6 @@ class CommandRunner extends Controller { - /** * Stores the info about found Commands. * @@ -47,6 +46,11 @@ class CommandRunner extends Controller */ protected $commands = []; + /** + * @var \CodeIgniter\Log\Logger + */ + protected $logger; + //-------------------------------------------------------------------- /** diff --git a/system/CLI/Console.php b/system/CLI/Console.php index 3a73e05dc911..7daccf9b0137 100644 --- a/system/CLI/Console.php +++ b/system/CLI/Console.php @@ -63,15 +63,20 @@ public function __construct(CodeIgniter $app) /** * Runs the current command discovered on the CLI. + * + * @param bool $useSafeOutput + * + * @return \CodeIgniter\HTTP\RequestInterface|\CodeIgniter\HTTP\Response|\CodeIgniter\HTTP\ResponseInterface|mixed + * @throws \CodeIgniter\HTTP\RedirectException */ - public function run() + public function run(bool $useSafeOutput = false) { $path = CLI::getURI() ?: 'list'; // Set the path for the application to route to. $this->app->setPath("ci{$path}"); - return $this->app->run(); + return $this->app->useSafeOutput($useSafeOutput)->run(); } //-------------------------------------------------------------------- diff --git a/system/Cache/Handlers/MemcachedHandler.php b/system/Cache/Handlers/MemcachedHandler.php index cd2405489fb1..36513248f6fe 100644 --- a/system/Cache/Handlers/MemcachedHandler.php +++ b/system/Cache/Handlers/MemcachedHandler.php @@ -287,7 +287,8 @@ public function getMetaData(string $key) $stored = $this->memcached->get($key); - if (count($stored) !== 3) + // if not an array, don't try to count for PHP7.2 + if (! is_array($stored) || count($stored) !== 3) { return FALSE; } diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 412fa7447d7a..5b7524635791 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -37,6 +37,7 @@ */ use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\Request; +use CodeIgniter\HTTP\ResponseInterface; use Config\Services; use Config\Cache; use CodeIgniter\HTTP\URI; @@ -66,12 +67,6 @@ class CodeIgniter */ protected $startTime; - /** - * Amount of memory at app start. - * @var int - */ - protected $startMemory; - /** * Total app execution time * @var float @@ -138,12 +133,18 @@ class CodeIgniter */ protected $path; + /** + * Should the Response instance "pretend" + * to keep from setting headers/cookies/etc + * @var bool + */ + protected $useSafeOutput = false; + //-------------------------------------------------------------------- public function __construct($config) { $this->startTime = microtime(true); - $this->startMemory = memory_get_usage(true); $this->config = $config; } @@ -202,12 +203,23 @@ public function run(RouteCollectionInterface $routes = null, bool $returnRespons // Check for a cached page. Execution will stop // if the page has been cached. $cacheConfig = new Cache(); - $this->displayCache($cacheConfig); + $response = $this->displayCache($cacheConfig); + if ($response instanceof ResponseInterface) + { + if ($returnResponse) + { + return $response; + } + + $this->response->pretend($this->useSafeOutput)->send(); + $this->callExit(EXIT_SUCCESS); + } try { return $this->handleRequest($routes, $cacheConfig, $returnResponse); - } catch (Router\RedirectException $e) + } + catch (Router\RedirectException $e) { $logger = Services::logger(); $logger->info('REDIRECTED ROUTE at ' . $e->getMessage()); @@ -225,6 +237,24 @@ public function run(RouteCollectionInterface $routes = null, bool $returnRespons //-------------------------------------------------------------------- + /** + * Set our Response instance to "pretend" mode so that things like + * cookies and headers are not actually sent, allowing PHP 7.2+ to + * not complain when ini_set() function is used. + * + * @param bool $safe + * + * @return $this + */ + public function useSafeOutput(bool $safe = true) + { + $this->useSafeOutput = $safe; + + return $this; + } + + //-------------------------------------------------------------------- + /** * Handles the main request logic and fires the controller. * @@ -232,6 +262,7 @@ public function run(RouteCollectionInterface $routes = null, bool $returnRespons * @param $cacheConfig * @param bool $returnResponse * + * @return \CodeIgniter\HTTP\RequestInterface|\CodeIgniter\HTTP\Response|\CodeIgniter\HTTP\ResponseInterface|mixed * @throws \CodeIgniter\Filters\Exceptions\FilterException */ protected function handleRequest(RouteCollectionInterface $routes = null, $cacheConfig, bool $returnResponse = false) @@ -242,7 +273,11 @@ protected function handleRequest(RouteCollectionInterface $routes = null, $cache $filters = Services::filters(); $uri = $this->request instanceof CLIRequest ? $this->request->getPath() : $this->request->uri->getPath(); - $filters->run($uri, 'before'); + $possibleRedirect = $filters->run($uri, 'before'); + if($possibleRedirect instanceof RedirectResponse) + { + return $possibleRedirect; + } $returned = $this->startController(); @@ -495,8 +530,9 @@ public function displayCache($config) } $output = $this->displayPerformanceMetrics($output); - $this->response->setBody($output)->send(); - $this->callExit(EXIT_SUCCESS); + $this->response->setBody($output); + + return $this->response; }; } @@ -549,7 +585,6 @@ public function getPerformanceStats() return [ 'startTime' => $this->startTime, 'totalTime' => $this->totalTime, - 'startMemory' => $this->startMemory ]; } @@ -727,7 +762,8 @@ protected function startController() */ protected function createController() { - $class = new $this->controller($this->request, $this->response); + $class = new $this->controller(); + $class->initController($this->request, $this->response, Services::logger()); $this->benchmark->stop('controller_constructor'); @@ -917,7 +953,7 @@ public function spoofRequestMethod() */ protected function sendResponse() { - $this->response->send(); + $this->response->pretend($this->useSafeOutput)->send(); } //-------------------------------------------------------------------- diff --git a/system/Commands/Database/MigrateStatus.php b/system/Commands/Database/MigrateStatus.php index 364dc422c90e..b51dfb7a3a80 100644 --- a/system/Commands/Database/MigrateStatus.php +++ b/system/Commands/Database/MigrateStatus.php @@ -93,6 +93,12 @@ class MigrateStatus extends BaseCommand '-g' => 'Set database group', ]; + protected $ignoredNamespaces = [ + 'CodeIgniter', + 'Config', + 'Tests\Support' + ]; + /** * Displays a list of all migrations and whether they've been run or not. * @@ -114,25 +120,25 @@ public function run(array $params = []) // Loop for all $namespaces foreach ($namespaces as $namespace => $path) { + if (in_array($namespace, $this->ignoredNamespaces)) + { + continue; + } $runner->setNamespace($namespace); $migrations = $runner->findMigrations(); $history = $runner->getHistory(); + CLI::write($namespace); + if (empty($migrations)) { - CLI::error("$namespace: " . lang('Migrations.noneFound')); + CLI::error(lang('Migrations.noneFound')); continue; } ksort($migrations); - CLI::newLine(1); - - CLI::write(lang('Migrations.historyFor') . "$namespace: ", 'green'); - - CLI::newLine(1); - $max = 0; foreach ($migrations as $version => $migration) { @@ -142,7 +148,7 @@ public function run(array $params = []) $max = max($max, strlen($file)); } - CLI::write(str_pad(lang('Migrations.filename'), $max + 6) . lang('Migrations.on'), 'yellow'); + CLI::write(' '. str_pad(lang('Migrations.filename'), $max + 4) . lang('Migrations.on'), 'yellow'); foreach ($migrations as $version => $migration) @@ -157,7 +163,7 @@ public function run(array $params = []) $date = date("Y-m-d H:i:s", $row['time']); } - CLI::write(str_pad($migration->name, $max + 6) . ($date ? $date : '---')); + CLI::write(str_pad(' '.$migration->name, $max + 6) . ($date ? $date : '---')); } } } diff --git a/system/Commands/ListCommands.php b/system/Commands/ListCommands.php index 121c5a26c8f3..d17c349cfe00 100644 --- a/system/Commands/ListCommands.php +++ b/system/Commands/ListCommands.php @@ -125,77 +125,68 @@ public function run(array $params) */ protected function describeCommands(array $commands = []) { - arsort($commands); + ksort($commands); - $names = array_keys($commands); - $descs = array_column($commands, 'description'); - $groups = array_column($commands, 'group'); - $lastGroup = ''; + // Sort into buckets by group + $sorted = []; + $maxTitleLength = 0; - // Pad each item to the same length - $names = $this->padArray($names, 2, 2); - $countNames = count($names); - for ($i = 0; $i < $countNames; $i ++ ) + foreach ($commands as $title => $command) { - $lastGroup = $this->describeGroup($groups[$i], $lastGroup); - - $out = CLI::color($names[$i], 'yellow'); - - if (isset($descs[$i])) + if (! isset($sorted[$command['group']])) { - $out .= CLI::wrap($descs[$i], 125, strlen($names[$i])); + $sorted[$command['group']] = []; } - CLI::write($out); + $sorted[$command['group']][$title] = $command; + + $maxTitleLength = max($maxTitleLength, strlen($title)); } - } - //-------------------------------------------------------------------- + ksort($sorted); - /** - * Outputs the description, if necessary. - * - * @param string $new - * @param string $old - * - * @return string - */ - protected function describeGroup(string $new, string $old) - { - if ($new == $old) + // Display it all... + foreach ($sorted as $group => $items) { - return $old; - } + CLI::newLine(); + CLI::write($group); - CLI::newLine(); - CLI::write($new); + foreach ($items as $title => $item) + { + $title = $this->padTitle($title, $maxTitleLength, 2, 2); + + $out = CLI::color($title, 'yellow'); + + if (isset($item['description'])) + { + $out .= CLI::wrap($item['description'], 125, strlen($title)); + } - return $new; + CLI::write($out); + } + } } //-------------------------------------------------------------------- /** - * Returns a new array where all of the string elements have - * been padding with trailing spaces to be the same length. + * Pads our string out so that all titles are the same length to nicely line up descriptions. * - * @param array $array - * @param int $extra // How many extra spaces to add at the end - * @param int $indent + * @param string $item + * @param $max + * @param int $extra // How many extra spaces to add at the end + * @param int $indent * * @return array */ - protected function padArray($array, $extra = 2, $indent = 0) + protected function padTitle(string $item, $max, $extra = 2, $indent = 0) { - $max = max(array_map('strlen', $array)) + $extra + $indent; + $max += $extra + $indent; - foreach ($array as &$item) - { - $item = str_repeat(' ', $indent) . $item; - $item = str_pad($item, $max); - } + $item = str_repeat(' ', $indent) . $item; + $item = str_pad($item, $max); - return $array; + return $item; } //-------------------------------------------------------------------- diff --git a/system/Commands/Server/rewrite.php b/system/Commands/Server/rewrite.php index a7af4d421149..aedc5deef350 100644 --- a/system/Commands/Server/rewrite.php +++ b/system/Commands/Server/rewrite.php @@ -6,7 +6,7 @@ * development server based around PHP's built-in development * server. This file simply tries to mimic Apache's mod_rewrite * functionality so the site will operate as normal. - * + * */ // @codeCoverageIgnoreStart // Avoid this file run when listing commands @@ -22,7 +22,7 @@ $uri = urldecode(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)); // Front Controller path - expected to be in the default folder -$fcpath = realpath(__DIR__ . '/../../../public') . DIRECTORY_SEPARATOR; +$fcpath = $_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR; // Full path $path = $fcpath . ltrim($uri, '/'); diff --git a/system/Common.php b/system/Common.php index 72cbe76ed969..62bd6032826d 100644 --- a/system/Common.php +++ b/system/Common.php @@ -270,8 +270,16 @@ function esc($data, $context = 'html', $encoding = null) $method = 'escape' . ucfirst($context); } - // @todo Optimize this to only load a single instance during page request. - $escaper = new \Zend\Escaper\Escaper($encoding); + static $escaper; + if (! $escaper) + { + $escaper = new \Zend\Escaper\Escaper($encoding); + } + + if ($encoding && $escaper->getEncoding() !== $encoding) + { + $escaper = new \Zend\Escaper\Escaper($encoding); + } $data = $escaper->$method($data); } @@ -605,7 +613,7 @@ function helper($filenames) */ function app_timezone() { - $config = new \Config\App(); + $config = config(\Config\App::class); return $config->appTimezone; } @@ -626,7 +634,7 @@ function app_timezone() */ function csrf_token() { - $config = new \Config\App(); + $config = config(\Config\App::class); return $config->CSRFTokenName; } @@ -762,7 +770,7 @@ function old(string $key, $default = null, $escape = 'html') } // If the result was serialized array or string, then unserialize it for use... - if (substr($value, 0, 2) == 'a:' || substr($value, 0, 2) == 's:') + if (strpos($value, 'a:') === 0 || strpos($value, 's:') === 0) { $value = unserialize($value); } @@ -919,7 +927,7 @@ function is_really_writable($file) */ function slash_item($item) { - $config = new \Config\App(); + $config = config(\Config\App::class); $configItem = $config->{$item}; if ( ! isset($configItem) || empty(trim($configItem))) diff --git a/system/Config/BaseService.php b/system/Config/BaseService.php index b8ffa3b27c07..6fda4d33e6ab 100644 --- a/system/Config/BaseService.php +++ b/system/Config/BaseService.php @@ -202,7 +202,7 @@ protected static function discoverServices(string $name, array $arguments) } } - if (! count(static::$services)) + if (! static::$services) { return; } diff --git a/system/Config/Config.php b/system/Config/Config.php index 0cf79dd3e744..8cef1942d5be 100644 --- a/system/Config/Config.php +++ b/system/Config/Config.php @@ -70,8 +70,6 @@ public static function get(string $name, bool $getShared = true) $class = substr($name, $pos + 1); } - $class = strtolower($class); - if (! $getShared) { return self::createClass($name); diff --git a/system/Config/DotEnv.php b/system/Config/DotEnv.php index c3684317e5c9..0ac1f24c9c29 100644 --- a/system/Config/DotEnv.php +++ b/system/Config/DotEnv.php @@ -109,6 +109,7 @@ public function load() } } + return true; // for success } //-------------------------------------------------------------------- diff --git a/system/Config/Services.php b/system/Config/Services.php index 90f914c44974..68edf67bbedc 100644 --- a/system/Config/Services.php +++ b/system/Config/Services.php @@ -35,11 +35,10 @@ * @since Version 3.0.0 * @filesource */ +use Config\App; use CodeIgniter\Database\ConnectionInterface; use CodeIgniter\Database\MigrationRunner; -use CodeIgniter\HTTP\URI; use CodeIgniter\View\RendererInterface; -use Config\App; /** * Services Configuration file. @@ -124,7 +123,7 @@ public static function clirequest(\Config\App $config = null, $getShared = true) if (! is_object($config)) { - $config = new \Config\App(); + $config = config(App::class); } return new \CodeIgniter\HTTP\CLIRequest($config); @@ -143,12 +142,7 @@ public static function clirequest(\Config\App $config = null, $getShared = true) * * @return \CodeIgniter\HTTP\CURLRequest */ - public static function curlrequest( - array $options = [], - $response = null, - \Config\App $config = null, - $getShared = true - ) { + public static function curlrequest(array $options = [], $response = null, \Config\App $config = null, $getShared = true) { if ($getShared === true) { return self::getSharedInstance('curlrequest', $options, $response, $config); @@ -156,7 +150,7 @@ public static function curlrequest( if (! is_object($config)) { - $config = new \Config\App(); + $config = config(App::class); } if (! is_object($response)) @@ -540,7 +534,7 @@ public static function request(\Config\App $config = null, $getShared = true) if (! is_object($config)) { - $config = new App(); + $config = config(App::class); } return new \CodeIgniter\HTTP\IncomingRequest( @@ -570,7 +564,7 @@ public static function response(\Config\App $config = null, $getShared = true) if (! is_object($config)) { - $config = new \Config\App(); + $config = config(App::class); } return new \CodeIgniter\HTTP\Response($config); @@ -595,7 +589,7 @@ public static function redirectResponse(\Config\App $config = null, $getShared = if (! is_object($config)) { - $config = new \Config\App(); + $config = config(App::class); } $response = new \CodeIgniter\HTTP\RedirectResponse($config); @@ -671,7 +665,7 @@ public static function security(\Config\App $config = null, $getShared = true) if (! is_object($config)) { - $config = new \Config\App(); + $config = config(App::class); } return new \CodeIgniter\Security\Security($config); @@ -694,7 +688,7 @@ public static function session(\Config\App $config = null, $getShared = true) if (! is_object($config)) { - $config = new \Config\App(); + $config = config(App::class); } $logger = self::logger(true); @@ -771,7 +765,7 @@ public static function toolbar(\Config\App $config = null, $getShared = true) if (! is_object($config)) { - $config = new \Config\App(); + $config = config(App::class); } return new \CodeIgniter\Debug\Toolbar($config); diff --git a/system/Controller.php b/system/Controller.php index e471754e7105..ef1ba0448b33 100644 --- a/system/Controller.php +++ b/system/Controller.php @@ -39,6 +39,7 @@ use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Log\Logger; use CodeIgniter\Validation\Validation; +use Psr\Log\LoggerInterface; /** * Class Controller @@ -99,18 +100,17 @@ class Controller /** * Constructor. * - * @param RequestInterface $request - * @param ResponseInterface $response - * @param Logger $logger + * @param RequestInterface $request + * @param ResponseInterface $response + * @param \Psr\Log\LoggerInterface $logger + * + * @throws \CodeIgniter\HTTP\RedirectException */ - public function __construct(RequestInterface $request, ResponseInterface $response, Logger $logger = null) + public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) { $this->request = $request; - $this->response = $response; - - $this->logger = is_null($logger) ? Services::logger(true) : $logger; - + $this->logger = $logger; $this->logger->info('Controller "' . get_class($this) . '" loaded.'); if ($this->forceHTTPS > 0) @@ -132,6 +132,8 @@ public function __construct(RequestInterface $request, ResponseInterface $respon * @param int $duration The number of seconds this link should be * considered secure for. Only with HSTS header. * Default value is 1 year. + * + * @throws \CodeIgniter\HTTP\RedirectException */ public function forceHTTPS(int $duration = 31536000) { @@ -182,9 +184,10 @@ public function validate($rules, array $messages = []): bool { $this->validator = Services::validation(); - $success = $this->validator->withRequest($this->request) - ->setRules($rules, $messages) - ->run(); + $success = $this->validator + ->withRequest($this->request) + ->setRules($rules, $messages) + ->run(); return $success; } diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 9de59e8fd0c9..4bc38037b677 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -1305,6 +1305,28 @@ public function set($key, $value = '', $escape = null) //-------------------------------------------------------------------- + /** + * Returns the previously set() data, alternatively resetting it + * if needed. + * + * @param bool $clean + * + * @return array + */ + public function getSetData(bool $clean = false) + { + $data = $this->QBSet; + + if ($clean) + { + $this->QBSet = []; + } + + return $data; + } + + //-------------------------------------------------------------------- + /** * Get SELECT query string * @@ -1486,13 +1508,13 @@ public function getWhere($where = null, $limit = null, $offset = null) * @param array $set An associative array of insert values * @param bool $escape Whether to escape values and identifiers * - * @param int $batch_size + * @param int $batchSize * @param bool $testing * * @return int Number of rows inserted or FALSE on failure * @throws DatabaseException */ - public function insertBatch($set = null, $escape = null, $batch_size = 100, $testing = false) + public function insertBatch($set = null, $escape = null, $batchSize = 100, $testing = false) { if ($set === null) { @@ -1525,9 +1547,9 @@ public function insertBatch($set = null, $escape = null, $batch_size = 100, $tes // Batch this baby $affected_rows = 0; - for ($i = 0, $total = count($this->QBSet); $i < $total; $i += $batch_size) + for ($i = 0, $total = count($this->QBSet); $i < $total; $i += $batchSize) { - $sql = $this->_insertBatch($this->db->protectIdentifiers($table, true, $escape, false), $this->QBKeys, array_slice($this->QBSet, $i, $batch_size)); + $sql = $this->_insertBatch($this->db->protectIdentifiers($table, true, $escape, false), $this->QBKeys, array_slice($this->QBSet, $i, $batchSize)); if ($testing) { @@ -1953,15 +1975,15 @@ protected function validateUpdate() * * Compiles an update string and runs the query * - * @param array $set An associative array of update values - * @param string $index The where key - * @param int $batch_size The size of the batch to run - * @param bool $returnSQL True means SQL is returned, false will execute the query + * @param array $set An associative array of update values + * @param string $index The where key + * @param int $batchSize The size of the batch to run + * @param bool $returnSQL True means SQL is returned, false will execute the query * * @return mixed Number of rows affected or FALSE on failure * @throws \CodeIgniter\Database\Exceptions\DatabaseException */ - public function updateBatch($set = null, $index = null, $batch_size = 100, $returnSQL = false) + public function updateBatch($set = null, $index = null, $batchSize = 100, $returnSQL = false) { if ($index === null) { @@ -2003,9 +2025,9 @@ public function updateBatch($set = null, $index = null, $batch_size = 100, $retu // Batch this baby $affected_rows = 0; $savedSQL = []; - for ($i = 0, $total = count($this->QBSet); $i < $total; $i += $batch_size) + for ($i = 0, $total = count($this->QBSet); $i < $total; $i += $batchSize) { - $sql = $this->_updateBatch($table, array_slice($this->QBSet, $i, $batch_size), $this->db->protectIdentifiers($index) + $sql = $this->_updateBatch($table, array_slice($this->QBSet, $i, $batchSize), $this->db->protectIdentifiers($index) ); if ($returnSQL) diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index b0dfab8a4c2a..555d06b76a10 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -1495,7 +1495,7 @@ public function callFunction(string $functionName, ...$params) public function listTables($constrain_by_prefix = FALSE) { // Is there a cached result? - if (isset($this->dataCache['table_names']) && count($this->dataCache['table_names'])) + if (isset($this->dataCache['table_names']) && $this->dataCache['table_names']) { return $this->dataCache['table_names']; } diff --git a/system/Database/Forge.php b/system/Database/Forge.php index 33122ec1dd6c..2b94a1c5d907 100644 --- a/system/Database/Forge.php +++ b/system/Database/Forge.php @@ -564,7 +564,7 @@ protected function _createTableAttributes($attributes) { if (is_string($key)) { - $sql .= ' ' . strtoupper($key) . ' ' . $attributes[$key]; + $sql .= ' ' . strtoupper($key) . ' ' . $this->db->escape($attributes[$key]); } } diff --git a/system/Database/MySQLi/Forge.php b/system/Database/MySQLi/Forge.php index b83bd42c3f61..c24ca2e64572 100644 --- a/system/Database/MySQLi/Forge.php +++ b/system/Database/MySQLi/Forge.php @@ -109,18 +109,18 @@ protected function _createTableAttributes($attributes) { if (is_string($key)) { - $sql .= ' ' . strtoupper($key) . ' = ' . $attributes[$key]; + $sql .= ' ' . strtoupper($key) . ' = ' . $this->db->escape($attributes[$key]); } } if ( ! empty($this->db->charset) && ! strpos($sql, 'CHARACTER SET') && ! strpos($sql, 'CHARSET')) { - $sql .= ' DEFAULT CHARACTER SET = ' . $this->db->charset; + $sql .= ' DEFAULT CHARACTER SET = ' . $this->db->escape($this->db->charset); } if ( ! empty($this->db->DBCollat) && ! strpos($sql, 'COLLATE')) { - $sql .= ' COLLATE = ' . $this->db->DBCollat; + $sql .= ' COLLATE = ' . $this->db->escape($this->db->DBCollat); } return $sql; diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index 8c347b8c1346..55e777ded06b 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -148,7 +148,7 @@ public function replace($set = null, $returnSQL = false) $this->set($set); } - if (count($this->QBSet) === 0) + if (! $this->QBSet) { if (CI_DEBUG) { diff --git a/system/Database/Postgre/Forge.php b/system/Database/Postgre/Forge.php index cddb9b77bbb4..446aa65f48a7 100644 --- a/system/Database/Postgre/Forge.php +++ b/system/Database/Postgre/Forge.php @@ -76,6 +76,19 @@ class Forge extends \CodeIgniter\Database\Forge //-------------------------------------------------------------------- + /** + * CREATE TABLE attributes + * + * @param array $attributes Associative array of table attributes + * @return string + */ + protected function _createTableAttributes($attributes) + { + return ''; + } + + //-------------------------------------------------------------------- + /** * ALTER TABLE * diff --git a/system/Debug/Toolbar.php b/system/Debug/Toolbar.php index 9399626d633d..ce96b098277d 100644 --- a/system/Debug/Toolbar.php +++ b/system/Debug/Toolbar.php @@ -35,8 +35,9 @@ * @since Version 3.0.0 * @filesource */ -use CodeIgniter\Config\BaseConfig; +use Config\App; use Config\Services; +use CodeIgniter\Config\BaseConfig; use CodeIgniter\Format\XMLFormatter; /** @@ -93,13 +94,12 @@ public function __construct(BaseConfig $config) * * @param float $startTime App start time * @param float $totalTime - * @param float $startMemory * @param \CodeIgniter\HTTP\RequestInterface $request * @param \CodeIgniter\HTTP\ResponseInterface $response * * @return string JSON encoded data */ - public function run($startTime, $totalTime, $startMemory, $request, $response): string + public function run($startTime, $totalTime, $request, $response): string { // Data items used within the view. $data['url'] = current_url(); @@ -107,7 +107,7 @@ public function run($startTime, $totalTime, $startMemory, $request, $response): $data['isAJAX'] = $request->isAJAX(); $data['startTime'] = $startTime; $data['totalTime'] = $totalTime*1000; - $data['totalMemory'] = number_format((memory_get_peak_usage()-$startMemory)/1048576, 3); + $data['totalMemory'] = number_format((memory_get_peak_usage())/1024/1024, 3); $data['segmentDuration'] = $this->roundTo($data['totalTime']/7, 5); $data['segmentCount'] = (int)ceil($data['totalTime']/$data['segmentDuration']); $data['CI_VERSION'] = \CodeIgniter\CodeIgniter::CI_VERSION; @@ -221,7 +221,7 @@ protected static function format(string $data, string $format = 'html') $files = []; $current = self::$request->getGet('debugbar_time'); - $app = new \Config\App; + $app = config(App::class); for ($i = 0; $i < $total; $i++) { diff --git a/system/Debug/Toolbar/Collectors/Config.php b/system/Debug/Toolbar/Collectors/Config.php index 9e290100d374..cd23cc747016 100644 --- a/system/Debug/Toolbar/Collectors/Config.php +++ b/system/Debug/Toolbar/Collectors/Config.php @@ -1,14 +1,14 @@ CodeIgniter::CI_VERSION, diff --git a/system/Debug/Toolbar/Collectors/Database.php b/system/Debug/Toolbar/Collectors/Database.php index 21a81962eca5..84097fae519f 100644 --- a/system/Debug/Toolbar/Collectors/Database.php +++ b/system/Debug/Toolbar/Collectors/Database.php @@ -204,8 +204,8 @@ public function getBadgeValue() */ public function getTitleDetails(): string { - return '(' . count(static::$queries) . ' Queries across ' . count($this->connections) . ' Connection' . - (count($this->connections) > 1 ? 's' : '') . ')'; + return '(' . count(static::$queries) . ' Queries across ' . ($countConnection = count($this->connections)) . ' Connection' . + ($countConnection > 1 ? 's' : '') . ')'; } //-------------------------------------------------------------------- diff --git a/system/Email/Email.php b/system/Email/Email.php index 4a8750ee84b9..018b522ad65a 100644 --- a/system/Email/Email.php +++ b/system/Email/Email.php @@ -1224,7 +1224,7 @@ public function wordWrap($str, $charlim = null) } // Put our markers back - if (count($unwrap) > 0) + if ($unwrap) { foreach ($unwrap as $key => $val) { diff --git a/system/Entity.php b/system/Entity.php index 709021159321..d34bfe3ac96a 100644 --- a/system/Entity.php +++ b/system/Entity.php @@ -348,7 +348,7 @@ protected function castAs($value, string $type) $value = (object)$value; break; case 'array': - if (is_string($value) && (substr($value, 0, 2) === 'a:' || substr($value, 0, 2) === 's:')) + if (is_string($value) && (strpos($value,'a:') === 0 || strpos($value, 's:') === 0)) { $value = unserialize($value); } diff --git a/system/Filters/Filters.php b/system/Filters/Filters.php index e5571fafa51b..939907d931e1 100644 --- a/system/Filters/Filters.php +++ b/system/Filters/Filters.php @@ -322,7 +322,7 @@ protected function processMethods() protected function processFilters(string $uri = null) { - if ( ! isset($this->config->filters) || ! count($this->config->filters)) + if ( ! isset($this->config->filters) || ! $this->config->filters) { return; } diff --git a/system/HTTP/CLIRequest.php b/system/HTTP/CLIRequest.php index 746ebeaca0ac..34cc40fed060 100644 --- a/system/HTTP/CLIRequest.php +++ b/system/HTTP/CLIRequest.php @@ -213,7 +213,7 @@ protected function parseCommand() $options_found = true; - if (substr($argv[$i], 0, 1) != '-') + if (strpos($argv[$i], '-') !== 0) { continue; } @@ -222,7 +222,7 @@ protected function parseCommand() $value = null; // If the next item starts with a dash it's a value - if (isset($argv[$i + 1]) && substr($argv[$i + 1], 0, 1) != '-') + if (isset($argv[$i + 1]) && strpos($argv[$i + 1], '-') !== 0) { $value = filter_var($argv[$i + 1], FILTER_SANITIZE_STRING); $i ++; diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php index 8569d79d4739..3eb725c69dfe 100644 --- a/system/HTTP/CURLRequest.php +++ b/system/HTTP/CURLRequest.php @@ -516,7 +516,7 @@ protected function setResponseHeaders(array $headers = []) $this->response->setHeader($title, $value); } - else if (substr($header, 0, 4) == 'HTTP') + else if (strpos($header, 'HTTP') === 0) { preg_match('#^HTTP\/([12]\.[01]) ([0-9]+) (.+)#', $header, $matches); diff --git a/system/HTTP/Exceptions/HTTPException.php b/system/HTTP/Exceptions/HTTPException.php index a8f2ae464255..9b9cf56730aa 100644 --- a/system/HTTP/Exceptions/HTTPException.php +++ b/system/HTTP/Exceptions/HTTPException.php @@ -1,10 +1,12 @@ -getValueDotNotationSyntax($index, $value[$current_index]); } diff --git a/system/HTTP/Files/UploadedFile.php b/system/HTTP/Files/UploadedFile.php index 7fd3e7e9ed22..ef46d3999926 100644 --- a/system/HTTP/Files/UploadedFile.php +++ b/system/HTTP/Files/UploadedFile.php @@ -1,5 +1,7 @@ -path = $path; - $this->name = $originalName; - $this->originalName = $originalName; - $this->originalMimeType = $mimeType; - $this->size = $size; - $this->error = $error; + $this->path = $path; + $this->name = $originalName; + $this->originalName = $originalName; + $this->originalMimeType = $mimeType; + $this->size = $size; + $this->error = $error; parent::__construct($path, false); } @@ -157,30 +159,34 @@ public function move(string $targetPath, string $name = null, bool $overwrite = if ($this->hasMoved) { - throw new FileException('The file has already been moved.'); + throw HTTPException::forAlreadyMoved(); } if ( ! $this->isValid()) { - throw new FileException('The original file is not a valid file.'); + throw HTTPException::forInvalidFile(); } - $targetPath = rtrim($targetPath, '/') . '/'; - $name = is_null($name) ? $this->getName() : $name; + $targetPath = rtrim($targetPath, '/') . '/'; + $name = is_null($name) ? $this->getName() : $name; $destination = $overwrite ? $targetPath . $name : $this->getDestination($targetPath . $name); - if ( ! @move_uploaded_file($this->path, $destination)) + try + { + @move_uploaded_file($this->path, $destination); + } + catch (\Exception $e) { $error = error_get_last(); - throw new \RuntimeException(sprintf('Could not move file %s to %s (%s)', basename($this->path), $targetPath, strip_tags($error['message']))); + throw HTTPException::forMoveFailed(basename($this->path), $targetPath, strip_tags($error['message'])); } @chmod($targetPath, 0777 & ~umask()); // Success, so store our new information - $this->path = $targetPath; - $this->name = basename($destination); - $this->hasMoved = true; + $this->path = $targetPath; + $this->name = basename($destination); + $this->hasMoved = true; return true; } @@ -193,20 +199,20 @@ public function move(string $targetPath, string $name = null, bool $overwrite = * * @return string The path set or created. */ - protected function setPath($path) - { - if (!is_dir($path)) - { - mkdir($path, 0777, true); - //create the index.html file - if (!file_exists($path.'index.html')) - { - $file = fopen($path.'index.html', 'x+'); - fclose($file); - } - } - return $path; - } + protected function setPath($path) + { + if ( ! is_dir($path)) + { + mkdir($path, 0777, true); + //create the index.html file + if ( ! file_exists($path . 'index.html')) + { + $file = fopen($path . 'index.html', 'x+'); + fclose($file); + } + } + return $path; + } //-------------------------------------------------------------------- @@ -260,6 +266,7 @@ public function getError(): int public function getErrorString() { static $errors = [ + UPLOAD_ERR_OK => 'The file uploaded with success.', UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive.', UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.', UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.', diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index 81fc864fde0a..5b16068bbfec 100755 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -1,4 +1,6 @@ -files->all(); // return all files } - //-------------------------------------------------------------------- /** @@ -584,10 +586,14 @@ protected function detectURI($protocol, $baseURL) $this->uri->setHost(parse_url($baseURL, PHP_URL_HOST)); $this->uri->setPort(parse_url($baseURL, PHP_URL_PORT)); $this->uri->resolveRelativeURI(parse_url($baseURL, PHP_URL_PATH)); - } - else + } else { - throw FrameworkException::forEmptyBaseURL(); + // @codeCoverageIgnoreStart + if ( ! is_cli()) + { + throw FrameworkException::forEmptyBaseURL(); + } + // @codeCoverageIgnoreEnd } } @@ -601,7 +607,7 @@ protected function detectURI($protocol, $baseURL) * * @return string */ - public function detectPath($protocol) + public function detectPath($protocol = '') { if (empty($protocol)) { @@ -639,25 +645,21 @@ public function detectPath($protocol) */ public function negotiate(string $type, array $supported, bool $strictMatch = false) { - if (is_null($this->negotiate)) + if (is_null($this->negotiator)) { - $this->negotiate = Services::negotiator($this, true); + $this->negotiator = Services::negotiator($this, true); } switch (strtolower($type)) { case 'media': - return $this->negotiate->media($supported, $strictMatch); - break; + return $this->negotiator->media($supported, $strictMatch); case 'charset': - return $this->negotiate->charset($supported); - break; + return $this->negotiator->charset($supported); case 'encoding': - return $this->negotiate->encoding($supported); - break; + return $this->negotiator->encoding($supported); case 'language': - return $this->negotiate->language($supported); - break; + return $this->negotiator->language($supported); } throw HTTPException::forInvalidNegotiationType($type); @@ -686,25 +688,26 @@ protected function parseRequestURI(): string if (isset($_SERVER['SCRIPT_NAME'][0])) { + // strip the script name from the beginning of the URI if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0) { $uri = (string) substr($uri, strlen($_SERVER['SCRIPT_NAME'])); - } - elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0) - { - $uri = (string) substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME']))); - } + } elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0) + // if the script is nested, strip the parent folder & script from the URI + if (strpos($uri, $_SERVER['SCRIPT_NAME']) > 0) + $uri = (string) substr($uri, strpos($uri, $_SERVER['SCRIPT_NAME']) + strlen($_SERVER['SCRIPT_NAME'])); + elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) > 0) + $uri = (string) substr($uri, strpos($uri, dirname($_SERVER['SCRIPT_NAME']))); } - // This section ensures that even on servers that require the URI to be in the query string (Nginx) a correct + // This section ensures that even on servers that require the URI to contain the query string (Nginx) a correct // URI is found, and also fixes the QUERY_STRING getServer var and $_GET array. if (trim($uri, '/') === '' && strncmp($query, '/', 1) === 0) { $query = explode('?', $query, 2); $uri = $query[0]; $_SERVER['QUERY_STRING'] = $query[1] ?? ''; - } - else + } else { $_SERVER['QUERY_STRING'] = $query; } @@ -735,8 +738,7 @@ protected function parseQueryString(): string if (trim($uri, '/') === '') { return ''; - } - elseif (strncmp($uri, '/', 1) === 0) + } elseif (strncmp($uri, '/', 1) === 0) { $uri = explode('?', $uri, 2); $_SERVER['QUERY_STRING'] = $uri[1] ?? ''; diff --git a/system/HTTP/Message.php b/system/HTTP/Message.php index 1d9634e1dd02..663255bc5208 100644 --- a/system/HTTP/Message.php +++ b/system/HTTP/Message.php @@ -1,4 +1,5 @@ -setHeader($header, $_SERVER[$key]); - } - else - { - $this->setHeader($header, ''); - } + $this->setHeader($header, $_SERVER[$key]); // Add us to the header map so we can find them case-insensitively $this->headerMap[strtolower($header)] = $header; @@ -246,13 +240,6 @@ public function getHeaderLine(string $name): string return ''; } - // If there are more than 1 headers with this name, - // then return the value of the first. - if (is_array($this->headers[$orig_name])) - { - return $this->headers[$orig_name][0]->getValueLine(); - } - return $this->headers[$orig_name]->getValueLine(); } @@ -286,10 +273,6 @@ public function setHeader(string $name, $value) { $this->headers[$name] = new Header($name, $value); } - else - { - $this->headers[$name][] = new Header($name, $value); - } return $this; } diff --git a/system/HTTP/Negotiate.php b/system/HTTP/Negotiate.php index 0f23152423f6..7af05b35b701 100644 --- a/system/HTTP/Negotiate.php +++ b/system/HTTP/Negotiate.php @@ -213,7 +213,7 @@ protected function getBestMatch(array $supported, string $header = null, bool $e // If no acceptable values exist, return the // first that we support. - if (empty($acceptable)) + if (count($acceptable) === 0) { return $supported[0]; } diff --git a/system/HTTP/Request.php b/system/HTTP/Request.php index 139a457efed2..37fd8573a0d0 100644 --- a/system/HTTP/Request.php +++ b/system/HTTP/Request.php @@ -131,7 +131,7 @@ public function getIPAddress(): string if ($spoof) { - for ($i = 0, $c = count($this->proxyIPs); $i < $c; $i ++ ) + for ($i = 0, $c = count($proxy_ips); $i < $c; $i ++ ) { // Check if we have an IP address or a subnet if (strpos($proxy_ips[$i], '/') === FALSE) diff --git a/system/HTTP/Response.php b/system/HTTP/Response.php index 44791bbc45c1..19ecf588e7d0 100644 --- a/system/HTTP/Response.php +++ b/system/HTTP/Response.php @@ -35,11 +35,10 @@ * @since Version 3.0.0 * @filesource */ -use CodeIgniter\HTTP\Exceptions\HTTPException; -use CodeIgniter\Services; use Config\App; -use Config\Format; use Config\Mimes; +use Config\Format; +use CodeIgniter\HTTP\Exceptions\HTTPException; /** * Redirect exception @@ -421,8 +420,6 @@ public function setJSON($body) $this->body = $this->formatBody($body, 'json'); return $this; - - return $this; } //-------------------------------------------------------------------- @@ -438,7 +435,7 @@ public function getJSON() if ($this->bodyFormat != 'json') { - $config = new Format(); + $config = config(Format::class); $formatter = $config->getFormatter('application/json'); $body = $formatter->format($body); @@ -476,7 +473,7 @@ public function getXML() if ($this->bodyFormat != 'xml') { - $config = new Format(); + $config = config(Format::class); $formatter = $config->getFormatter('application/xml'); $body = $formatter->format($body); @@ -505,7 +502,7 @@ protected function formatBody($body, string $format) // Nothing much to do for a string... if (! is_string($body)) { - $config = new Format(); + $config = config(Format::class); $formatter = $config->getFormatter($mime); $body = $formatter->format($body); @@ -660,7 +657,7 @@ public function send() public function sendHeaders() { // Have the headers already been sent? - if (headers_sent()) + if ($this->pretend || headers_sent()) { return $this; } diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php index 56ac99f02edf..cd82a884f6e4 100644 --- a/system/HTTP/URI.php +++ b/system/HTTP/URI.php @@ -1,4 +1,5 @@ -getScheme(), $this->getAuthority(), $this->getPath(), // Absolute URIs should use a "/" for an empty path - $this->getQuery(), $this->getFragment() + $this->getQuery(), $this->getFragment() ); } @@ -704,7 +705,10 @@ public function setQuery(string $query) continue; } - $parts[$key] = $value; + // URL Decode the value to protect + // from double-encoding a URL. + // Especially useful with the Pager. + $parts[$key] = $this->decode($value); } $this->query = $parts; @@ -714,6 +718,29 @@ public function setQuery(string $query) //-------------------------------------------------------------------- + /** + * Checks the value to see if it has been urlencoded and decodes it if so. + * The urlencode check is not perfect but should catch most cases. + * + * @param string $value + * + * @return string + */ + protected function decode(string $value) + { + if (empty($value)) + { + return $value; + } + + $decoded = urldecode($value); + + // This won't catch all cases, specifically + // changing ' ' to '+' has the same length + // but doesn't really matter for our cases here. + return strlen($decoded) < strlen($value) ? $decoded : $value; + } + /** * Split a query value into it's key/value elements, if both * are present. @@ -870,7 +897,8 @@ protected function filterPath(string $path = null) // Encode characters $path = preg_replace_callback( - '/(?:[^' . self::CHAR_UNRESERVED . ':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/', function(array $matches) { + '/(?:[^' . self::CHAR_UNRESERVED . ':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/', function(array $matches) + { return rawurlencode($matches[0]); }, $path ); @@ -923,13 +951,8 @@ protected function applyParts($parts) { if ( ! is_null($parts['port'])) { + // Valid port numbers are enforced by earlier parse_url or setPort() $port = (int) $parts['port']; - - if (1 > $port || 0xffff < $port) - { - throw HTTPException::forInvalidPort($port); - } - $this->port = $port; } } @@ -998,7 +1021,7 @@ public function resolveRelativeURI(string $uri) } else { - if (substr($relative->getPath(), 0, 1) == '/') + if (strpos($relative->getPath(), '/') === 0) { $transformed->setPath($relative->getPath()); } @@ -1107,7 +1130,7 @@ public function removeDotSegments(string $path): string if ($output != '/') { // Add leading slash if necessary - if (substr($path, 0, 1) == '/') + if (strpos($path, '/') === 0) { $output = '/' . $output; } diff --git a/system/HTTP/UserAgent.php b/system/HTTP/UserAgent.php index 94684cce96f6..91ab2609e9b9 100644 --- a/system/HTTP/UserAgent.php +++ b/system/HTTP/UserAgent.php @@ -349,7 +349,7 @@ protected function compileData() */ protected function setPlatform() { - if (is_array($this->config->platforms) && count($this->config->platforms) > 0) + if (is_array($this->config->platforms) && $this->config->platforms) { foreach ($this->config->platforms as $key => $val) { @@ -376,7 +376,7 @@ protected function setPlatform() */ protected function setBrowser() { - if (is_array($this->config->browsers) && count($this->config->browsers) > 0) + if (is_array($this->config->browsers) && $this->config->browsers) { foreach ($this->config->browsers as $key => $val) { @@ -404,7 +404,7 @@ protected function setBrowser() */ protected function setRobot() { - if (is_array($this->config->robots) && count($this->config->robots) > 0) + if (is_array($this->config->robots) && $this->config->robots) { foreach ($this->config->robots as $key => $val) { @@ -431,7 +431,7 @@ protected function setRobot() */ protected function setMobile() { - if (is_array($this->config->mobiles) && count($this->config->mobiles) > 0) + if (is_array($this->config->mobiles) && $this->config->mobiles) { foreach ($this->config->mobiles as $key => $val) { diff --git a/system/Helpers/array_helper.php b/system/Helpers/array_helper.php index 76093f52cebe..45ae7e1bb1a4 100644 --- a/system/Helpers/array_helper.php +++ b/system/Helpers/array_helper.php @@ -33,7 +33,7 @@ function dot_array_search(string $index, array $array) function _array_search_dot(array $indexes, array $array) { // Grab the current index - $currentIndex = count($indexes) + $currentIndex = $indexes ? array_shift($indexes) : null; @@ -71,7 +71,7 @@ function _array_search_dot(array $indexes, array $array) } // Do we need to recursively search this value? - if (is_array($array[$currentIndex]) && count($array[$currentIndex])) + if (is_array($array[$currentIndex]) && $array[$currentIndex]) { return _array_search_dot($indexes, $array[$currentIndex]); } diff --git a/system/Helpers/cookie_helper.php b/system/Helpers/cookie_helper.php index 346404b613e8..616ab9a60dbe 100755 --- a/system/Helpers/cookie_helper.php +++ b/system/Helpers/cookie_helper.php @@ -95,7 +95,7 @@ function set_cookie($name, string $value = '', string $expire = '', string $doma */ function get_cookie($index, bool $xssClean = false) { - $app = new \Config\App(); + $app = config(\Config\App::class); $appCookiePrefix = $app->cookiePrefix; $prefix = isset($_COOKIE[$index]) ? '' : $appCookiePrefix; diff --git a/system/Helpers/form_helper.php b/system/Helpers/form_helper.php index dcb7ab750d7e..a4dca20006cb 100644 --- a/system/Helpers/form_helper.php +++ b/system/Helpers/form_helper.php @@ -48,12 +48,12 @@ * Creates the opening portion of the form. * * @param string $action the URI segments of the form destination - * @param array $attributes a key/value pair of attributes + * @param array|string $attributes a key/value pair of attributes, or string representation * @param array $hidden a key/value pair hidden data * * @return string */ - function form_open(string $action = '', array $attributes = [], array $hidden = []): string + function form_open(string $action = '', $attributes = [], array $hidden = []): string { // If no action is provided then set to the current url if ( ! $action) @@ -75,14 +75,14 @@ function form_open(string $action = '', array $attributes = [], array $hidden = } if (stripos($attributes, 'accept-charset=') === false) { - $config = new \Config\App(); + $config = config(\Config\App::class); $attributes .= ' accept-charset="' . strtolower($config->charset) . '"'; } $form = '