From 4b873a47923253aa71adf812360b7b8311292c91 Mon Sep 17 00:00:00 2001 From: video-prize-ranch Date: Mon, 19 Dec 2022 15:50:03 -0500 Subject: [PATCH] Initial _redirects implementation --- cmd/main.go | 4 +- server/handler/handler.go | 8 ++-- server/handler/handler_custom_domain.go | 4 +- server/handler/handler_raw_domain.go | 6 +-- server/handler/handler_sub_domain.go | 10 ++--- server/handler/handler_test.go | 1 + server/handler/try.go | 36 +++++++++++++++++ server/upstream/redirects.go | 52 +++++++++++++++++++++++++ 8 files changed, 106 insertions(+), 15 deletions(-) create mode 100644 server/upstream/redirects.go diff --git a/cmd/main.go b/cmd/main.go index b72013a..d511021 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -80,6 +80,8 @@ func Serve(ctx *cli.Context) error { canonicalDomainCache := cache.NewKeyValueCache() // dnsLookupCache stores DNS lookups for custom domains dnsLookupCache := cache.NewKeyValueCache() + // redirectsCache stores redirects in _redirects files + redirectsCache := cache.NewKeyValueCache() // clientResponseCache stores responses from the Gitea server clientResponseCache := cache.NewKeyValueCache() @@ -93,7 +95,7 @@ func Serve(ctx *cli.Context) error { giteaClient, rawInfoPage, BlacklistedPaths, allowedCorsDomains, - dnsLookupCache, canonicalDomainCache) + dnsLookupCache, canonicalDomainCache, redirectsCache) httpHandler := server.SetupHTTPACMEChallengeServer(challengeCache) diff --git a/server/handler/handler.go b/server/handler/handler.go index 78301e9..3462e01 100644 --- a/server/handler/handler.go +++ b/server/handler/handler.go @@ -25,7 +25,7 @@ func Handler(mainDomainSuffix, rawDomain string, giteaClient *gitea.Client, rawInfoPage string, blacklistedPaths, allowedCorsDomains []string, - dnsLookupCache, canonicalDomainCache cache.SetGetKey, + dnsLookupCache, canonicalDomainCache, redirectsCache cache.SetGetKey, ) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { log := log.With().Strs("Handler", []string{req.Host, req.RequestURI}).Logger() @@ -93,21 +93,21 @@ func Handler(mainDomainSuffix, rawDomain string, mainDomainSuffix, rawInfoPage, trimmedHost, pathElements, - canonicalDomainCache) + canonicalDomainCache, redirectsCache) } else if strings.HasSuffix(trimmedHost, mainDomainSuffix) { log.Debug().Msg("subdomain request detecded") handleSubDomain(log, ctx, giteaClient, mainDomainSuffix, trimmedHost, pathElements, - canonicalDomainCache) + canonicalDomainCache, redirectsCache) } else { log.Debug().Msg("custom domain request detecded") handleCustomDomain(log, ctx, giteaClient, mainDomainSuffix, trimmedHost, pathElements, - dnsLookupCache, canonicalDomainCache) + dnsLookupCache, canonicalDomainCache, redirectsCache) } } } diff --git a/server/handler/handler_custom_domain.go b/server/handler/handler_custom_domain.go index 2f98085..f9a081e 100644 --- a/server/handler/handler_custom_domain.go +++ b/server/handler/handler_custom_domain.go @@ -18,7 +18,7 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g mainDomainSuffix string, trimmedHost string, pathElements []string, - dnsLookupCache, canonicalDomainCache cache.SetGetKey, + dnsLookupCache, canonicalDomainCache, redirectsCache cache.SetGetKey, ) { // Serve pages from custom domains targetOwner, targetRepo, targetBranch := dns.GetTargetFromDNS(trimmedHost, mainDomainSuffix, dnsLookupCache) @@ -63,7 +63,7 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g } log.Debug().Msg("tryBranch, now trying upstream 7") - tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache) + tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache) return } diff --git a/server/handler/handler_raw_domain.go b/server/handler/handler_raw_domain.go index 5e974da..aa41c52 100644 --- a/server/handler/handler_raw_domain.go +++ b/server/handler/handler_raw_domain.go @@ -19,7 +19,7 @@ func handleRaw(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Clie mainDomainSuffix, rawInfoPage string, trimmedHost string, pathElements []string, - canonicalDomainCache cache.SetGetKey, + canonicalDomainCache, redirectsCache cache.SetGetKey, ) { // Serve raw content from RawDomain log.Debug().Msg("raw domain") @@ -41,7 +41,7 @@ func handleRaw(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Clie TargetPath: path.Join(pathElements[3:]...), }, true); works { log.Trace().Msg("tryUpstream: serve raw domain with specified branch") - tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache) + tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache) return } log.Debug().Msg("missing branch info") @@ -58,7 +58,7 @@ func handleRaw(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Clie TargetPath: path.Join(pathElements[2:]...), }, true); works { log.Trace().Msg("tryUpstream: serve raw domain with default branch") - tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache) + tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache) } else { html.ReturnErrorPage(ctx, fmt.Sprintf("raw domain could not find repo '%s/%s' or repo is empty", targetOpt.TargetOwner, targetOpt.TargetRepo), diff --git a/server/handler/handler_sub_domain.go b/server/handler/handler_sub_domain.go index 2a75e9f..bda6a11 100644 --- a/server/handler/handler_sub_domain.go +++ b/server/handler/handler_sub_domain.go @@ -19,7 +19,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite mainDomainSuffix string, trimmedHost string, pathElements []string, - canonicalDomainCache cache.SetGetKey, + canonicalDomainCache, redirectsCache cache.SetGetKey, ) { // Serve pages from subdomains of MainDomainSuffix log.Debug().Msg("main domain suffix") @@ -51,7 +51,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite TargetPath: path.Join(pathElements[2:]...), }, true); works { log.Trace().Msg("tryUpstream: serve with specified repo and branch") - tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache) + tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache) } else { html.ReturnErrorPage(ctx, fmt.Sprintf("explizite set branch %q do not exist at '%s/%s'", targetOpt.TargetBranch, targetOpt.TargetOwner, targetOpt.TargetRepo), @@ -72,7 +72,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite TargetPath: path.Join(pathElements[1:]...), }, true); works { log.Trace().Msg("tryUpstream: serve default pages repo with specified branch") - tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache) + tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache) } else { html.ReturnErrorPage(ctx, fmt.Sprintf("explizite set branch %q do not exist at '%s/%s'", targetOpt.TargetBranch, targetOpt.TargetOwner, targetOpt.TargetRepo), @@ -94,7 +94,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite TargetPath: path.Join(pathElements[1:]...), }, false); works { log.Debug().Msg("tryBranch, now trying upstream 5") - tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache) + tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache) return } } @@ -109,7 +109,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite TargetPath: path.Join(pathElements...), }, false); works { log.Debug().Msg("tryBranch, now trying upstream 6") - tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache) + tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache) return } diff --git a/server/handler/handler_test.go b/server/handler/handler_test.go index 626564a..8564bd1 100644 --- a/server/handler/handler_test.go +++ b/server/handler/handler_test.go @@ -20,6 +20,7 @@ func TestHandlerPerformance(t *testing.T) { []string{"raw.codeberg.org", "fonts.codeberg.org", "design.codeberg.org"}, cache.NewKeyValueCache(), cache.NewKeyValueCache(), + cache.NewKeyValueCache(), ) testCase := func(uri string, status int) { diff --git a/server/handler/try.go b/server/handler/try.go index 5a09b91..4c4ae19 100644 --- a/server/handler/try.go +++ b/server/handler/try.go @@ -18,6 +18,7 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client, mainDomainSuffix, trimmedHost string, options *upstream.Options, canonicalDomainCache cache.SetGetKey, + redirectsCache cache.SetGetKey, ) { // check if a canonical domain exists on a request on MainDomain if strings.HasSuffix(trimmedHost, mainDomainSuffix) { @@ -38,6 +39,41 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client, // Add host for debugging. options.Host = trimmedHost + // Check for redirects + redirects := options.GetRedirects(giteaClient, redirectsCache) + if len(redirects) > 0 { + for _, redirect := range redirects { + reqUrl := ctx.Req.RequestURI + trimmedFromUrl := strings.TrimSuffix(redirect.From, "/*") + if strings.TrimSuffix(redirect.From, "/") == strings.TrimSuffix(reqUrl, "/") { + if redirect.StatusCode == 200 { + options.TargetPath = redirect.To + } else { + ctx.Redirect(redirect.To, redirect.StatusCode) + return + } + } + if strings.HasSuffix(redirect.From, "/*") && strings.HasPrefix(reqUrl, trimmedFromUrl) { + if strings.Contains(redirect.To, ":splat") { + splatUrl := strings.ReplaceAll(redirect.To, ":splat", strings.TrimPrefix(reqUrl, trimmedFromUrl)) + if redirect.StatusCode == 200 { + options.TargetPath = splatUrl + } else { + ctx.Redirect(splatUrl, redirect.StatusCode) + return + } + } else { + if redirect.StatusCode == 200 { + options.TargetPath = redirect.To + } else { + ctx.Redirect(redirect.To, redirect.StatusCode) + return + } + } + } + } + } + // Try to request the file from the Gitea API if !options.Upstream(ctx, giteaClient) { html.ReturnErrorPage(ctx, "", ctx.StatusCode) diff --git a/server/upstream/redirects.go b/server/upstream/redirects.go new file mode 100644 index 0000000..f27aed4 --- /dev/null +++ b/server/upstream/redirects.go @@ -0,0 +1,52 @@ +package upstream + +import ( + "strconv" + "strings" + "time" + + "codeberg.org/codeberg/pages/server/cache" + "codeberg.org/codeberg/pages/server/gitea" + "github.com/rs/zerolog/log" +) + +type Redirect struct { + From string + To string + StatusCode int +} + +// redirectsCacheTimeout specifies the timeout for the redirects cache. +var redirectsCacheTimeout = 10 * time.Minute + +const redirectsConfig = "_redirects" + +// GetRedirects returns redirects specified in the _redirects file. +func (o *Options) GetRedirects(giteaClient *gitea.Client, redirectsCache cache.SetGetKey) []Redirect { + var redirects []Redirect + + if cachedValue, ok := redirectsCache.Get(o.TargetOwner + "/" + o.TargetRepo + "/" + o.TargetBranch); ok { + redirects = cachedValue.([]Redirect) + } else { + body, err := giteaClient.GiteaRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, redirectsConfig) + if err == nil { + for _, line := range strings.Split(string(body), "\n") { + redirectArr := strings.Fields(line) + if len(redirectArr) == 3 { + statusCode, err := strconv.Atoi(redirectArr[2]) + if err != nil { + log.Info().Err(err).Msgf("could not read %s of %s/%s", redirectsConfig, o.TargetOwner, o.TargetRepo) + } + + redirects = append(redirects, Redirect{ + From: redirectArr[0], + To: redirectArr[1], + StatusCode: statusCode, + }) + } + } + } + _ = redirectsCache.Set(o.TargetOwner+"/"+o.TargetRepo+"/"+o.TargetBranch, redirects, redirectsCacheTimeout) + } + return redirects +}