package website import ( "encoding/json" "net/http" "regexp" "slices" "strings" "go.alanpearce.eu/website/internal/config" ihttp "go.alanpearce.eu/website/internal/http" "go.alanpearce.eu/website/internal/server" "github.com/kevinpollet/nego" "github.com/osdevisnot/sorvor/pkg/livereload" ) type Options struct { Source string `conf:"default:../website"` Destination string `conf:"default:public"` Redirect bool `conf:"default:true"` Development bool `conf:"default:false,flag:dev"` BaseURL config.URL `conf:"default:localhost"` VCS struct { Branch string `conf:"default:main"` RemoteURL config.URL `conf:"default:https://git.alanpearce.eu/website"` } LiveReload *livereload.LiveReload `conf:"-"` } func (website *Website) webfinger(w http.ResponseWriter, r *http.Request) *ihttp.Error { if r.URL.Query().Get("resource") == website.acctResource { w.Header().Add("Content-Type", "application/jrd+json") w.Header().Add("Access-Control-Allow-Origin", "*") if err := json.NewEncoder(w).Encode(website.me); err != nil { return &ihttp.Error{ Code: http.StatusInternalServerError, Cause: err, } } } return nil } func (website *Website) ServeHTTP(w http.ResponseWriter, r *http.Request) *ihttp.Error { urlPath, shouldRedirect := website.reader.CanonicalisePath(r.URL.Path) if shouldRedirect { http.Redirect(w, r, urlPath, 302) return nil } file, err := website.reader.GetFile(urlPath) if err != nil { return &ihttp.Error{ Message: "Error reading file", Code: http.StatusInternalServerError, } } if file == nil { return &ihttp.Error{ Message: "File not found", Code: http.StatusNotFound, } } w.Header().Add("ETag", file.Etag) w.Header().Add("Vary", "Accept-Encoding") w.Header().Add("Content-Security-Policy", website.config.CSP.String()) for k, v := range website.config.Extra.Headers { w.Header().Add(k, v) } enc := nego.NegotiateContentEncoding(r, file.AvailableEncodings()...) switch enc { case "br", "gzip", "zstd": w.Header().Add("Content-Encoding", enc) } w.Header().Add("Content-Type", file.ContentType) http.ServeContent(w, r, file.Path, file.LastModified, file.Encodings[enc]) return nil } func (website *Website) MakeRedirectorApp() *server.App { mux := http.NewServeMux() re := regexp.MustCompile( "^(.*)\\." + strings.ReplaceAll(website.config.WildcardDomain, ".", `\.`) + "$", ) replace := "${1}." + website.config.Domains[0] mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { switch { case slices.Contains(website.config.Domains, r.Host): path, _ := website.reader.CanonicalisePath(r.URL.Path) ihttp.Redirect( w, r, website.config.BaseURL.JoinPath(path), http.StatusMovedPermanently, ) case re.MatchString(r.Host): url := website.config.BaseURL.JoinPath() url.Host = re.ReplaceAllString(r.Host, replace) ihttp.Redirect(w, r, url, http.StatusTemporaryRedirect) case true: http.NotFound(w, r) } }) return &server.App{ WildcardDomain: website.config.WildcardDomain, Domains: website.config.Domains, Handler: mux, } }