137 lines
3 KiB
Go
137 lines
3 KiB
Go
|
// SPDX-FileCopyrightText: 2021 Luke Granger-Brown <depot@lukegb.com>
|
||
|
//
|
||
|
// SPDX-License-Identifier: Apache-2.0
|
||
|
|
||
|
package fuphttp
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"io"
|
||
|
"log"
|
||
|
"mime"
|
||
|
"net/http"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/google/safehtml"
|
||
|
"github.com/gorilla/mux"
|
||
|
"gocloud.dev/gcerrors"
|
||
|
)
|
||
|
|
||
|
type viewTemplateData struct {
|
||
|
Text string
|
||
|
Rendered safehtml.HTML
|
||
|
RawURL safehtml.TrustedResourceURL
|
||
|
|
||
|
Filename string
|
||
|
Meta *Metadata
|
||
|
|
||
|
ExpandBorder bool
|
||
|
}
|
||
|
|
||
|
func renderAsCode(mimeType string) bool {
|
||
|
if strings.HasPrefix(mimeType, "text/") {
|
||
|
return true
|
||
|
}
|
||
|
switch mimeType {
|
||
|
case "application/json", "application/xml":
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func (a *Application) view(rw http.ResponseWriter, r *http.Request) {
|
||
|
ctx := r.Context()
|
||
|
vars := mux.Vars(r)
|
||
|
filename := vars["filename"]
|
||
|
|
||
|
meta, err := metadata(ctx, a.storageBackend, filename)
|
||
|
switch errorCode(err) {
|
||
|
case gcerrors.NotFound:
|
||
|
a.notFound(rw, r)
|
||
|
return
|
||
|
default:
|
||
|
log.Printf("view(%q) metadata: %v", filename, err)
|
||
|
a.internalError(rw, r)
|
||
|
return
|
||
|
case gcerrors.OK:
|
||
|
// OK
|
||
|
}
|
||
|
|
||
|
// TODO(lukegb): Range header and conditionals?.
|
||
|
rdr, err := a.storageBackend.NewReader(ctx, filename, nil)
|
||
|
switch errorCode(err) {
|
||
|
case gcerrors.NotFound:
|
||
|
a.notFound(rw, r)
|
||
|
return
|
||
|
default:
|
||
|
a.internalError(rw, r)
|
||
|
return
|
||
|
case gcerrors.OK:
|
||
|
// OK
|
||
|
}
|
||
|
defer rdr.Close()
|
||
|
|
||
|
content, err := io.ReadAll(rdr)
|
||
|
if err != nil {
|
||
|
log.Printf("view(%q): ReadAll: %v", filename, err)
|
||
|
a.internalError(rw, r)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if v := meta.Attributes.ModTime; !v.IsZero() {
|
||
|
rw.Header().Set("Last-Modified", v.UTC().Format(http.TimeFormat))
|
||
|
}
|
||
|
|
||
|
data := viewTemplateData{
|
||
|
Filename: filename,
|
||
|
Text: string(content),
|
||
|
Meta: meta,
|
||
|
}
|
||
|
data.RawURL, err = safehtml.TrustedResourceURLAppend(
|
||
|
safehtml.TrustedResourceURLFromConstant("/raw/"),
|
||
|
filename)
|
||
|
if err != nil {
|
||
|
log.Printf("view(%q): TrustedResourceURLAppend: %v", filename, err)
|
||
|
a.internalError(rw, r)
|
||
|
return
|
||
|
}
|
||
|
tmpl := a.viewTextTmpl
|
||
|
|
||
|
parsedContentType, _, err := mime.ParseMediaType(meta.Attributes.ContentType)
|
||
|
if err != nil {
|
||
|
log.Printf("view(%q): parsing content type %v: %v", filename, meta.Attributes.ContentType, err)
|
||
|
a.internalError(rw, r)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Is highlighting available?
|
||
|
if bytes.Contains(content, []byte{0}) {
|
||
|
// Probably binary.
|
||
|
tmpl = a.viewBinaryTmpl
|
||
|
} else if a.highlighter != nil {
|
||
|
log.Printf("view(%q): content type %v", filename, parsedContentType)
|
||
|
if parsedContentType == "text/markdown" {
|
||
|
highlighted, err := a.highlighter.Markdown(ctx, data.Text)
|
||
|
if err != nil {
|
||
|
log.Printf("view(%q): rendering markdown: %v", filename, err)
|
||
|
} else {
|
||
|
data.Rendered = highlighted
|
||
|
tmpl = a.viewRenderedTmpl
|
||
|
}
|
||
|
} else if renderAsCode(parsedContentType) {
|
||
|
highlighted, err := a.highlighter.Code(ctx, filename, "Dark", data.Text)
|
||
|
if err != nil {
|
||
|
log.Printf("view(%q): rendering markdown: %v", filename, err)
|
||
|
} else {
|
||
|
data.Rendered = highlighted
|
||
|
data.ExpandBorder = true
|
||
|
tmpl = a.viewRenderedTmpl
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if err := tmpl.Execute(rw, data); err != nil {
|
||
|
log.Printf("view(%q): Execute: %v", filename, err)
|
||
|
}
|
||
|
}
|