-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathview.go
142 lines (139 loc) · 3.97 KB
/
view.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package main
import (
"github.com/gabriel-vasile/mimetype"
"io"
"mime"
"net/http"
"os"
urlpath "path"
"path/filepath"
"strings"
"time"
)
// rootHandler just redirects to /view/index. The root handler handles requests to the root path, and – implicity – all
// unhandled request. Thus, if the URL path is not "/", return a 404 NOT FOUND response.
func rootHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
} else {
http.Redirect(w, r, "/view/index", http.StatusFound)
}
}
// viewHandler serves pages. If the requested URL ends in ".rss" and the corresponding file ending with ".md" exists, a
// feed is generated and the "feed.html" template is used (it is used to generate a RSS 2.0 feed, even if the extension
// is ".html"). If the requested URL maps to a page name, the corresponding file (by appending ".md") is loaded and
// served using the "view.html" template. If the requested URL maps to an existing file, it is served (you can therefore
// request the ".md" files directly). If the requested URL maps to a directory, the browser is redirected to the index
// page. If none of the above, the browser is redirected to an edit page.
//
// Uploading files ending in ".rss" does not prevent RSS feed generation.
//
// Caching: a 304 NOT MODIFIED is returned if the request has an If-Modified-Since header that matches the file's
// modification time, truncated to one second. Truncation is required because the file's modtime has sub-second
// precision and the HTTP timestamp for the Last-Modified header has not.
func viewHandler(w http.ResponseWriter, r *http.Request, path string) {
const (
unknown = iota
file
page
rss
dir
)
t := unknown
if strings.HasSuffix(path, ".rss") {
path = path[:len(path)-4]
t = rss
}
fp := filepath.FromSlash(path)
fi, err := os.Stat(fp + ".md")
if err == nil {
if fi.IsDir() {
t = dir // directory ending in ".md"
} else if t == unknown {
t = page
}
// otherwise t == rss
} else {
if t == rss {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
if fp == "" {
fp = "." // make sure Stat works
}
fi, err = os.Stat(fp)
if err == nil {
if fi.IsDir() {
t = dir
} else {
t = file
}
}
}
// if nothing was found, offer to create it
if t == unknown {
http.Redirect(w, r, "/edit/"+path, http.StatusFound)
return
}
// directories are redirected to the index page
if t == dir {
http.Redirect(w, r, urlpath.Join("/view", path, "index"), http.StatusFound)
return
}
// if the page has not been modified, return (file, rss or page)
h, ok := r.Header["If-Modified-Since"]
if ok {
ti, err := http.ParseTime(h[0])
if err == nil && !fi.ModTime().Truncate(time.Second).After(ti) {
w.WriteHeader(http.StatusNotModified)
return
}
}
// if only the headers were requested, return
w.Header().Set("Last-Modified", fi.ModTime().UTC().Format(http.TimeFormat))
if r.Method == http.MethodHead {
w.WriteHeader(http.StatusOK)
return
}
// if the file exists, serve it
if t == file {
file, err := os.Open(fp)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// set MIME type by extension or by sniffing
mimeType := mime.TypeByExtension(filepath.Ext(fp))
if mimeType == "" {
mtype, err := mimetype.DetectReader(file)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
mimeType = mtype.String()
}
w.Header().Set("Content-Type", mimeType)
file.Seek(0, io.SeekStart)
// copy file
_, err = io.Copy(w, file)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
return
}
p, err := loadPage(path)
if err != nil {
http.Redirect(w, r, "/edit/"+path, http.StatusFound)
return
}
p.handleTitle(true)
if t == rss {
it := feed(p, fi.ModTime())
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`))
renderTemplate(w, p.Dir(), "feed", it)
return
}
p.renderHtml()
renderTemplate(w, p.Dir(), "view", p)
}