From 1fc3cf414ae47874e84eb8de3f3e2cee0b61fb2e Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Fri, 20 Dec 2024 15:14:37 -0500 Subject: [PATCH] Fix issue where the config.maxUrlSegments wasn't working, plus add new config.longUrlResponse where you can define the http response that should be used when there is an overflow of URL length, segments, or depth. --- wire/config.php | 20 ++++++++++++++-- wire/core/Config.php | 1 + wire/core/PagesPathFinder.php | 44 ++++++++++++++++++++++++----------- wire/core/PagesRequest.php | 12 +++++++--- 4 files changed, 58 insertions(+), 19 deletions(-) diff --git a/wire/config.php b/wire/config.php index ed0c7ba9..5553ee26 100644 --- a/wire/config.php +++ b/wire/config.php @@ -985,7 +985,7 @@ $config->protectCSRF = true; * @var int * */ -$config->maxUrlSegments = 4; +$config->maxUrlSegments = 20; /** * Maximum length for any individual URL segment (default=128) @@ -1004,7 +1004,23 @@ $config->maxUrlSegmentLength = 128; * @var int * */ -$config->maxUrlDepth = 30; +$config->maxUrlDepth = 30; + +/** + * Long URL response (URL depth, length or segments overflow) + * + * HTTP code that ProcessWire should respond with when it receives more URL segments, + * more URL depth, or longer URL length than what is allowed. Suggested values: + * + * - `404`: Page not found + * - `301`: Redirect to closest allowed URL (permanent) + * - `302`: Redirect to closest allowed URL (temporary) + * + * @var int + * @since 3.0.243 + * + */ +$config->longUrlResponse = 404; /** * Pagination URL prefix diff --git a/wire/core/Config.php b/wire/core/Config.php index 86439d6f..3bf67ba1 100644 --- a/wire/core/Config.php +++ b/wire/core/Config.php @@ -91,6 +91,7 @@ * @property int $maxUrlSegments Maximum number of extra stacked URL segments allowed in a page's URL (including page numbers) #pw-group-URLs * @property int $maxUrlSegmentLength Maximum length of any individual URL segment (default=128). #pw-group-URLs * @property int $maxUrlDepth Maximum URL/path slashes (depth) for request URLs. (Min=10, Max=60) #pw-group-URLs + * @property int $longUrlResponse Response code when URL segments, depth or length exceeds max allowed. #pw-group-URLs @since 3.0.243 * @property string $wireInputOrder Order that variables with the $input API var are handled when you access $input->var. #pw-group-HTTP-and-input * @property bool $wireInputLazy Specify true for $input API var to load input data in a lazy fashion and potentially use less memory. Default is false. #pw-group-HTTP-and-input * @property int $wireInputArrayDepth Maximum multi-dimensional array depth for input variables accessed from $input or 1 to only allow single dimension arrays. #pw-group-HTTP-and-input @since 3.0.178 diff --git a/wire/core/PagesPathFinder.php b/wire/core/PagesPathFinder.php index 0c27edae..d8844afb 100644 --- a/wire/core/PagesPathFinder.php +++ b/wire/core/PagesPathFinder.php @@ -380,8 +380,10 @@ class PagesPathFinder extends Wire { * */ protected function applyPagesRow(array $parts, $row) { - - $maxUrlSegmentLength = $this->wire()->config->maxUrlSegmentLength; + + $config = $this->wire()->config; + $maxUrlSegmentLength = $config->maxUrlSegmentLength; + $maxUrlSegments = $config->maxUrlSegments; $result = &$this->result; // array of [language name] => [ 'a', 'b', 'c' ] (from /a/b/c/) @@ -396,14 +398,28 @@ class PagesPathFinder extends Wire { if(!$id) { // if it didn’t resolve to DB page name then it is a URL segment - if(strlen($name) > $maxUrlSegmentLength) $name = substr($name, 0, $maxUrlSegmentLength); - $result['urlSegments'][] = $name; - if($this->verbose) { - $result['parts'][] = array( - 'type' => 'urlSegment', - 'value' => $name, - 'language' => '' - ); + if(strlen($name) > $maxUrlSegmentLength) { + $name = substr($name, 0, $maxUrlSegmentLength); + if($config->longUrlResponse >= 300) { + $result['response'] = $config->longUrlResponse; + $this->addResultError('urlSegmentLength', 'URL segment length > config.maxUrlSegmentLength'); + } + } + if(count($result['urlSegments']) + 1 > $maxUrlSegments) { + if($config->longUrlResponse >= 300) { + $this->addResultError('urlSegmentMAX', 'Number of URL segments exceeds config.maxUrlSegments'); + $result['response'] = $config->longUrlResponse; + break; + } + } else { + $result['urlSegments'][] = $name; + if($this->verbose) { + $result['parts'][] = array( + 'type' => 'urlSegment', + 'value' => $name, + 'language' => '' + ); + } } continue; } @@ -480,7 +496,7 @@ class PagesPathFinder extends Wire { * If language segment detected then remove it and populate language to result * * @param string $path - * @return array|bool + * @return array * */ protected function getPathParts($path) { @@ -497,7 +513,7 @@ class PagesPathFinder extends Wire { $lastPart = ''; if($this->strlen($path) > $maxPathLength) { - $result['response'] = 414; // 414=URI too long + $result['response'] = $config->longUrlResponse; // 414=URI too long $this->addResultError('pathLengthMAX', "Path length exceeds max allowed $maxPathLength"); $path = substr($path, 0, $maxPathLength); } @@ -506,7 +522,7 @@ class PagesPathFinder extends Wire { if(count($parts) > $maxDepth) { $parts = array_slice($parts, 0, $maxDepth); - $result['response'] = 414; + $result['response'] = $config->longUrlResponse; $this->addResultError('pathDepthMAX', 'Path depth exceeds config.maxUrlDepth'); } else if($path === '/' || $path === '' || !count($parts)) { return array(); @@ -1484,7 +1500,7 @@ class PagesPathFinder extends Wire { * */ protected function addResultError($name, $message, $force = false) { - if(!$this->verbose && !$force) return; + //if(!$this->verbose && !$force) return; $this->result['errors'][$name] = $message; } diff --git a/wire/core/PagesRequest.php b/wire/core/PagesRequest.php index cb47dad5..4675ecf5 100644 --- a/wire/core/PagesRequest.php +++ b/wire/core/PagesRequest.php @@ -593,9 +593,15 @@ class PagesRequest extends Wire { } $maxUrlDepth = $config->maxUrlDepth; - if($maxUrlDepth > 0 && substr_count($it, '/') > $config->maxUrlDepth) { - $this->setResponseCode(414, 'Request URL exceeds max depth set in $config->maxUrlDepth'); - return false; + if($maxUrlDepth > 0 && substr_count($it, '/') > $maxUrlDepth) { + if(in_array($config->longUrlResponse, [ 302, 301 ])) { + $parts = array_slice(explode('/', $it), 0, $maxUrlDepth); + $it = '/' . trim(implode('/', $parts), '/') . '/'; + $this->setRedirectPath($it, $config->longUrlResponse); + } else { + $this->setResponseCode($config->longUrlResponse, 'Request URL exceeds max depth set in $config->maxUrlDepth'); + return false; + } } if(!isset($it[0]) || $it[0] != '/') $it = "/$it";