2017-10-08 04:48:58 +00:00
|
|
|
package core
|
|
|
|
|
|
|
|
import (
|
2017-10-31 16:42:15 +00:00
|
|
|
"errors"
|
2017-12-02 18:47:21 +00:00
|
|
|
"html/template"
|
|
|
|
"io/ioutil"
|
2017-10-08 04:48:58 +00:00
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// PageHandler is the catch-all route handler, for serving static web pages.
|
|
|
|
func (b *Blog) PageHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
path := r.URL.Path
|
2017-12-23 02:34:58 +00:00
|
|
|
// log.Debug("Catch-all page handler invoked for request URI: %s", path)
|
2017-10-08 04:48:58 +00:00
|
|
|
|
|
|
|
// Remove trailing slashes by redirecting them away.
|
|
|
|
if len(path) > 1 && path[len(path)-1] == '/' {
|
2017-10-31 16:42:15 +00:00
|
|
|
b.Redirect(w, strings.TrimRight(path, "/"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Restrict special paths.
|
|
|
|
if strings.HasPrefix(strings.ToLower(path), "/.") {
|
|
|
|
b.Forbidden(w, r)
|
2017-10-08 04:48:58 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Search for a file that matches their URL.
|
2017-10-31 16:42:15 +00:00
|
|
|
filepath, err := b.ResolvePath(path)
|
|
|
|
if err != nil {
|
2017-11-24 19:56:32 +00:00
|
|
|
// See if it resolves as a blog entry.
|
2017-11-27 03:05:31 +00:00
|
|
|
err = b.viewPost(w, r, strings.TrimLeft(path, "/"))
|
2017-11-24 19:56:32 +00:00
|
|
|
if err != nil {
|
|
|
|
b.NotFound(w, r, "The page you were looking for was not found.")
|
|
|
|
}
|
2017-10-31 16:42:15 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Is it a template file?
|
2017-12-23 02:34:58 +00:00
|
|
|
if strings.HasSuffix(filepath.URI, ".gohtml") {
|
2017-10-31 16:42:15 +00:00
|
|
|
b.RenderTemplate(w, r, filepath.URI, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-12-02 18:47:21 +00:00
|
|
|
// Is it a Markdown file?
|
|
|
|
if strings.HasSuffix(filepath.URI, ".md") || strings.HasSuffix(filepath.URI, ".markdown") {
|
|
|
|
source, err := ioutil.ReadFile(filepath.Absolute)
|
|
|
|
if err != nil {
|
|
|
|
b.Error(w, r, "Couldn't read Markdown source!")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Render it to HTML and find out its title.
|
|
|
|
body := string(source)
|
|
|
|
html := b.RenderTrustedMarkdown(body)
|
|
|
|
title, _ := TitleFromMarkdown(body)
|
|
|
|
|
|
|
|
b.RenderTemplate(w, r, ".markdown", NewVars(map[interface{}]interface{}{
|
2017-12-23 23:29:38 +00:00
|
|
|
"Title": title,
|
|
|
|
"HTML": template.HTML(html),
|
|
|
|
"MarkdownFile": filepath.URI,
|
2017-12-02 18:47:21 +00:00
|
|
|
}))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-10-31 16:42:15 +00:00
|
|
|
http.ServeFile(w, r, filepath.Absolute)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filepath represents a file discovered in the document roots, and maintains
|
|
|
|
// both its relative and absolute components.
|
|
|
|
type Filepath struct {
|
|
|
|
// Canonicalized URI version of the file resolved on disk,
|
|
|
|
// possible with a file extension injected.
|
|
|
|
// (i.e. "/about" -> "about.html")
|
|
|
|
URI string
|
2017-11-27 02:52:14 +00:00
|
|
|
Basename string
|
2017-10-31 16:42:15 +00:00
|
|
|
Relative string // Relative path including document root (i.e. "root/about.html")
|
|
|
|
Absolute string // Absolute path on disk (i.e. "/opt/blog/root/about.html")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f Filepath) String() string {
|
|
|
|
return f.Relative
|
|
|
|
}
|
|
|
|
|
|
|
|
// ResolvePath matches a filesystem path to a relative request URI.
|
|
|
|
//
|
|
|
|
// This checks the UserRoot first and then the DocumentRoot. This way the user
|
|
|
|
// may override templates from the core app's document root.
|
|
|
|
func (b *Blog) ResolvePath(path string) (Filepath, error) {
|
|
|
|
// Strip leading slashes.
|
|
|
|
if path[0] == '/' {
|
|
|
|
path = strings.TrimPrefix(path, "/")
|
|
|
|
}
|
|
|
|
|
2017-11-15 14:55:15 +00:00
|
|
|
// If you need to debug this function, edit this block.
|
|
|
|
debug := func(tmpl string, args ...interface{}) {
|
|
|
|
if false {
|
|
|
|
log.Debug(tmpl, args...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
debug("Resolving filepath for URI: %s", path)
|
2017-11-27 03:44:36 +00:00
|
|
|
for _, root := range []string{b.UserRoot, b.DocumentRoot} {
|
2017-10-31 16:42:15 +00:00
|
|
|
if len(root) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resolve the file path.
|
2017-10-08 04:48:58 +00:00
|
|
|
relPath := filepath.Join(root, path)
|
|
|
|
absPath, err := filepath.Abs(relPath)
|
2017-11-27 02:52:14 +00:00
|
|
|
basename := filepath.Base(relPath)
|
2017-10-08 04:48:58 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Error("%v", err)
|
|
|
|
}
|
|
|
|
|
2017-11-15 14:55:15 +00:00
|
|
|
debug("Expected filepath: %s", absPath)
|
2017-10-08 04:48:58 +00:00
|
|
|
|
|
|
|
// Found an exact hit?
|
|
|
|
if stat, err := os.Stat(absPath); !os.IsNotExist(err) && !stat.IsDir() {
|
2017-11-15 14:55:15 +00:00
|
|
|
debug("Exact filepath found: %s", absPath)
|
2017-11-27 02:52:14 +00:00
|
|
|
return Filepath{path, basename, relPath, absPath}, nil
|
2017-10-08 04:48:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Try some supported suffixes.
|
|
|
|
suffixes := []string{
|
2017-10-31 16:42:15 +00:00
|
|
|
".gohtml",
|
2017-10-08 04:48:58 +00:00
|
|
|
".html",
|
2017-10-31 16:42:15 +00:00
|
|
|
"/index.gohtml",
|
2017-10-08 04:48:58 +00:00
|
|
|
"/index.html",
|
|
|
|
".md",
|
|
|
|
"/index.md",
|
|
|
|
}
|
|
|
|
for _, suffix := range suffixes {
|
2017-10-31 16:42:15 +00:00
|
|
|
test := absPath + suffix
|
|
|
|
if stat, err := os.Stat(test); !os.IsNotExist(err) && !stat.IsDir() {
|
2017-11-15 14:55:15 +00:00
|
|
|
debug("Filepath found via suffix %s: %s", suffix, test)
|
2017-11-27 02:52:14 +00:00
|
|
|
return Filepath{path + suffix, basename + suffix, relPath + suffix, test}, nil
|
2017-10-08 04:48:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-31 16:42:15 +00:00
|
|
|
return Filepath{}, errors.New("not found")
|
2017-10-08 04:48:58 +00:00
|
|
|
}
|