diff --git a/.gitignore b/.gitignore index 686addc..064e02a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ config.json content/ +static/ diff --git a/handler/handler.go b/handler/handler.go index a39c290..72637ac 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -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 +} diff --git a/handler/post.go b/handler/post.go index d18377f..4ef0610 100644 --- a/handler/post.go +++ b/handler/post.go @@ -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 } diff --git a/handler/template.go b/handler/template.go index dfb5ff8..36f18c8 100644 --- a/handler/template.go +++ b/handler/template.go @@ -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 } diff --git a/templates/debug/style.css b/templates/debug/style.css new file mode 100644 index 0000000..41f6463 --- /dev/null +++ b/templates/debug/style.css @@ -0,0 +1,2 @@ +body { margin: 0 } +/* This is style.css of the debug template. */