fix styles and build options, add error handling, start StaticFileManager implementation
PGP Signature
-----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEkFh6dA+k/6CXFXU4O3+8IhROY5gFAmKhgWUACgkQO3+8IhRO Y5iHbQ//VfA9t5wdf23xXv02FU18RfswS9rnk0cKT1VEYSzY0nYloYXlxygdZ/Jv uLD+7Dc8ismtHCvAtU9S0sDER0QR5USuFs1GyejWc3jq66EVHvz8NMDrR0LmT9sW iu2xVnM5Mgf9SNrLqZWQs429qtw1z+dLAz4zdedW//XP2JX783PnzLeji+qj8RMr jI9j7Qjb8Rn2jSzLhp+M4utqvILonmIZoE3BNHXoS91V8jg23R7JM+Xasz4Wt8y5 E0Go6pCSau3Q+8qowLLxSOxevlgIDGNFmMZqbNRcEGRaetAlfPHb979y2dCfvaym TbYQN5l+jojKzfss5UDkoeYnI8Ic0NhijIyLCZ6BpDgpg8IVxCw0R/1TcgZ86ESw T5b9J7htp5vg8/jrOMVbIUNnPLbsfHikcSIrQy5OLqZrzZ4HxBT7BkUV9up0qX9E iPLCtSJazf58VvU3CVm4fI6FVkkXfsxSULRzdTw5uxiDhciZXcuqULXVAqcfAjBq JHegysYUtPc3ZmsvV+ybi2yDdnMvnVmMajKKwvl7z5Faqyj7QpeMHHAsUQx/FfOn uKrV/rQYRYryAP8TYIiQ+rj3XUGrD5T960PO2NJA+8p7Uw+nyOX735YD0la/j7lb vvgXF0t1w1mVyJBu/F1+YSqzK0XFJzgrO+yYsN8/1XXJ5z1WkKc= =M89I -----END PGP SIGNATURE-----
@@ -1,8 +1,19 @@
package archetype +import ( + "time" +) + type BuildStatus struct { Success bool Message string +} + +type Page struct { + Title string + Content string + Edited time.Time + Error string } type ConfigOption struct {@@ -18,7 +29,7 @@ BuildOptions() []string
GetConfig() map[ConfigOption]string SetConfig(map[ConfigOption]string) error ListPages() map[string]string - GetPage(string) (Page, error) + GetPage(string) Page FormatPage(string) string FormattingHelp() string CreatePage(slug, title, content string) error
@@ -72,16 +72,25 @@ }
return pages } -func (self *EurekaAdapter) GetPage(filename string) (Page, error) { +func (self *EurekaAdapter) GetPage(filename string) Page { + if strings.Contains(filename, "../") || strings.Contains(filename, "..\\") { + return Page{ + Error: "You cannot escape!", + } + } fullPath := filepath.Join(self.Root, "inc", filename) f, err := os.ReadFile(fullPath) if err != nil { - return Page{}, err + return Page{ + Error: err.Error(), + } } if !strings.HasSuffix(filename, ".htm") { - return Page{}, errors.New("Page file extension is not '.htm'") + return Page{ + Error: "Page file extension is not '.htm'", + } } title := strings.Replace(@@ -93,7 +102,7 @@ return Page{
Title: title, Content: content, Edited: fileInfo.ModTime(), - }, nil + } } func (self *EurekaAdapter) FormatPage(raw string) string {@@ -110,6 +119,10 @@ func (self *EurekaAdapter) CreatePage(slug, title, content string) error {
// eureka creates titles from slugs, so we transform the title into the slug slug = strings.ReplaceAll(title, " ", "_") + ".htm" path := filepath.Join(self.Root, "inc", slug) + + if strings.Contains(slug, "../") || strings.Contains(slug, "..\\") { + return errors.New("You cannot escape!") + } _, err := os.Stat(path) if err == nil || !os.IsNotExist(err) {@@ -127,6 +140,12 @@
func (self *EurekaAdapter) SavePage(oldSlug, newSlug, title, content string) error { // eureka creates titles from slugs, so we transform the title into the slug newSlug = strings.ReplaceAll(title, " ", "_") + ".htm" + + if strings.Contains(newSlug, "../") || strings.Contains(newSlug, "..\\") || + strings.Contains(oldSlug, "../") || strings.Contains(oldSlug, "..\\") { + return errors.New("You cannot escape!") + } + f, err := os.Create(filepath.Join(self.Root, "inc", newSlug)) if err != nil { return err@@ -151,6 +170,10 @@ return nil
} func (self *EurekaAdapter) DeletePage(slug string) error { + if strings.Contains(slug, "../") || strings.Contains(slug, "..\\") { + return errors.New("You cannot escape!") + } + siteRoot := self.Config[ConfigOption{ Name: "SITEROOT", Type: "string",@@ -164,14 +187,14 @@ return os.Remove(filepath.Join(self.Root, "inc", slug))
} func (self *EurekaAdapter) Build(buildOptions map[string][]string) BuildStatus { - - twtxt := buildOptions["twtxt"][0] - cmdArgs := "" + twtxt := strings.Join(buildOptions["twtxt"], " ") + cmdArgs := []string{} if twtxt != "" { - cmdArgs += "-t " + twtxt + cmdArgs = append(cmdArgs, "-t") + cmdArgs = append(cmdArgs, twtxt) } - cmd := exec.Command("./build.sh", cmdArgs) + cmd := exec.Command("./build.sh", cmdArgs...) cmd.Dir = self.Root out, err := cmd.CombinedOutput()
@@ -1,11 +0,0 @@
-package archetype - -import ( - "time" -) - -type Page struct { - Title string - Content string - Edited time.Time -}
@@ -0,0 +1,16 @@
+package archetype + +type StaticFileManager struct { + Root string + ShowHtml bool + ShowHidden bool +} + +type FileManager interface { + Init(cfg Config) error + ListTree() []string + ListSubTree(root string) []string + AddFile(path string, file interface{}) error + MkDir(path string) error + Remove(path string) error +}
@@ -3,9 +3,9 @@
import ( "net/http" core "nilfm.cc/git/nirvash/archetype" - shell "nilfm.cc/git/nirvash/lfo" + . "nilfm.cc/git/nirvash/lfo" "nilfm.cc/git/quartzgun/indentalUserDB" - "nilfm.cc/git/quartzgun/middleware" + . "nilfm.cc/git/quartzgun/middleware" "nilfm.cc/git/quartzgun/renderer" "nilfm.cc/git/quartzgun/router" "os"@@ -40,14 +40,14 @@
rtr.Get("/login", renderer.Template( pathConcat(templateRoot, "login.html"))) - rtr.Post("/login", middleware.Authorize("/", udb, "/login?tryagain=1")) + rtr.Post("/login", Authorize("/", udb, "/login?tryagain=1")) - rtr.Get("/logout", middleware.Bunt("/", udb, "/login?tryagain=1")) + rtr.Get("/logout", Bunt("/", udb, "/login?tryagain=1")) rtr.Get( "/", - middleware.Protected( - shell.WithAdapter( + Protected( + WithAdapter( renderer.Template( pathConcat(templateRoot, "cms_list.html"), pathConcat(templateRoot, "header.html"),@@ -59,9 +59,9 @@ "/login"))
rtr.Get( `/edit/(?P<Slug>\S+)`, - middleware.Fortify( - middleware.Protected( - shell.WithAdapter( + Fortify( + Protected( + WithAdapter( renderer.Template( pathConcat(templateRoot, "cms_edit.html"), pathConcat(templateRoot, "header.html"),@@ -73,10 +73,10 @@ "/login")))
rtr.Post( `/save/(?P<Slug>\S+)`, - middleware.Defend( - middleware.Protected( - shell.WithAdapter( - shell.EnsurePageData( + Defend( + Protected( + WithAdapter( + EnsurePageData( renderer.Template( pathConcat(templateRoot, "cms_save.html"), pathConcat(templateRoot, "header.html"),@@ -91,9 +91,9 @@ "/"))
rtr.Get( `/new`, - middleware.Fortify( - middleware.Protected( - shell.WithAdapter( + Fortify( + Protected( + WithAdapter( renderer.Template( pathConcat(templateRoot, "cms_new.html"), pathConcat(templateRoot, "header.html"),@@ -105,10 +105,10 @@ "/login")))
rtr.Post( `/create`, - middleware.Defend( - middleware.Protected( - shell.WithAdapter( - shell.EnsurePageData( + Defend( + Protected( + WithAdapter( + EnsurePageData( renderer.Template( pathConcat(templateRoot, "cms_create.html"), pathConcat(templateRoot, "header.html"),@@ -123,9 +123,9 @@ "/"))
rtr.Get( `/build`, - middleware.Fortify( - middleware.Protected( - shell.WithAdapter( + Fortify( + Protected( + WithAdapter( renderer.Template( pathConcat(templateRoot, "build.html"), pathConcat(templateRoot, "header.html"),@@ -137,10 +137,10 @@ "/login")))
rtr.Post( `/build-run`, - middleware.Defend( - middleware.Protected( - shell.SanitizeFormMap( - shell.WithAdapter( + Defend( + Protected( + SanitizeFormMap( + WithAdapter( renderer.Template( pathConcat(templateRoot, "build_run.html"), pathConcat(templateRoot, "header.html"),@@ -154,9 +154,9 @@ "/"))
rtr.Post( `/delete/(?P<Slug>\S+)`, - middleware.Defend( - middleware.Protected( - shell.WithAdapter( + Defend( + Protected( + WithAdapter( renderer.Template( pathConcat(templateRoot, "delete.html"), pathConcat(templateRoot, "header.html"),@@ -170,9 +170,9 @@ "/"))
rtr.Get( `/config`, - middleware.Fortify( - middleware.Protected( - shell.WithAdapter( + Fortify( + Protected( + WithAdapter( renderer.Template( pathConcat(templateRoot, "config.html"), pathConcat(templateRoot, "header.html"),@@ -184,11 +184,11 @@ "/login")))
rtr.Post( `/config-set`, - middleware.Defend( - middleware.Protected( - shell.SanitizeFormMap( - shell.FormMapToAdapterConfig( - shell.WithAdapter( + Defend( + Protected( + SanitizeFormMap( + FormMapToAdapterConfig( + WithAdapter( renderer.Template( pathConcat(templateRoot, "config_set.html"), pathConcat(templateRoot, "header.html"),
@@ -3,7 +3,6 @@ padding: 0;
margin: 0; font-family: sans-serif; font-size: 16px; - height: 100vh; background: black; color: white; background: url('/static/bg2.png');@@ -16,6 +15,7 @@ .login-body {
background: url('/static/bg.png'); background-size: cover; background-position: center center; + height: 100vh; } .login {@@ -48,7 +48,7 @@ position: absolute;
} -.login form input, .login form input:-internal-autofill-selected { +.login form input, #user-input, #password-input { display: block; margin: 1em; margin-left: auto;@@ -60,7 +60,7 @@ color: lightgray;
padding: 0.2em; } -.login form input[type="text"], login form input[type="password"] { +.login form input[type="text"], .login form input[type="password"] { transition: border 1s; outline: none; margin-bottom: -17px;@@ -73,7 +73,6 @@ }
.login form input[type="submit"] { - margin-top: -17px; text-transform: uppercase; transition: background 1s, color 1s; }@@ -84,7 +83,7 @@ color: black;
} .login .error { - positon: relative; + position: relative; text-align: center; margin-left: auto; margin-right: auto;@@ -109,11 +108,12 @@ }
nav { text-align: center; - background: rgba(0,0,0,0.8); - padding-top: 0.5em; + background: rgba(0,0,0,0.8); + padding-top: 0.5em; padding-bottom: 0.5em; position: sticky; top: 0px; + z-index: 3; } nav ul {@@ -136,9 +136,23 @@ a:hover {
color: cyan; } -a.new-page-button { +.new-page-button-wrapper { + display: block; + width: 80%; + max-width: 500px; position: sticky; - top: 0; + top: 5em; + margin-left: auto; + margin-right: auto; + z-index: 2; + text-align: right; + height: 0; + overflow-y: visible; +} + +a.new-page-button { + position: relative; + top: 1em; text-decoration: none; background: transparent; border: solid 2px lightgray;@@ -147,8 +161,6 @@ color: lightgray;
padding: 0.2em; text-transform: uppercase; transition: background 1s, color 1s; - float: right; - z-index: 2; } a.new-page-button:hover {@@ -164,8 +176,7 @@ background: rgba(0,0,0,0.8);
display: block; border-left: 8px solid cyan; padding-left: 8px; - position: sticky; - top: 3em; + position: relative; } .page-list, form.editor, form.build, form.configurator, span.adapter-error, span.adapter-success, .danger-zone {@@ -177,7 +188,6 @@ background: rgba(0,0,0,0.8);
padding: 2em; margin-left: auto; margin-right: auto; - max-height: calc(100vh - 20em); overflow-y: auto; }@@ -210,6 +220,12 @@ width: 100%;
} + + +form.editor input.slug-input { + font-size: 100%; +} + form.editor input.title-input { font-size: 150%; }@@ -230,7 +246,7 @@ height: 25em;
} form.configurator textarea { - marign: 0; + margin: 0; width: 100%; height: 5em; }@@ -272,3 +288,7 @@
form input[hidden] { display: none; } + +form input[readonly] { + border: none; +}
@@ -6,6 +6,12 @@ {{ $noEmpty := .FormValue "no-empty" }}
{{ template "header" . }} +{{ if ($page).Error }} + <h2>Page Error</h2> + + <span class="adapter-error">{{($page).Error}}</span> +{{ else }} + <h2>Edit Page</h2> <form class="editor" method="POST" action="/save/{{$slug}}">@@ -14,12 +20,12 @@ <span class="edit-error">Empty fields are not allowed - please try again</span><br/>
{{ end }} <input hidden name="csrfToken" value="{{$csrfToken}}"/> <input hidden name="oldSlug" value="{{$slug}}"/> - {{ if $editableSlugs }} + <label for="title">Title</label><br/> + <input class="title-input" id="title" type="text" name="title" value="{{($page).Title}}" required/><br/> + {{ if $editableSlugs }} <label for="slug">Slug</label><br/> <input class="slug-input" id="slug" type="text" name="slug" value="{{$slug}}" required/><br/> {{ end }} - <label for="title">Title</label><br/> - <input class="title-input" id="title" type="text" name="title" value="{{($page).Title}}" required/><br/> <span class="edited-time">last edited {{($page).Edited.Format "2006-01-02 15:04"}}</span><br/> <label for="content">Content</label><br/> <textarea class="content-input" id="content" name="content" required>{{($page).Content}}</textarea><br/>@@ -38,4 +44,7 @@ </label><br/>
<input type="submit" value="DELETE"/> </form> </details> + +{{ end }} + {{ template "footer" . }}
@@ -4,8 +4,11 @@ {{ template "header" .}}
<h2>Pages</h2> +<div class="new-page-button-wrapper"> + <a class="new-page-button" href="/new">New Page</a> +</div> + <div class="page-list"> - <a class="new-page-button" href="/new">New Page</a> <ul> {{ range $slug, $title := $pages }} <li><a href="/edit/{{$slug}}">{{$title}}</a></li>@@ -13,6 +16,4 @@ {{ end }}
</ul> </div> -<div class="static-files-list"> -</div> -{{ template "footer" .}}+{{ template "footer" .}}
@@ -11,12 +11,12 @@ {{ if $noEmpty }}
<span class="edit-error">Empty fields are not allowed - please try again</span><br/> {{ end }} <input hidden name="csrfToken" value="{{$csrfToken}}"/> + <label for="title">Title</label><br/> + <input class="title-input" id="title" type="text" name="title" required/><br/> {{ if $editableSlugs }} <label for="slug">Slug</label><br/> <input class="slug-input" id="slug" type="text" name="slug" required/><br/> {{ end }} - <label for="title">Title</label><br/> - <input class="title-input" id="title" type="text" name="title" required/><br/> <label for="content">Content</label><br/> <textarea class="content-input" id="content" name="content" required></textarea><br/> <input type="submit" value="Save"/>