From 43e8ff3e3e21f979931f5ee6becba6942d71dc85 Mon Sep 17 00:00:00 2001 From: Shish Date: Fri, 9 Feb 2024 12:43:53 +0000 Subject: [PATCH] more tests --- core/basepage.php | 4 +- core/event.php | 10 +- core/polyfills.php | 45 -------- core/tests/PolyfillsTest.php | 41 ------- core/tests/UrlsTest.php | 216 +++++++++++++++++++++++++++++------ core/tests/UtilTest.php | 31 ----- core/urls.php | 103 ++++++++++++++++- core/util.php | 31 ----- ext/setup/main.php | 7 ++ 9 files changed, 299 insertions(+), 189 deletions(-) diff --git a/core/basepage.php b/core/basepage.php index 3b972af7..9580c135 100644 --- a/core/basepage.php +++ b/core/basepage.php @@ -688,7 +688,7 @@ class NavLink $this->description = $description; $this->order = $order; if ($active == null) { - $query = ltrim(_get_query(), "/"); + $query = _get_query(); if ($query === "") { // This indicates the front page, so we check what's set as the front page $front_page = trim($config->get_string(SetupConfig::FRONT_PAGE), "/"); @@ -716,7 +716,7 @@ class NavLink /** * Woo! We can actually SEE THE CURRENT PAGE!! (well... see it highlighted in the menu.) */ - $url = $url ?? ltrim(_get_query(), "/"); + $url = $url ?? _get_query(); $re1 = '.*?'; $re2 = '((?:[a-z][a-z_]+))'; diff --git a/core/event.php b/core/event.php index c66b2ac9..f1cc3130 100644 --- a/core/event.php +++ b/core/event.php @@ -47,6 +47,7 @@ class InitExtEvent extends Event class PageRequestEvent extends Event { public string $method; + public string $path; /** * @var string[] */ @@ -61,13 +62,12 @@ class PageRequestEvent extends Event $this->method = $method; - // trim starting slashes - $path = ltrim($path, "/"); - - // if path is not specified, use the default front page - if (empty($path)) { /* empty is faster than strlen */ + // if we're looking at the root of the install, + // use the default front page + if ($path == "") { $path = $config->get_string(SetupConfig::FRONT_PAGE); } + $this->path = $path; // break the path into parts $args = explode('/', $path); diff --git a/core/polyfills.php b/core/polyfills.php index d9dc50ac..e575cf0c 100644 --- a/core/polyfills.php +++ b/core/polyfills.php @@ -293,51 +293,6 @@ function zglob(string $pattern): array } } -/** - * Figure out the path to the shimmie install directory. - * - * eg if shimmie is visible at https://foo.com/gallery, this - * function should return /gallery - * - * PHP really, really sucks. - */ -function get_base_href(): string -{ - if (defined("BASE_HREF") && !empty(BASE_HREF)) { - return BASE_HREF; - } - if(str_ends_with($_SERVER['PHP_SELF'], 'index.php')) { - $self = $_SERVER['PHP_SELF']; - } elseif(isset($_SERVER['SCRIPT_FILENAME']) && isset($_SERVER['DOCUMENT_ROOT'])) { - $self = substr($_SERVER['SCRIPT_FILENAME'], strlen(rtrim($_SERVER['DOCUMENT_ROOT'], "/"))); - } else { - die("PHP_SELF or SCRIPT_FILENAME need to be set"); - } - $dir = dirname($self); - $dir = str_replace("\\", "/", $dir); - $dir = rtrim($dir, "/"); - return $dir; -} - -/** - * The opposite of the standard library's parse_url - * - * @param array $parsed_url - */ -function unparse_url(array $parsed_url): string -{ - $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; - $host = $parsed_url['host'] ?? ''; - $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; - $user = $parsed_url['user'] ?? ''; - $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; - $pass = ($user || $pass) ? "$pass@" : ''; - $path = $parsed_url['path'] ?? ''; - $query = !empty($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; - $fragment = !empty($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; - return "$scheme$user$pass$host$port$path$query$fragment"; -} - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ * Input / Output Sanitising * diff --git a/core/tests/PolyfillsTest.php b/core/tests/PolyfillsTest.php index 91fe64e1..2ca5328f 100644 --- a/core/tests/PolyfillsTest.php +++ b/core/tests/PolyfillsTest.php @@ -254,45 +254,4 @@ class PolyfillsTest extends TestCase deltree($dir); $this->assertFalse(file_exists($dir)); } - - /** - * @param array $vars - */ - private function _tbh(array $vars, string $result): void - { - // update $_SERVER with $vars, call get_base_href() and check result, then reset $_SERVER to original value - $old_server = $_SERVER; - $_SERVER = array_merge($_SERVER, $vars); - $this->assertEquals($result, get_base_href()); - $_SERVER = $old_server; - } - - public function test_get_base_href(): void - { - // PHP_SELF should point to "the currently executing script - // relative to the document root" - $this->_tbh(["PHP_SELF" => "/index.php"], ""); - $this->_tbh(["PHP_SELF" => "/mydir/index.php"], "/mydir"); - - - // SCRIPT_FILENAME should point to "the absolute pathname of - // the currently executing script" and DOCUMENT_ROOT should - // point to "the document root directory under which the - // current script is executing" - $this->_tbh([ - "PHP_SELF" => "", - "SCRIPT_FILENAME" => "/var/www/html/mydir/index.php", - "DOCUMENT_ROOT" => "/var/www/html", - ], "/mydir"); - $this->_tbh([ - "PHP_SELF" => "", - "SCRIPT_FILENAME" => "/var/www/html/mydir/index.php", - "DOCUMENT_ROOT" => "/var/www/html/", - ], "/mydir"); - $this->_tbh([ - "PHP_SELF" => "", - "SCRIPT_FILENAME" => "/var/www/html/index.php", - "DOCUMENT_ROOT" => "/var/www/html", - ], ""); - } } diff --git a/core/tests/UrlsTest.php b/core/tests/UrlsTest.php index 918d4dbc..fd198529 100644 --- a/core/tests/UrlsTest.php +++ b/core/tests/UrlsTest.php @@ -5,74 +5,224 @@ declare(strict_types=1); namespace Shimmie2; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\Depends; require_once "core/urls.php"; class UrlsTest extends TestCase { - public function test_search_link(): void + /** + * An integration test for + * - search_link() + * - make_link() + * - _get_query() + * - get_search_terms() + */ + #[Depends("test_search_link")] + public function test_get_search_terms_from_search_link(): void { - $this->assertEquals( - "/test/post/list/bar%20foo/1", - search_link(["foo", "bar"]) - ); - $this->assertEquals( - "/test/post/list/cat%2A%20rating%3D%3F/1", - search_link(["rating=?", "cat*"]) - ); + /** + * @param array $vars + * @return array + */ + function _gst(array $terms): array + { + $pre = new PageRequestEvent("GET", _get_query(search_link($terms))); + $pre->page_matches("post/list"); + return $pre->get_search_terms(); + } + + global $config; + foreach([true, false] as $nice_urls) { + $config->set_bool('nice_urls', $nice_urls); + + $this->assertEquals( + ["bar", "foo"], + _gst(["foo", "bar"]) + ); + $this->assertEquals( + ["AC/DC"], + _gst(["AC/DC"]) + ); + $this->assertEquals( + ["cat*", "rating=?"], + _gst(["rating=?", "cat*"]), + ); + } } + #[Depends("test_get_base_href")] public function test_make_link(): void { - // basic + global $config; + foreach([true, false] as $nice_urls) { + $config->set_bool('nice_urls', $nice_urls); + + // basic + $this->assertEquals( + $nice_urls ? "/test/foo" : "/test/index.php?q=foo", + make_link("foo") + ); + + // remove leading slash from path + $this->assertEquals( + $nice_urls ? "/test/foo" : "/test/index.php?q=foo", + make_link("/foo") + ); + + // query + $this->assertEquals( + $nice_urls ? "/test/foo?a=1&b=2" : "/test/index.php?q=foo&a=1&b=2", + make_link("foo", "a=1&b=2") + ); + + // hash + $this->assertEquals( + $nice_urls ? "/test/foo#cake" : "/test/index.php?q=foo#cake", + make_link("foo", null, "cake") + ); + + // query + hash + $this->assertEquals( + $nice_urls ? "/test/foo?a=1&b=2#cake" : "/test/index.php?q=foo&a=1&b=2#cake", + make_link("foo", "a=1&b=2", "cake") + ); + } + } + + #[Depends("test_make_link")] + public function test_search_link(): void + { + global $config; + foreach([true, false] as $nice_urls) { + $config->set_bool('nice_urls', $nice_urls); + + $this->assertEquals( + $nice_urls ? "/test/post/list/bar%20foo/1" : "/test/index.php?q=post/list/bar%20foo/1", + search_link(["foo", "bar"]) + ); + $this->assertEquals( + $nice_urls ? "/test/post/list/AC%2FDC/1" : "/test/index.php?q=post/list/AC%2FDC/1", + search_link(["AC/DC"]) + ); + $this->assertEquals( + $nice_urls ? "/test/post/list/cat%2A%20rating%3D%3F/1" : "/test/index.php?q=post/list/cat%2A%20rating%3D%3F/1", + search_link(["rating=?", "cat*"]) + ); + } + } + + #[Depends("test_get_base_href")] + public function test_get_query(): void + { + // just validating an assumption that this test relies upon + $this->assertEquals(get_base_href(), "/test"); + $this->assertEquals( - "/test/foo", - make_link("foo") + "tasty/cake", + _get_query("/test/tasty/cake"), + 'http://$SERVER/$INSTALL_DIR/$PATH should return $PATH' + ); + $this->assertEquals( + "tasty/cake", + _get_query("/test/index.php?q=tasty/cake"), + 'http://$SERVER/$INSTALL_DIR/index.php?q=$PATH should return $PATH' ); - // remove leading slash from path $this->assertEquals( - "/test/foo", - make_link("/foo") + "tasty/cake%20pie", + _get_query("/test/index.php?q=tasty/cake%20pie"), + 'URL encoded paths should be left alone' + ); + $this->assertEquals( + "tasty/cake%20pie", + _get_query("/test/tasty/cake%20pie"), + 'URL encoded queries should be left alone' ); - // query $this->assertEquals( - "/test/foo?a=1&b=2", - make_link("foo", "a=1&b=2") + "", + _get_query("/test/"), + 'If just viewing install directory, should return /' + ); + $this->assertEquals( + "", + _get_query("/test/index.php"), + 'If just viewing index.php, should return /' ); - // hash $this->assertEquals( - "/test/foo#cake", - make_link("foo", null, "cake") + "post/list/tasty%2Fcake/1", + _get_query("/test/post/list/tasty%2Fcake/1"), + 'URL encoded niceurls should be left alone, even encoded slashes' ); - - // query + hash $this->assertEquals( - "/test/foo?a=1&b=2#cake", - make_link("foo", "a=1&b=2", "cake") + "post/list/tasty%2Fcake/1", + _get_query("/test/index.php?q=post/list/tasty%2Fcake/1"), + 'URL encoded uglyurls should be left alone, even encoded slashes' ); } + public function test_is_https_enabled(): void + { + $this->assertFalse(is_https_enabled(), "HTTPS should be disabled by default"); + + $_SERVER['HTTPS'] = "on"; + $this->assertTrue(is_https_enabled(), "HTTPS should be enabled when set to 'on'"); + unset($_SERVER['HTTPS']); + } + + public function test_get_base_href(): void + { + // PHP_SELF should point to "the currently executing script + // relative to the document root" + $this->assertEquals("", get_base_href(["PHP_SELF" => "/index.php"])); + $this->assertEquals("/mydir", get_base_href(["PHP_SELF" => "/mydir/index.php"])); + + // SCRIPT_FILENAME should point to "the absolute pathname of + // the currently executing script" and DOCUMENT_ROOT should + // point to "the document root directory under which the + // current script is executing" + $this->assertEquals("", get_base_href([ + "PHP_SELF" => "", + "SCRIPT_FILENAME" => "/var/www/html/index.php", + "DOCUMENT_ROOT" => "/var/www/html", + ]), "root directory"); + $this->assertEquals("/mydir", get_base_href([ + "PHP_SELF" => "", + "SCRIPT_FILENAME" => "/var/www/html/mydir/index.php", + "DOCUMENT_ROOT" => "/var/www/html", + ]), "subdirectory"); + $this->assertEquals("", get_base_href([ + "PHP_SELF" => "", + "SCRIPT_FILENAME" => "/var/www/html/index.php", + "DOCUMENT_ROOT" => "/var/www/html/", + ]), "trailing slash in DOCUMENT_ROOT root should be ignored"); + $this->assertEquals("/mydir", get_base_href([ + "PHP_SELF" => "", + "SCRIPT_FILENAME" => "/var/www/html/mydir/index.php", + "DOCUMENT_ROOT" => "/var/www/html/", + ]), "trailing slash in DOCUMENT_ROOT subdir should be ignored"); + } + + #[Depends("test_is_https_enabled")] + #[Depends("test_get_base_href")] public function test_make_http(): void { - // relative to shimmie install $this->assertEquals( "http://cli-command/test/foo", - make_http("foo") + make_http("foo"), + "relative to shimmie root" ); - - // relative to web server $this->assertEquals( "http://cli-command/foo", - make_http("/foo") + make_http("/foo"), + "relative to web server" ); - - // absolute $this->assertEquals( "https://foo.com", - make_http("https://foo.com") + make_http("https://foo.com"), + "absolute URL should be left alone" ); } diff --git a/core/tests/UtilTest.php b/core/tests/UtilTest.php index deaeca5c..d622cc2d 100644 --- a/core/tests/UtilTest.php +++ b/core/tests/UtilTest.php @@ -162,35 +162,4 @@ class UtilTest extends TestCase path_to_tags("/category:/tag/baz.jpg") ); } - - public function test_get_query(): void - { - // niceurls - $_SERVER["REQUEST_URI"] = "/test/tasty/cake"; - $this->assertEquals("/tasty/cake", _get_query()); - - // no niceurls - $_SERVER["REQUEST_URI"] = "/test/index.php?q=/tasty/cake"; - $this->assertEquals("/tasty/cake", _get_query()); - - // leave url encoding alone - $_SERVER["REQUEST_URI"] = "/test/index.php?q=/tasty/cake%20pie"; - $this->assertEquals("/tasty/cake%20pie", _get_query()); - - // if just viewing index.php - $_SERVER["REQUEST_URI"] = "/test/index.php"; - $this->assertEquals("/", _get_query()); - - // niceurl root - $_SERVER["REQUEST_URI"] = "/test/"; - $this->assertEquals("/", _get_query()); - - // niceurls with encoded slashes - $_SERVER["REQUEST_URI"] = "/test/post/list/tasty%2Fcake/1"; - $this->assertEquals("/post/list/tasty%2Fcake/1", _get_query()); - - // query string with encoded slashes - $_SERVER["REQUEST_URI"] = "/test/index.php?q=/post/list/tasty%2Fcake/1"; - $this->assertEquals("/post/list/tasty%2Fcake/1", _get_query()); - } } diff --git a/core/urls.php b/core/urls.php index b74580e6..7851fc50 100644 --- a/core/urls.php +++ b/core/urls.php @@ -41,7 +41,8 @@ function search_link(array $terms = [], int $page = 1): string * Figure out the correct way to link to a page, taking into account * things like the nice URLs setting. * - * eg make_link("foo/bar") becomes "/v2/index.php?q=foo/bar" + * eg make_link("foo/bar") becomes either "/v2/foo/bar" (niceurls) or + * "/v2/index.php?q=foo/bar" (uglyurls) */ function make_link(?string $page = null, ?string $query = null, ?string $fragment = null): string { @@ -66,6 +67,106 @@ function make_link(?string $page = null, ?string $query = null, ?string $fragmen return unparse_url($parts); } +/** + * Figure out the current page from a link that make_link() generated + * + * SHIT: notes for the future, because the web stack is a pile of hacks + * + * - According to some specs, "/" is for URL dividers with heiracial + * significance and %2F is for slashes that are just slashes. This + * is what shimmie currently does - eg if you search for "AC/DC", + * the shimmie URL will be /post/list/AC%2FDC/1 + * - According to some other specs "/" and "%2F" are identical... + * - PHP's $_GET[] automatically urldecodes the inputs so we can't + * tell the difference between q=foo/bar and q=foo%2Fbar + * - REQUEST_URI contains the exact URI that was given to us, so we + * can parse it for ourselves + * - generates + * q=post%2Flist + * + * This function should always return strings with no leading slashes + */ +function _get_query(?string $uri = null): string +{ + $parsed_url = parse_url($uri ?? $_SERVER['REQUEST_URI']); + + // if we're looking at http://site.com/$INSTALL_DIR/index.php, + // then get the query from the "q" parameter + if(($parsed_url["path"] ?? "") == (get_base_href() . "/index.php")) { + // $q = $_GET["q"] ?? ""; + // default to looking at the root + $q = ""; + // (we need to manually parse the query string because PHP's $_GET + // does an extra round of URL decoding, which we don't want) + foreach(explode('&', $parsed_url['query'] ?? "") as $z) { + $qps = explode('=', $z, 2); + if(count($qps) == 2 && $qps[0] == "q") { + $q = $qps[1]; + } + } + } + + // if we're looking at http://site.com/$INSTALL_DIR/$PAGE, + // then get the query from the path + else { + $q = substr($parsed_url["path"] ?? "", strlen(get_base_href() . "/")); + } + + assert(!str_starts_with($q, "/")); + return $q; +} + +/** + * Figure out the path to the shimmie install directory. + * + * eg if shimmie is visible at https://foo.com/gallery, this + * function should return /gallery + * + * PHP really, really sucks. + * + * This function should always return strings with no trailing + * slashes, so that it can be used like `get_base_href() . "/data/asset.abc"` + * + * @param array|null $server_settings + */ +function get_base_href(?array $server_settings = null): string +{ + if (defined("BASE_HREF") && !empty(BASE_HREF)) { + return BASE_HREF; + } + $server_settings = $server_settings ?? $_SERVER; + if(str_ends_with($server_settings['PHP_SELF'], 'index.php')) { + $self = $server_settings['PHP_SELF']; + } elseif(isset($server_settings['SCRIPT_FILENAME']) && isset($server_settings['DOCUMENT_ROOT'])) { + $self = substr($server_settings['SCRIPT_FILENAME'], strlen(rtrim($server_settings['DOCUMENT_ROOT'], "/"))); + } else { + die("PHP_SELF or SCRIPT_FILENAME need to be set"); + } + $dir = dirname($self); + $dir = str_replace("\\", "/", $dir); + $dir = rtrim($dir, "/"); + return $dir; +} + +/** + * The opposite of the standard library's parse_url + * + * @param array $parsed_url + */ +function unparse_url(array $parsed_url): string +{ + $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; + $host = $parsed_url['host'] ?? ''; + $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; + $user = $parsed_url['user'] ?? ''; + $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; + $pass = ($user || $pass) ? "$pass@" : ''; + $path = $parsed_url['path'] ?? ''; + $query = !empty($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; + $fragment = !empty($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; + return "$scheme$user$pass$host$port$path$query$fragment"; +} + /** * Take the current URL and modify some parameters diff --git a/core/util.php b/core/util.php index 2fe46641..2055ff84 100644 --- a/core/util.php +++ b/core/util.php @@ -713,37 +713,6 @@ function _get_user(): User return $my_user; } -function _get_query(): string -{ - // if q is set in POST, use that - if(isset($_POST["q"])) { - return $_POST["q"]; - } - - // if q is set in GET, use that - // (we need to manually parse the query string because PHP's $_GET - // does an extra round of URL decoding, which we don't want) - $parts = parse_url($_SERVER['REQUEST_URI']); - $qs = []; - foreach(explode('&', $parts['query'] ?? "") as $z) { - $qps = explode('=', $z, 2); - if(count($qps) == 2) { - $qs[$qps[0]] = $qps[1]; - } - } - if(isset($qs["q"])) { - return $qs["q"]; - } - - // if we're just looking at index.php, use the default query - if(str_ends_with($parts["path"] ?? "", "index.php")) { - return "/"; - } - - // otherwise, use the request URI minus the base path - return substr($parts["path"] ?? "", strlen(get_base_href())); -} - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ * HTML Generation * diff --git a/ext/setup/main.php b/ext/setup/main.php index 1b185ce9..2cbb9a3b 100644 --- a/ext/setup/main.php +++ b/ext/setup/main.php @@ -319,6 +319,13 @@ class Setup extends Extension { global $config, $page, $user; + if ($event->page_matches("nicedebug")) { + $page->set_mode(PageMode::DATA); + $page->set_data(json_encode_ex([ + "args" => $event->args, + ])); + } + if ($event->page_matches("nicetest")) { $page->set_mode(PageMode::DATA); $page->set_data("ok");