From c11af12551e44144c21e4f24628f63efcf81c188 Mon Sep 17 00:00:00 2001 From: Christophe HENRY Date: Sun, 11 Apr 2021 21:35:55 +0200 Subject: [PATCH] Adds the url path * The /htmgem directory can be renamed and moved. * Everything works without URL rewriting enabled. --- css/base.css | 27 ++++++++++++-- css/htmgem.css | 41 ++++++++++++++------ index.php | 49 +++++++++++++++--------- lib-htmgem.inc.php | 93 +++++++++++++++++++++++++++++++--------------- lib-html.inc.php | 79 ++++++++++++++++++++++++++++++++------- tests/miscTest.php | 80 ++++++++++++++++++++++++++++++--------- 6 files changed, 276 insertions(+), 93 deletions(-) diff --git a/css/base.css b/css/base.css index a925733..ef6993f 100644 --- a/css/base.css +++ b/css/base.css @@ -2,7 +2,7 @@ html { font-family: sans-serif; } -body { +#gmi { max-width: 1024px; margin: auto; margin-top: 0.5em; @@ -33,13 +33,26 @@ a { text-decoration: none; } -a { - text-decoration: none; +.menu:nth-of-type(1) .menu-line { + text-align: left; +} +.menu:nth-of-type(3) .menu-line { + text-align: right; +} + +.menu a, .menu a:visited { + #color: #888; +} +.menu a:hover { + #color: #000; +} +.menu hr { + border: 1px solid lightgrey; } @media only screen and (max-width: 1024px) { body { - margin: 3rem; + margin: 0.5rem 3rem; } h1 { font-size: 4rem; @@ -53,4 +66,10 @@ a { p, pre, ul, blockquote { font-size: 2.6rem; } + .menu { + font-size: 2rem; + } + .menu hr { + border: 1px solid gray; + } } diff --git a/css/htmgem.css b/css/htmgem.css index d1db5fb..8949208 100644 --- a/css/htmgem.css +++ b/css/htmgem.css @@ -17,47 +17,66 @@ blockquote { padding: 1rem; } -a { - margin: -1.35rem; +.menu a, .menu a:visited { + color: #888; +} + +.menu a:hover { + color: #000; +} + +.menu hr { + color: white; +} + +#gmi a { + margin: -0.7rem; color:#820; } -a:before { +#gmi a:before { content: "๐Ÿ”— "; } -a:visited { +#gmi a:visited { color: #868; } -a.local:before { +#gmi a.local:before { content: "๐Ÿ›ฉ๏ธ "; font-weight: bold; } -a.gemini:before { +#gmi a.gemini:before { content: "๐Ÿš€ "; } -a.gopher:before { +#gmi a.gopher:before { content: "๐Ÿ“œ "; } -a.https:before { +#gmi a.https:before { content: "๐Ÿ•ธ๏ธ "; font-weight: bolder; } -a.http:before { +#gmi a.http:before { content: "๐Ÿ•ธ๏ธ "; font-weight: lighter; } -a.mumble:before { +#gmi a.mumble:before { content: "๐ŸŽค "; } -a.mailto:before { +#gmi a.mailto:before { content: "โœ‰๏ธ "; } +@media only screen and (max-width: 1024px) { + + #gmi a { + margin: -2.9rem; + } +} + diff --git a/index.php b/index.php index cc4b7a9..2f85b0f 100644 --- a/index.php +++ b/index.php @@ -4,7 +4,11 @@ require_once "lib-htmgem.inc.php"; require_once "lib-html.inc.php"; require_once "lib-io.inc.php"; -# The url argument is always absolute compared to the document root. +$documentRoot = $_SERVER['DOCUMENT_ROOT']; +$scheme = (@$_SERVER['REQUEST_SCHEME']??"http")."://"; +$domain = $_SERVER['HTTP_HOST']; +$php_self = $_SERVER['PHP_SELF']; // by default: /htmgem/index.php +$php_self_dir = dirname($php_self); $url = @$_REQUEST["url"]; $urlRewriting = @$_REQUEST["rw"]=="1"; @@ -18,18 +22,21 @@ $urlRewriting = @$_REQUEST["rw"]=="1"; if (empty($url)) { if (!file_exists("index.gmi")) { http_response_code(403); - die(""); + } else { + $gt_html = new \htmgem\GemTextTranslate_html(file_get_contents("index.gmi"), true, "$php_self?url=", $php_self_dir); + if (empty($gt_html->getCss)) $gt_html->addCss($php_self_dir."/css/htmgem.css"); + + // No URL Rewritting assumed + echo \htmgem\html\getHtmlWithMenu($gt_html, $scheme, $domain, $php_self, "$php_self?url="); } - $gt_html = new \htmgem\GemTextTranslate_html(@file_get_contents("index.gmi"), true, "/htmgem"); - echo \htmgem\html\getFullHtml($gt_html); exit(); } -$documentRoot = $_SERVER['DOCUMENT_ROOT']; - -/** - * Provides index.gmi if no page given - */ +$url = \htmgem\resolve_path( + // Some webservers (Apache) don't add the slash + // while others (Nginx) doโ€ฆ + ( $url[0] == "/" ? "" : "/" ) . $url +); if (!preg_match("/\.gmi$/", $url)) { if ($url[-1] == "/") $url = $url."index.gmi"; @@ -55,9 +62,13 @@ switch(true) { if ($go404) { error_log("HtmGem: 404 $url $filePath"); http_response_code(404); - $page404 = \htmgem\html\get404GmiPage("Page not found", $url); + $page404 = \htmgem\html\get404GmiPage($url); $gt_html = new \htmgem\GemTextTranslate_html($page404); - echo \htmgem\html\getFullHtml($gt_html); + if (empty($gt_html->getCss)) $gt_html->addCss($php_self_dir."/css/htmgem.css"); + if ($urlRewriting) + echo \htmgem\html\getHtmlWithMenu($gt_html, $scheme, $domain, $url); + else + echo \htmgem\html\getHtmlWithMenu($gt_html, $scheme, $domain, $url, "$php_self?url="); exit(); } @@ -101,12 +112,12 @@ EOL; } if ($urlRewriting) - $baseUrl = null; + $gt_html = new \htmgem\GemTextTranslate_html($fileContents, $gt_htmlextDecoration); else - $baseUrl = dirname($url); -$gt_html = new \htmgem\GemTextTranslate_html($fileContents, $gt_htmlextDecoration, $baseUrl); + $gt_html = new \htmgem\GemTextTranslate_html($fileContents, $gt_htmlextDecoration, "$php_self?url=", dirname($url)); + if ("none" == $style) { - $gt_html->addCss(""); + #$gt_html->addCss(""); } elseif ("/" == @$style[0]) { $gt_html->addCss($style); } elseif (empty($style)) { @@ -119,9 +130,13 @@ if ("none" == $style) { $gt_html->addCss($localCss); } } else { #TODO: regex check for $style - $gt_html->addCss("/htmgem/css/$style.css"); + $gt_html->addCss("$php_self_dir/css/$style.css"); } +if (empty($gt_html->getCss)) $gt_html->addCss($php_self_dir."/css/htmgem.css"); -echo \htmgem\html\getFullHtml($gt_html); +if ($urlRewriting) + echo \htmgem\html\getHtmlWithMenu($gt_html, $scheme, $domain, $url); +else + echo \htmgem\html\getHtmlWithMenu($gt_html, $scheme, $domain, $url, "$php_self?url="); ?> diff --git a/lib-htmgem.inc.php b/lib-htmgem.inc.php index cc305ec..dea91a6 100644 --- a/lib-htmgem.inc.php +++ b/lib-htmgem.inc.php @@ -5,6 +5,55 @@ namespace htmgem; mb_internal_encoding("UTF-8"); mb_regex_encoding("UTF-8"); +/** + * Resolve $path interpretating / . and .. + * @param $path str + * @returns "/" if .. goes above the limit + */ +function resolve_path($path) { + if (empty($path)) return ""; + $absolute = "/"==$path[0]; + $parts = array_filter(explode("/", $path), 'strlen'); + $chuncks = array(); + foreach ($parts as $part) { + if ('.' == $part) continue; + if ('..' == $part) { + if (is_null(array_pop($chuncks))) return "/"; + } else { + $chuncks[] = $part; + } + } + $output = implode("/", $chuncks); + if ($absolute) $output = "/".$output; + return $output; +} + +/** + * Splits link (without .. or .) into parts along with direct url access. + * @param url + * + * Ex. /dir1/dir2/page.gmi + * --> "dir1" --> "/dir1" + * --> "dir2" --> "/dir1/dir2" + * --> "page.gmi" --> "/dir2/page.gmi" + */ +function split_path_links($path, $prefix="") { + $parts = array_filter(explode("/", $path), 'strlen'); + if (empty($parts)) return array(); + if ("/"==$path[0]) + $stack = "/"; + else + $stack = ""; + $output = array(); + $slash = ""; + foreach ($parts as $part) { + $stack .= $slash.$part; + $output[$part] = $prefix.$stack; + $slash = "/"; + } + return $output; +} + /** * Parses the gemtext and generates the internal format version * @param str $fileContents the gemtext to parse @@ -12,7 +61,7 @@ mb_regex_encoding("UTF-8"); function gemtextParser($fileContents) { if (empty($fileContents)) return array(); $fileContents = rtrim($fileContents); // removes last empty line - $fileLines = mb_split("\R", $fileContents); // Unix, Mac, Windows line feeds + $fileLines = mb_split("\n|\r\n?", $fileContents); // Unix, Mac, Windows line feeds $mode = null; $current = array(); foreach ($fileLines as $line) { @@ -182,8 +231,15 @@ class GemtextTranslate_html { protected $pageTitle = ""; public $translatedGemtext; - function __construct($parsedGemtext, $textDecoration=true, $baseUrl=Null) { - $this->baseUrl = $baseUrl; + /** + * @param $parsedGemtext the gemtext internal format + * @param $textDecoration bool to interpret or not the text decoration + * @param $urlPrefix the prefix to prepend if the URL rewriting is not on + * @param $currentPageDir the current directory, to be used without URL rewriting + */ + function __construct($parsedGemtext, $textDecoration=true, $urlPrefix=null, $currentPageDir=null) { + $this->urlPrefix = $urlPrefix; + $this->currentPageDir = $currentPageDir; if (empty($parsedGemtext)) $parsedGemtext = ""; // to delete the last empty lines $parsedGemtext = rtrim($parsedGemtext); @@ -266,29 +322,6 @@ class GemtextTranslate_html { $text = preg_replace("/ +/", " ", $text); } - /** - * Resolve $path interpretating / . and .. - * @param $path str - * @returns "/" if .. goes above the limit - */ - public static function resolve_path($path) { - if (empty($path)) return ""; - $absolute = "/"==$path[0]; - $parts = array_filter(explode("/", $path), 'strlen'); - $chuncks = array(); - foreach ($parts as $part) { - if ('.' == $part) continue; - if ('..' == $part) { - if (is_null(array_pop($chuncks))) return "/"; - } else { - $chuncks[] = $part; - } - } - $output = implode("/", $chuncks); - if ($absolute) $output = "/".$output; - return $output; - } - public function translate($textDecoration=true) { $output = ""; foreach ($this->parsedGemtext as $node) { @@ -344,10 +377,10 @@ class GemtextTranslate_html { preg_match("/^([^:]+):/", $link, $matches); $protocol = @$matches[1]??"local"; if ("local"==$protocol) { - if (!is_null($this->baseUrl)) { // No URL rewriting - if ($link[0]!="/") $link = "{$this->baseUrl}/$link"; - $link = self::resolve_path($link); - $link = "/htmgem/index.php?url=$link"; + if (!is_null($this->urlPrefix)) { // No URL rewriting + $link = $this->currentPageDir."/".$link; + $link = resolve_path($link); + $link = $this->urlPrefix.$link; } $newWindow = ""; } else { diff --git a/lib-html.inc.php b/lib-html.inc.php index ec04522..ca98040 100644 --- a/lib-html.inc.php +++ b/lib-html.inc.php @@ -5,12 +5,10 @@ namespace htmgem\html; mb_internal_encoding("UTF-8"); mb_regex_encoding("UTF-8"); -/** - * Returns a full HTML page base - */ -function getFullHtml(\htmgem\GemtextTranslate_html $gt_html) { +$txt_icon = "Hอœอกm "; + +function getHeader(\htmgem\GemtextTranslate_html $gt_html) { $css = $gt_html->getCss(); - if (!$css) $css = array("/htmgem/css/htmgem.css"); $output = << @@ -23,21 +21,76 @@ EOL; } $output .= << -\n -EOL; - $output .= $gt_html->translatedGemtext; - $output .= "\n\n"; +EOL; return $output; } -function get404Gmipage($message, $url) { +function array_key_last_slice($array) { + // array_key_last() only available as of php v7.3.0 + return key(array_slice($array, -1)); +} + +/** + * @param $url the full URL to display + * @param $pageLink if not null, means no URL rewritting + */ +function getMenu(string $scheme, string $domain, string $path, string $prefix=null) { + global $txt_icon; + $links = \htmgem\split_path_links($path, $prefix); + + // Removes the last part, as it won't hold a link + $lastLink = array_key_last_slice($links); + if ("index.gmi"==$lastLink) { + // removes the index page + array_pop($links); + $lastLink = array_key_last_slice($links); + } + array_pop($links); + + $links = array($domain => "$prefix/") + $links; + $linkList = array(); + foreach ($links as $label=>$link) { + $linkList []= "$label\n"; + } + $linkList [] = $lastLink."\n"; // The last part holds no link + $output = "\n"; + return $output; +} + +function getFooter(\htmgem\GemtextTranslate_html $gt_html) { + return "\n\n"; +} + +function getHtmlWithMenu($gt_html, $scheme, $domain, $path, $prefix=null) { + $menu = getMenu($scheme, $domain, $path, $prefix); + echo getHeader($gt_html); + echo "\n"; + echo "\n"; + echo "
\n"; + echo $gt_html->translatedGemtext; + echo "
\n"; + echo "\n"; + echo getFooter($gt_html); +} + +function get404Gmipage($url) { return << .. ๐Ÿ”„ ๐Ÿ”„ EOF; } diff --git a/tests/miscTest.php b/tests/miscTest.php index beb607f..2327c7a 100644 --- a/tests/miscTest.php +++ b/tests/miscTest.php @@ -5,89 +5,133 @@ require_once dirname(__FILE__)."/../lib-htmgem.inc.php"; final class miscTest extends TestCase { - public function test_resolveLink(): void { + public function test_split_path_links(): void { $this->assertSame( - \htmgem\GemtextTranslate_html::resolve_path(""), + array(), + \htmgem\split_path_links(""), + "empty link" + ); + $this->assertSame( + array( + "noslash" => "noslash", + ), + \htmgem\split_path_links("noslash"), + "no slash" + ); + $this->assertSame( + array(), + \htmgem\split_path_links("/"), + "only a slash" + ); + $this->assertSame( + array( + "one" => "/one", + ), + \htmgem\split_path_links("/one"), + "/one" + ); + $this->assertSame( + array( + "one" => "one", + "two" => "one/two", + ), + \htmgem\split_path_links("one/two"), + "one/two" + ); + $this->assertSame( + array( + "one" => "/one", + "two" => "/one/two", + "file.ext" => "/one/two/file.ext", + ), + \htmgem\split_path_links("/one/two/file.ext"), + "/one/two/file.ext" + ); + } + + public function test_resolve_path(): void { + $this->assertSame( + \htmgem\resolve_path(""), "", "empty link" ); $this->assertSame( - \htmgem\GemtextTranslate_html::resolve_path("test"), + \htmgem\resolve_path("test"), "test", "single word" ); $this->assertSame( - \htmgem\GemtextTranslate_html::resolve_path(" "), + \htmgem\resolve_path(" "), " ", "single space" ); $this->assertSame( - \htmgem\GemtextTranslate_html::resolve_path(" A B "), + \htmgem\resolve_path(" A B "), " A B ", "several space" ); $this->assertSame( - \htmgem\GemtextTranslate_html::resolve_path("/"), + \htmgem\resolve_path("/"), "/", "one slash" ); $this->assertSame( - \htmgem\GemtextTranslate_html::resolve_path("//"), + \htmgem\resolve_path("//"), "/", "two slashes" ); $this->assertSame( - \htmgem\GemtextTranslate_html::resolve_path("/////"), + \htmgem\resolve_path("/////"), "/", "five slashes" ); $this->assertSame( - \htmgem\GemtextTranslate_html::resolve_path("one/"), + \htmgem\resolve_path("one/"), "one", "strip the last slash" ); $this->assertSame( - \htmgem\GemtextTranslate_html::resolve_path("/two"), + \htmgem\resolve_path("/two"), "/two", "slash at the beginning" ); $this->assertSame( - \htmgem\GemtextTranslate_html::resolve_path("/two/"), + \htmgem\resolve_path("/two/"), "/two", "slash at the beginning and the end" ); $this->assertSame( - \htmgem\GemtextTranslate_html::resolve_path("one/two/"), + \htmgem\resolve_path("one/two/"), "one/two", "only the last slash remains" ); $this->assertSame( - \htmgem\GemtextTranslate_html::resolve_path("one/two/three//"), + \htmgem\resolve_path("one/two/three//"), "one/two/three", "strip the last slashes" ); $this->assertSame( - \htmgem\GemtextTranslate_html::resolve_path("one/../"), + \htmgem\resolve_path("one/../"), "", "empty one" ); $this->assertSame( - \htmgem\GemtextTranslate_html::resolve_path("one/two/../"), + \htmgem\resolve_path("one/two/../"), "one", "empty one two" ); $this->assertSame( - \htmgem\GemtextTranslate_html::resolve_path("one/two/../.."), + \htmgem\resolve_path("one/two/../.."), "", "empty one two twice" ); $this->assertSame( - \htmgem\GemtextTranslate_html::resolve_path("one/../two/./../three"), + \htmgem\resolve_path("one/../two/./../three"), "three", "waltz" ); $this->assertSame( - \htmgem\GemtextTranslate_html::resolve_path("one/../.."), + \htmgem\resolve_path("one/../.."), "/", "directory traversal" );