From f822292eb3bbd5f616d70c6dd385142dd5952885 Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Thu, 21 Sep 2017 11:37:46 -0400 Subject: [PATCH] Some updates to ProcessPageView and additions for processwire/processwire-issues#366 to better handle URLs with unrecognized characters --- wire/modules/Process/ProcessPageView.module | 93 +++++++++++++++++---- 1 file changed, 79 insertions(+), 14 deletions(-) diff --git a/wire/modules/Process/ProcessPageView.module b/wire/modules/Process/ProcessPageView.module index 93876bcb..e922e215 100644 --- a/wire/modules/Process/ProcessPageView.module +++ b/wire/modules/Process/ProcessPageView.module @@ -115,7 +115,7 @@ class ProcessPageView extends Process { // check if there is an 'it' GET variable present in the request URL query string, which we don't want here if(isset($_GET['it']) && strpos($this->dirtyURL, '?it=') !== false || strpos($this->dirtyURL, '&it=')) { // force to use path in request url rather than contents of 'it' var - list($it, $x) = explode('?', $this->dirtyURL); + list($it,) = explode('?', $this->dirtyURL); $rootURL = $this->wire('config')->urls->root; if(strlen($rootURL) > 1 && strpos($it, $rootURL) === 0) $it = substr($it, strlen($rootURL)-1); $it = str_replace('index.php', '', $it); @@ -214,12 +214,13 @@ class ProcessPageView extends Process { return $this->pageNotFound(new NullPage(), $this->requestURL, true, 'requested page resolved to NullPage'); } + return ''; } /** * Method executed when externally bootstrapped * - * @return blank string + * @return string blank string * */ public function ___executeExternal() { @@ -257,9 +258,12 @@ class ProcessPageView extends Process { * Hook called when the pageview failed to finish due to an exception. * * Sends a copy of the exception that occurred. + * + * @param \Exception $e * */ public function ___failed(\Exception $e) { + if($e) {} $this->wire()->setStatus(ProcessWire::statusFailed); } @@ -270,21 +274,70 @@ class ProcessPageView extends Process { * */ protected function getPage() { - + + /** @var Config $config */ $config = $this->wire('config'); - $shit = isset($_GET['it']) ? trim($_GET['it']) : "/"; // dirty - $it = preg_replace('{[^-_./a-zA-Z0-9]}', '', $shit); // clean + + // force redirect to actual page URL? (if different from request URL) + $forceRedirect = false; + + // did URL end with index.php|htm|html? If so we might redirect if a page matches without it. + $indexRedirect = false; + + /** @var string $shit Dirty URL */ + /** @var string $it Clean URL */ + + if(isset($_GET['it'])) { + // normal request + $shit = trim($_GET['it']); + + } else if(isset($_SERVER['REQUEST_URI'])) { + // abnormal request, something about request URL made .htaccess skip it, or index.php called directly + $shit = trim($_SERVER['REQUEST_URI']); + if(strpos($shit, '?') !== false) list($shit,) = explode('?', $shit); + if($config->urls->root != '/') { + if(strpos($shit, $config->urls->root) === 0) { + // remove root URL from request + $shit = substr($shit, strlen($config->urls->root) - 1); + } else { + // request URL outside of our root directory + return null; + } + } + } else { + $shit = '/'; + } + + if($shit === '/') { + $it = '/'; + } else { + $it = preg_replace('{[^-_./a-zA-Z0-9]}', '', $shit); // clean + } + unset($_GET['it']); - if($shit !== $it && $config->pageNameCharset == 'UTF8') { - $it = $this->wire('sanitizer')->pagePathNameUTF8($shit); + if($shit !== $it) { + // sanitized URL does not match requested URL + if($config->pageNameCharset == 'UTF8') { + // test for extended page name URL + $it = $this->wire('sanitizer')->pagePathNameUTF8($shit); + } + if($shit !== $it) { + // if still does not match then fail + return null; + } } if(!isset($it[0]) || $it[0] != '/') $it = "/$it"; if(strpos($it, '//') !== false) return null; - - // if request contains index.php and the $it string does not, make it redirect to correct version - $forceRedirect = strpos($this->dirtyURL, 'index.php') !== false && strpos($it, 'index.php') === false; + + if(strpos($it, '/index.') !== false && preg_match('{/index\.(php|html?)$}', $it, $matches)) { + // if request is to index.php|htm|html, make note of it to determine if we can redirect later + $indexRedirect = true; + } else if(strpos($this->dirtyURL, 'index.php') !== false && strpos($it, 'index.php') === false) { + // if request contains index.php and the $it string does not, make it redirect to correct version + $forceRedirect = true; + } $numParts = substr_count($it, '/'); if($numParts > $config->maxUrlDepth) return null; @@ -351,7 +404,14 @@ class ProcessPageView extends Process { if(!$page || !$page->id) return null; // if URL segments and/or page numbers are present and not allowed then abort - if(!$this->checkUrlSegments($urlSegments, $page)) return null; + if(!$this->checkUrlSegments($urlSegments, $page)) { + if($indexRedirect && $cnt === 1) { + // index.php|htm|html segments if not used by page can redirect to URL without it + $forceRedirect = true; + } else { + return null; + } + } if($forceRedirect && !$this->redirectURL) $this->redirectURL = $page->url; @@ -429,7 +489,7 @@ class ProcessPageView extends Process { if(!$maxPageNum) $maxPageNum = 999; if($pageNum > $maxPageNum) return false; } - $page->pageNum = $pageNum; // backwards compatibility + $page->setQuietly('pageNum', $pageNum); // backwards compatibility $this->input->setPageNum($pageNum); array_pop($urlSegments); } @@ -536,7 +596,7 @@ class ProcessPageView extends Process { * * If the user doesn't have access, then a login Page or NULL (for 404) is returned instead. * - * @param $page + * @param Page $page * @return Page|null * */ @@ -589,7 +649,7 @@ class ProcessPageView extends Process { * if a similar check is needed somewhere else in the core. * * @param Page $page - * @return Page|null + * @return Page|null|bool * */ protected function checkAccessDelegated(Page $page) { @@ -685,6 +745,10 @@ class ProcessPageView extends Process { * Passthru a file for a non-public page * * If the page is public, then it just does a 301 redirect to the file. + * + * @param Page $page + * @param string $basename + * @throws Wire404Exception * */ protected function ___sendFile($page, $basename) { @@ -721,6 +785,7 @@ class ProcessPageView extends Process { * @return string */ protected function ___pageNotFound($page, $url, $triggerReady = false, $reason = '') { + if($page || $url || $reason) {} // variables provided for hooks only $this->responseType = self::responseTypeError; $config = $this->config;