// SPDX-FileCopyrightText: 2021 Luke Granger-Brown // // 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) } }