From 1e01a1295845cef1e042331d8db2c6e47e6f6595 Mon Sep 17 00:00:00 2001 From: Catherine Date: Thu, 2 Oct 2025 16:41:23 +0000 Subject: [PATCH] Implement force redirects. --- src/pages.go | 36 +++++++++++++++++++++--------------- src/redirects.go | 19 +++++++++++++++---- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/pages.go b/src/pages.go index a317eb9..ef2e970 100644 --- a/src/pages.go +++ b/src/pages.go @@ -141,22 +141,28 @@ func getPage(w http.ResponseWriter, r *http.Request) error { return err } entry = manifest.Contents[entryPath] - if entry == nil || entry.GetType() == Type_Invalid { - if !appliedRedirect { - originalURL := (&url.URL{Host: r.Host}).ResolveReference(r.URL) - redirectURL, redirectStatus := ApplyRedirects(manifest, originalURL) - if Is3xxHTTPStatus(redirectStatus) { - w.Header().Set("Location", redirectURL.String()) - w.WriteHeader(int(redirectStatus)) - fmt.Fprintf(w, "see %s\n", redirectURL.String()) - return nil - } else if redirectURL != nil { - entryPath = strings.TrimPrefix(redirectURL.Path, "/") - status = int(redirectStatus) - appliedRedirect = true - continue - } + if !appliedRedirect { + redirectKind := RedirectAny + if entry != nil && entry.GetType() != Type_Invalid { + redirectKind = RedirectForce } + originalURL := (&url.URL{Host: r.Host}).ResolveReference(r.URL) + redirectURL, redirectStatus := ApplyRedirects(manifest, originalURL, redirectKind) + if Is3xxHTTPStatus(redirectStatus) { + w.Header().Set("Location", redirectURL.String()) + w.WriteHeader(int(redirectStatus)) + fmt.Fprintf(w, "see %s\n", redirectURL.String()) + return nil + } else if redirectURL != nil { + entryPath = strings.TrimPrefix(redirectURL.Path, "/") + status = int(redirectStatus) + // Apply user redirects at most once; if something ends in a loop, it should be + // the user agent, not the pages server. + appliedRedirect = true + continue + } + } + if entry == nil || entry.GetType() == Type_Invalid { status = 404 if entryPath != notFoundPage { entryPath = notFoundPage diff --git a/src/redirects.go b/src/redirects.go index d5314aa..c53d9a0 100644 --- a/src/redirects.go +++ b/src/redirects.go @@ -80,9 +80,6 @@ func validateRule(rule redirects.Rule) error { if toURL.Host != "" && !Is3xxHTTPStatus(uint(rule.Status)) { return fmt.Errorf("'to' URL may only include a hostname for 3xx status rules") } - if rule.Force { - return fmt.Errorf("force redirects are not supported") - } return nil } @@ -130,10 +127,24 @@ func toOrFromComponent(to, from string) string { } } -func ApplyRedirects(manifest *Manifest, fromURL *url.URL) (toURL *url.URL, status uint) { +type RedirectKind int + +const ( + RedirectAny RedirectKind = iota + RedirectForce +) + +func ApplyRedirects( + manifest *Manifest, fromURL *url.URL, kind RedirectKind, +) ( + toURL *url.URL, status uint, +) { fromSegments := pathSegments(fromURL.Path) next: for _, rule := range manifest.Redirects { + if kind == RedirectForce && !*rule.Force { + continue + } // check if the rule matches fromURL ruleFromURL, _ := url.Parse(*rule.From) // pre-validated in `validateRule` if ruleFromURL.Scheme != "" && fromURL.Scheme != ruleFromURL.Scheme {