depot/web/fup/fuphttp/httpview.go

136 lines
3.1 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", "application/xhtml+xml", "application/x-csh", "application/x-sh":
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)
}
}