// SPDX-FileCopyrightText: 2021 Luke Granger-Brown // // SPDX-License-Identifier: Apache-2.0 package fuphttp import ( "context" "fmt" "io/fs" "net/http" "github.com/google/safehtml" "github.com/google/safehtml/template" "github.com/google/safehtml/template/uncheckedconversions" shuncheckedconversions "github.com/google/safehtml/uncheckedconversions" "github.com/gorilla/mux" "hg.lukegb.com/lukegb/depot/web/fup/hashfs" ) type Config struct { Templates fs.FS Static fs.FS StaticRoot safehtml.TrustedResourceURL } type Application struct { indexTmpl *template.Template notFoundTmpl *template.Template } func (a *Application) Handler() http.Handler { r := mux.NewRouter() r.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { rw.WriteHeader(http.StatusNotFound) a.notFoundTmpl.Execute(rw, nil) }) return r } func parseTemplate(t *template.Template, fsys fs.FS, name string) (*template.Template, error) { bs, err := fs.ReadFile(fsys, name) if err != nil { return nil, fmt.Errorf("reading template %q: %w", name, err) } return t.ParseFromTrustedTemplate( uncheckedconversions.TrustedTemplateFromStringKnownToSatisfyTypeContract(string(bs)), ) } func loadTemplate(fsys fs.FS, name string, funcs template.FuncMap) (*template.Template, error) { t := template.New(name).Funcs(funcs) var err error if t, err = parseTemplate(t, fsys, "base.html"); err != nil { return nil, fmt.Errorf("loading base template: %w", err) } if t, err = parseTemplate(t, fsys, fmt.Sprintf("%s.html", name)); err != nil { return nil, fmt.Errorf("loading leaf template: %w", err) } return t, nil } func New(ctx context.Context, cfg *Config) (*Application, error) { a := new(Application) tmpls := []struct { t **template.Template name string }{ {&a.indexTmpl, "index"}, {&a.notFoundTmpl, "404"}, } funcMap := template.FuncMap{ "static": func(s string) safehtml.TrustedResourceURL { staticPath := s if fs, ok := cfg.Static.(*hashfs.FS); ok { sp, ok := fs.LookupHashedName(staticPath) if ok { staticPath = sp } else { log.Printf("warning: couldn't find static file %v", staticPath) } } return shuncheckedconversions.TrustedResourceURLFromStringKnownToSatisfyTypeContract(cfg.StaticRoot.String() + staticPath) }, } for _, tmpl := range tmpls { t, err := loadTemplate(cfg.Templates, tmpl.name, funcMap) if err != nil { return nil, fmt.Errorf("loading template %q: %w", tmpl.name, err) } *tmpl.t = t } return a, nil }