feat: Add static path and per-template style.css

The handler now searches in the static path first and fallback to
the content directory otherwise. If that fails, the 404 template is
rendered as expected.

The style.css path is now reserved for templates' style.css file and
does not follow the regular search path. This means that /style.css will
serve a 404 page even if static/style.css or content/style.css.md
exists.
master
Luther Wen Xu 2020-12-01 22:16:56 +07:00
parent ccc23cead0
commit a50504f747
Signed by: chanbakjsd
GPG Key ID: B7D77E3E9D102B70
5 changed files with 82 additions and 3 deletions

1
.gitignore vendored

@ -1,2 +1,3 @@
config.json
content/
static/

@ -1,6 +1,12 @@
package handler
import "net/http"
import (
"fmt"
"net/http"
"os"
)
var fs = http.FileServer(http.Dir("static/"))
// Index is the handler that displays the index page.
func Index(w http.ResponseWriter, r *http.Request) {
@ -13,5 +19,45 @@ func Handler(w http.ResponseWriter, r *http.Request) {
Index(w, r)
return
}
if r.URL.Path == "/style.css" {
TemplateStyle(w, r)
return
}
if Static(w, r) {
return
}
Post(w, r)
}
// TemplateStyle is a handler that servers the style of the current template.
func TemplateStyle(w http.ResponseWriter, r *http.Request) {
if themeCSS == nil {
// 404 if the theme CSS doesn't exist as it's optional.
CurrentTheme.ExecuteTemplate(w, "404.tmpl", nil)
return
}
w.Header().Add("Content-Type", "text/css")
_, _ = w.Write(themeCSS)
}
// Static is a handler that serves static content if available.
// If it has served something (the static file or an error), it returns true.
func Static(w http.ResponseWriter, r *http.Request) bool {
fileName := "static" + r.URL.Path
// Check if the file exists and is not a directory.
info, err := os.Stat(fileName)
if os.IsNotExist(err) || (info != nil && info.IsDir()) {
return false
}
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
CurrentTheme.ExecuteTemplate(w, "500.tmpl", fmt.Errorf("error reading static folder: %w", err))
return true
}
// The file probably exist now. Even if there's a mismatch, the malicious attacker can only get a
// 404 or a directory listing.
fs.ServeHTTP(w, r)
return true
}

@ -1,6 +1,7 @@
package handler
import (
"fmt"
"net/http"
"os"
)
@ -18,7 +19,7 @@ func Post(w http.ResponseWriter, r *http.Request) {
}
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
CurrentTheme.ExecuteTemplate(w, "500.tmpl", err)
CurrentTheme.ExecuteTemplate(w, "500.tmpl", fmt.Errorf("error reading content folder: %w", err))
return
}

@ -1,10 +1,19 @@
package handler
import "html/template"
import (
"html/template"
"io/ioutil"
"os"
"github.com/sirupsen/logrus"
)
// CurrentTheme is the set of template being used.
var CurrentTheme *template.Template
// themeCSS is the CSS of the current theme.
var themeCSS []byte
// SetTheme parses the provided theme name and sets it as the current theme.
func SetTheme(themeName string) error {
var err error
@ -17,5 +26,25 @@ func SetTheme(themeName string) error {
folder+"post.tmpl", // Page for posts.
)
if err != nil {
return err
}
// Check if the style CSS exists and is not a directory.
info, err := os.Stat(folder + "style.css")
if os.IsNotExist(err) || (info != nil && info.IsDir()) {
return nil
}
f, err := os.Open(folder + "style.css")
if err != nil {
return err
}
defer func() {
err := f.Close()
if err != nil {
logrus.Fatalf("Error while closing theme CSS file: %v", err)
}
}()
themeCSS, err = ioutil.ReadAll(f)
return err
}

@ -0,0 +1,2 @@
body { margin: 0 }
/* This is style.css of the debug template. */