server: use custom error page

This commit is contained in:
Jeremy Baxter 2026-01-10 21:27:43 +13:00
parent dcb8094403
commit 3bfcf5794f
2 changed files with 49 additions and 23 deletions

View file

@ -31,7 +31,8 @@ type Page interface {
func writePage(p Page, w http.ResponseWriter) { func writePage(p Page, w http.ResponseWriter) {
t, err := template.ParseFS(staticFS, "static/templates/" + p.SourceFile()) t, err := template.ParseFS(staticFS, "static/templates/" + p.SourceFile())
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, "Internal server error: " + err.Error(),
http.StatusInternalServerError)
return return
} }
@ -41,6 +42,21 @@ func writePage(p Page, w http.ResponseWriter) {
} }
} }
type ErrorPage struct {
Status string
Reason string
}
func MakeErrorPage(status, reason string) (p ErrorPage) {
p.Status = status
p.Reason = reason
return
}
func (p ErrorPage) SourceFile() string { return "error.html" }
func (p ErrorPage) Title() string { return "" }
func (p ErrorPage) Body() template.HTML { return template.HTML("") }
type IndexPage struct { type IndexPage struct {
Showcase string Showcase string
} }

View file

@ -28,6 +28,16 @@ var artistPages map[string]map[string]ArtistPage
// mapping of artist names to album names to pages // mapping of artist names to album names to pages
var albumPages map[string]map[string]AlbumPage var albumPages map[string]map[string]AlbumPage
func httpErrorReason(w http.ResponseWriter, error int, reason string) {
errorName := fmt.Sprintf("%d %s", error, http.StatusText(error))
w.WriteHeader(error)
writePage(MakeErrorPage(errorName, reason), w)
}
func httpError(w http.ResponseWriter, error int) {
httpErrorReason(w, error, "")
}
func doIndexPage(w http.ResponseWriter, req *http.Request) { func doIndexPage(w http.ResponseWriter, req *http.Request) {
writePage(indexPage, w) writePage(indexPage, w)
} }
@ -64,13 +74,13 @@ func detectTextType(fileName string) string {
func serveStaticFiles(w http.ResponseWriter, req *http.Request) { func serveStaticFiles(w http.ResponseWriter, req *http.Request) {
if strings.HasPrefix(req.RequestURI, "/static/templates/") { if strings.HasPrefix(req.RequestURI, "/static/templates/") {
http.Error(w, "forbidden", http.StatusForbidden) httpError(w, http.StatusForbidden)
return return
} }
contents, err := staticFS.ReadFile(req.RequestURI[1:]) contents, err := staticFS.ReadFile(req.RequestURI[1:])
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusNotFound) httpError(w, http.StatusNotFound)
return return
} }
@ -86,30 +96,31 @@ func serveStaticFiles(w http.ResponseWriter, req *http.Request) {
func serveMediaDirectory(w http.ResponseWriter, req *http.Request) { func serveMediaDirectory(w http.ResponseWriter, req *http.Request) {
path, err := url.QueryUnescape("/" + strings.SplitN(req.RequestURI[1:], "/", 2)[1]) path, err := url.QueryUnescape("/" + strings.SplitN(req.RequestURI[1:], "/", 2)[1])
if err != nil { if err != nil {
http.Error(w, "bad request", http.StatusBadRequest) httpError(w, http.StatusBadRequest)
return return
} }
filePath := musicindex.MediaDirectory() + path filePath := musicindex.MediaDirectory() + path
if strings.Contains(path, "/..") || strings.Contains(path, "/.") { if strings.Contains(path, "/../") || strings.Contains(path, "/./") {
http.Error(w, "forbidden", http.StatusForbidden) httpError(w, http.StatusForbidden)
return return
} }
stat, err := os.Stat(filePath) stat, err := os.Stat(filePath)
if err != nil { if err != nil {
http.Error(w, "not found", http.StatusNotFound) httpError(w, http.StatusNotFound)
return return
} }
if stat.IsDir() { if stat.IsDir() {
http.Error(w, "is a directory; maybe you are looking for " + path, http.StatusForbidden) httpErrorReason(w, http.StatusForbidden,
"is a directory; maybe you are looking for " + path)
return return
} }
f, err := os.Open(filePath) f, err := os.Open(filePath)
if err != nil { if err != nil {
http.Error(w, "not found", http.StatusNotFound) httpError(w, http.StatusNotFound)
return return
} }
defer f.Close() defer f.Close()
@ -120,14 +131,14 @@ func handleArtistAlbumPage(w http.ResponseWriter, req *http.Request) {
// test artist // test artist
rx, err := regexp.Compile("^/([^/?]+)/?(\\?.*)?$") rx, err := regexp.Compile("^/([^/?]+)/?(\\?.*)?$")
if err != nil { if err != nil {
http.Error(w, "internal server error", http.StatusInternalServerError) httpError(w, http.StatusInternalServerError)
return return
} }
if (rx.MatchString(req.RequestURI)) { if (rx.MatchString(req.RequestURI)) {
artist, err := url.QueryUnescape(rx.FindStringSubmatch(req.RequestURI)[1]) artist, err := url.QueryUnescape(rx.FindStringSubmatch(req.RequestURI)[1])
if err != nil { if err != nil {
http.Error(w, "bad request", http.StatusBadRequest) httpError(w, http.StatusBadRequest)
return return
} }
if musicindex.ArtistExists(artist) { if musicindex.ArtistExists(artist) {
@ -142,14 +153,14 @@ func handleArtistAlbumPage(w http.ResponseWriter, req *http.Request) {
} }
// artist not found // artist not found
http.Error(w, "not found", http.StatusNotFound) httpError(w, http.StatusNotFound)
return return
} }
// test album tarball // test album tarball
rx, err = regexp.Compile("^/([^/?]+)/([^/?]+)\\.tar\\.gz$") rx, err = regexp.Compile("^/([^/?]+)/([^/?]+)\\.tar\\.gz$")
if err != nil { if err != nil {
http.Error(w, "internal server error", http.StatusInternalServerError) httpError(w, http.StatusInternalServerError)
return return
} }
@ -157,12 +168,12 @@ func handleArtistAlbumPage(w http.ResponseWriter, req *http.Request) {
captures := rx.FindStringSubmatch(req.RequestURI) captures := rx.FindStringSubmatch(req.RequestURI)
artist, err := url.QueryUnescape(captures[1]) artist, err := url.QueryUnescape(captures[1])
if err != nil { if err != nil {
http.Error(w, "bad request", http.StatusBadRequest) httpError(w, http.StatusBadRequest)
return return
} }
albumName, err := url.QueryUnescape(captures[2]) albumName, err := url.QueryUnescape(captures[2])
if err != nil { if err != nil {
http.Error(w, "bad request", http.StatusBadRequest) httpError(w, http.StatusBadRequest)
return return
} }
@ -171,8 +182,7 @@ func handleArtistAlbumPage(w http.ResponseWriter, req *http.Request) {
if ok { if ok {
f, err := os.Open(album.Tarball) f, err := os.Open(album.Tarball)
if err != nil { if err != nil {
http.Error(w, "internal server error: " + err.Error(), httpErrorReason(w, http.StatusInternalServerError, err.Error())
http.StatusInternalServerError)
return return
} }
defer f.Close() defer f.Close()
@ -187,14 +197,14 @@ func handleArtistAlbumPage(w http.ResponseWriter, req *http.Request) {
} }
// artist or album not found // artist or album not found
http.Error(w, "not found", http.StatusNotFound) httpError(w, http.StatusNotFound)
return return
} }
// test album // test album
rx, err = regexp.Compile("^/([^/?]+)/([^/?]+)/?(\\?.*)?$") rx, err = regexp.Compile("^/([^/?]+)/([^/?]+)/?(\\?.*)?$")
if err != nil { if err != nil {
http.Error(w, "internal server error", http.StatusInternalServerError) httpErrorReason(w, http.StatusInternalServerError, err.Error())
return return
} }
@ -202,12 +212,12 @@ func handleArtistAlbumPage(w http.ResponseWriter, req *http.Request) {
captures := rx.FindStringSubmatch(req.RequestURI) captures := rx.FindStringSubmatch(req.RequestURI)
artist, err := url.QueryUnescape(captures[1]) artist, err := url.QueryUnescape(captures[1])
if err != nil { if err != nil {
http.Error(w, "bad request", http.StatusBadRequest) httpError(w, http.StatusBadRequest)
return return
} }
albumName, err := url.QueryUnescape(captures[2]) albumName, err := url.QueryUnescape(captures[2])
if err != nil { if err != nil {
http.Error(w, "bad request", http.StatusBadRequest) httpError(w, http.StatusBadRequest)
return return
} }
@ -220,12 +230,12 @@ func handleArtistAlbumPage(w http.ResponseWriter, req *http.Request) {
} }
// artist or album not found // artist or album not found
http.Error(w, "not found", http.StatusNotFound) httpError(w, http.StatusNotFound)
return return
} }
// illegal URI format // illegal URI format
http.Error(w, "not found", http.StatusNotFound) httpError(w, http.StatusNotFound)
} }
func RunOn(address string) { func RunOn(address string) {