diff --git a/cmd/main.go b/cmd/main.go index dc2c621..3560ad4 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,7 +1,8 @@ +//go:build !fasthttp + package cmd import ( - "bytes" "context" "crypto/tls" "errors" @@ -47,7 +48,7 @@ func Serve(ctx *cli.Context) error { giteaRoot := strings.TrimSuffix(ctx.String("gitea-root"), "/") giteaAPIToken := ctx.String("gitea-api-token") rawDomain := ctx.String("raw-domain") - mainDomainSuffix := []byte(ctx.String("pages-domain")) + mainDomainSuffix := ctx.String("pages-domain") rawInfoPage := ctx.String("raw-info-page") listeningAddress := fmt.Sprintf("%s:%s", ctx.String("host"), ctx.String("port")) enableHTTPServer := ctx.Bool("enable-http-server") @@ -69,8 +70,8 @@ func Serve(ctx *cli.Context) error { } // Make sure MainDomain has a trailing dot, and GiteaRoot has no trailing slash - if !bytes.HasPrefix(mainDomainSuffix, []byte{'.'}) { - mainDomainSuffix = append([]byte{'.'}, mainDomainSuffix...) + if !strings.HasPrefix(mainDomainSuffix, ".") { + mainDomainSuffix = "." + mainDomainSuffix } keyCache := cache.NewKeyValueCache() @@ -88,7 +89,7 @@ func Serve(ctx *cli.Context) error { } // Create handler based on settings - handler := server.Handler(mainDomainSuffix, []byte(rawDomain), + handler := server.Handler(mainDomainSuffix, rawDomain, giteaClient, giteaRoot, rawInfoPage, BlacklistedPaths, allowedCorsDomains, diff --git a/html/error.go b/html/error.go index 572357b..b1ccc06 100644 --- a/html/error.go +++ b/html/error.go @@ -1,9 +1,9 @@ package html import ( - "bytes" "net/http" "strconv" + "strings" ) func errorMessage(statusCode int) string { @@ -20,8 +20,8 @@ func errorMessage(statusCode int) string { } // TODO: use template engine? -func errorBody(statusCode int) []byte { - return bytes.ReplaceAll(NotFoundPage, - []byte("%status"), - []byte(strconv.Itoa(statusCode)+" "+errorMessage(statusCode))) +func errorBody(statusCode int) string { + return strings.ReplaceAll(NotFoundPage, + "%status", + strconv.Itoa(statusCode)+" "+errorMessage(statusCode)) } diff --git a/html/error_fasthttp.go b/html/error_fasthttp.go index 120eb03..2180224 100644 --- a/html/error_fasthttp.go +++ b/html/error_fasthttp.go @@ -13,5 +13,5 @@ func ReturnErrorPage(ctx *fasthttp.RequestCtx, code int) { ctx.Response.Header.SetContentType("text/html; charset=utf-8") // TODO: use template engine? - ctx.Response.SetBody(errorBody(code)) + ctx.Response.SetBody([]byte(errorBody(code))) } diff --git a/html/error_std.go b/html/error_std.go index d87fd68..802cfdc 100644 --- a/html/error_std.go +++ b/html/error_std.go @@ -3,17 +3,21 @@ package html import ( - "bytes" "io" + "strings" "codeberg.org/codeberg/pages/server/context" ) // ReturnErrorPage sets the response status code and writes NotFoundPage to the response body, with "%status" replaced // with the provided status code. -func ReturnErrorPage(ctx *context.Context, code int) { +func ReturnErrorPage(ctx *context.Context, msg string, code int) { ctx.RespWriter.Header().Set("Content-Type", "text/html; charset=utf-8") ctx.RespWriter.WriteHeader(code) - io.Copy(ctx.RespWriter, bytes.NewReader(errorBody(code))) + if msg == "" { + msg = errorBody(code) + } + + io.Copy(ctx.RespWriter, strings.NewReader(msg)) } diff --git a/html/html.go b/html/html.go index d223e15..5e3842a 100644 --- a/html/html.go +++ b/html/html.go @@ -3,4 +3,4 @@ package html import _ "embed" //go:embed 404.html -var NotFoundPage []byte +var NotFoundPage string diff --git a/server/certificates/certificates.go b/server/certificates/certificates.go index 8944468..660228b 100644 --- a/server/certificates/certificates.go +++ b/server/certificates/certificates.go @@ -36,7 +36,7 @@ import ( ) // TLSConfig returns the configuration for generating, serving and cleaning up Let's Encrypt certificates. -func TLSConfig(mainDomainSuffix []byte, +func TLSConfig(mainDomainSuffix string, giteaClient *gitea.Client, dnsProvider string, acmeUseRateLimits bool, @@ -47,7 +47,6 @@ func TLSConfig(mainDomainSuffix []byte, // check DNS name & get certificate from Let's Encrypt GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { sni := strings.ToLower(strings.TrimSpace(info.ServerName)) - sniBytes := []byte(sni) if len(sni) < 1 { return nil, errors.New("missing sni") } @@ -69,23 +68,20 @@ func TLSConfig(mainDomainSuffix []byte, } targetOwner := "" - if bytes.HasSuffix(sniBytes, mainDomainSuffix) || bytes.Equal(sniBytes, mainDomainSuffix[1:]) { + if strings.HasSuffix(sni, mainDomainSuffix) || strings.EqualFold(sni, mainDomainSuffix[1:]) { // deliver default certificate for the main domain (*.codeberg.page) - sniBytes = mainDomainSuffix - sni = string(sniBytes) + sni = mainDomainSuffix } else { var targetRepo, targetBranch string - targetOwner, targetRepo, targetBranch = dnsutils.GetTargetFromDNS(sni, string(mainDomainSuffix), dnsLookupCache) + targetOwner, targetRepo, targetBranch = dnsutils.GetTargetFromDNS(sni, mainDomainSuffix, dnsLookupCache) if targetOwner == "" { // DNS not set up, return main certificate to redirect to the docs - sniBytes = mainDomainSuffix - sni = string(sniBytes) + sni = mainDomainSuffix } else { _, _ = targetRepo, targetBranch - _, valid := upstream.CheckCanonicalDomain(giteaClient, targetOwner, targetRepo, targetBranch, sni, string(mainDomainSuffix), canonicalDomainCache) + _, valid := upstream.CheckCanonicalDomain(giteaClient, targetOwner, targetRepo, targetBranch, sni, mainDomainSuffix, canonicalDomainCache) if !valid { - sniBytes = mainDomainSuffix - sni = string(sniBytes) + sni = mainDomainSuffix } } } @@ -98,9 +94,9 @@ func TLSConfig(mainDomainSuffix []byte, var tlsCertificate tls.Certificate var err error var ok bool - if tlsCertificate, ok = retrieveCertFromDB(sniBytes, mainDomainSuffix, dnsProvider, acmeUseRateLimits, certDB); !ok { + if tlsCertificate, ok = retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider, acmeUseRateLimits, certDB); !ok { // request a new certificate - if bytes.Equal(sniBytes, mainDomainSuffix) { + if strings.EqualFold(sni, mainDomainSuffix) { return nil, errors.New("won't request certificate for main domain, something really bad has happened") } @@ -192,7 +188,7 @@ func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error { return nil } -func retrieveCertFromDB(sni, mainDomainSuffix []byte, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) (tls.Certificate, bool) { +func retrieveCertFromDB(sni, mainDomainSuffix, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) (tls.Certificate, bool) { // parse certificate from database res, err := certDB.Get(string(sni)) if err != nil { @@ -208,7 +204,7 @@ func retrieveCertFromDB(sni, mainDomainSuffix []byte, dnsProvider string, acmeUs } // TODO: document & put into own function - if !bytes.Equal(sni, mainDomainSuffix) { + if !strings.EqualFold(sni, mainDomainSuffix) { tlsCertificate.Leaf, err = x509.ParseCertificate(tlsCertificate.Certificate[0]) if err != nil { panic(err) @@ -239,7 +235,7 @@ func retrieveCertFromDB(sni, mainDomainSuffix []byte, dnsProvider string, acmeUs var obtainLocks = sync.Map{} -func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Resource, user, dnsProvider string, mainDomainSuffix []byte, acmeUseRateLimits bool, keyDatabase database.CertDB) (tls.Certificate, error) { +func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Resource, user, dnsProvider, mainDomainSuffix string, acmeUseRateLimits bool, keyDatabase database.CertDB) (tls.Certificate, error) { name := strings.TrimPrefix(domains[0], "*") if dnsProvider == "" && len(domains[0]) > 0 && domains[0][0] == '*' { domains = domains[1:] @@ -252,7 +248,7 @@ func obtainCert(acmeClient *lego.Client, domains []string, renew *certificate.Re time.Sleep(100 * time.Millisecond) _, working = obtainLocks.Load(name) } - cert, ok := retrieveCertFromDB([]byte(name), mainDomainSuffix, dnsProvider, acmeUseRateLimits, keyDatabase) + cert, ok := retrieveCertFromDB(name, mainDomainSuffix, dnsProvider, acmeUseRateLimits, keyDatabase) if !ok { return tls.Certificate{}, errors.New("certificate failed in synchronous request") } @@ -405,7 +401,7 @@ func SetupAcmeConfig(acmeAPI, acmeMail, acmeEabHmac, acmeEabKID string, acmeAcce return myAcmeConfig, nil } -func SetupCertificates(mainDomainSuffix []byte, dnsProvider string, acmeConfig *lego.Config, acmeUseRateLimits, enableHTTPServer bool, challengeCache cache.SetGetKey, certDB database.CertDB) error { +func SetupCertificates(mainDomainSuffix, dnsProvider string, acmeConfig *lego.Config, acmeUseRateLimits, enableHTTPServer bool, challengeCache cache.SetGetKey, certDB database.CertDB) error { // getting main cert before ACME account so that we can fail here without hitting rate limits mainCertBytes, err := certDB.Get(string(mainDomainSuffix)) if err != nil { @@ -460,7 +456,7 @@ func SetupCertificates(mainDomainSuffix []byte, dnsProvider string, acmeConfig * return nil } -func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffix []byte, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) { +func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffix, dnsProvider string, acmeUseRateLimits bool, certDB database.CertDB) { for { // clean up expired certs now := time.Now() @@ -468,7 +464,7 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, mainDomainSuffi keyDatabaseIterator := certDB.Items() key, resBytes, err := keyDatabaseIterator.Next() for err == nil { - if !bytes.Equal(key, mainDomainSuffix) { + if !strings.EqualFold(string(key), mainDomainSuffix) { resGob := bytes.NewBuffer(resBytes) resDec := gob.NewDecoder(resGob) res := &certificate.Resource{} diff --git a/server/context/context.go b/server/context/context.go index 83cfed8..02001fa 100644 --- a/server/context/context.go +++ b/server/context/context.go @@ -2,7 +2,9 @@ package context import ( stdContext "context" + "io" "net/http" + "strings" ) type Context struct { @@ -31,6 +33,19 @@ func (c *Context) Response() *http.Response { return nil } +func (c *Context) String(raw string, status ...int) { + code := http.StatusOK + if len(status) != 0 { + code = status[0] + } + c.RespWriter.WriteHeader(code) + io.Copy(c.RespWriter, strings.NewReader(raw)) +} + +func (c *Context) IsMethod(m string) bool { + return c.Req.Method == m +} + func (c *Context) Redirect(uri string, statusCode int) { http.Redirect(c.RespWriter, c.Req, uri, statusCode) } @@ -41,3 +56,7 @@ func (c *Context) Redirect(uri string, statusCode int) { func (c *Context) Path() string { return c.Req.URL.Path } + +func (c *Context) Host() string { + return c.Req.URL.Host +} diff --git a/server/handler.go b/server/handler.go index 0c6a001..7d48013 100644 --- a/server/handler.go +++ b/server/handler.go @@ -3,7 +3,6 @@ package server import ( - "bytes" "net/http" "strings" @@ -27,7 +26,7 @@ const ( ) // Handler handles a single HTTP request to the web server. -func Handler(mainDomainSuffix, rawDomain []byte, +func Handler(mainDomainSuffix, rawDomain string, giteaClient *gitea.Client, giteaRoot, rawInfoPage string, blacklistedPaths, allowedCorsDomains []string, @@ -45,7 +44,7 @@ func Handler(mainDomainSuffix, rawDomain []byte, // Enable browser caching for up to 10 minutes ctx.RespWriter.Header().Set("Cache-Control", "public, max-age=600") - trimmedHost := utils.TrimHostPort([]byte(req.Host)) + trimmedHost := utils.TrimHostPort(req.Host) // Add HSTS for RawDomain and MainDomainSuffix if hsts := GetHSTSHeader(trimmedHost, mainDomainSuffix, rawDomain); hsts != "" { @@ -53,16 +52,16 @@ func Handler(mainDomainSuffix, rawDomain []byte, } // Block all methods not required for static pages - if !ctx.IsGet() && !ctx.IsHead() && !ctx.IsOptions() { - ctx.Response.Header.Set("Allow", "GET, HEAD, OPTIONS") - ctx.Error("Method not allowed", fasthttp.StatusMethodNotAllowed) + if !ctx.IsMethod(http.MethodGet) && !ctx.IsMethod(http.MethodHead) && !ctx.IsMethod(http.MethodOptions) { + ctx.RespWriter.Header().Set("Allow", http.MethodGet+", "+http.MethodHead+", "+http.MethodOptions) // duplic 1 + ctx.String("Method not allowed", fasthttp.StatusMethodNotAllowed) return } // Block blacklisted paths (like ACME challenges) for _, blacklistedPath := range blacklistedPaths { if strings.HasPrefix(ctx.Path(), blacklistedPath) { - html.ReturnErrorPage(ctx, fasthttp.StatusForbidden) + html.ReturnErrorPage(ctx, "", fasthttp.StatusForbidden) return } } @@ -70,7 +69,7 @@ func Handler(mainDomainSuffix, rawDomain []byte, // Allow CORS for specified domains allowCors := false for _, allowedCorsDomain := range allowedCorsDomains { - if bytes.Equal(trimmedHost, allowedCorsDomain) { + if strings.EqualFold(trimmedHost, allowedCorsDomain) { allowCors = true break } @@ -79,9 +78,9 @@ func Handler(mainDomainSuffix, rawDomain []byte, ctx.RespWriter.Header().Set(headerAccessControlAllowOrigin, "*") ctx.RespWriter.Header().Set(headerAccessControlAllowMethods, http.MethodGet+", "+http.MethodHead) } - ctx.RespWriter.Header().Set("Allow", http.MethodGet+", "+http.MethodHead+", "+http.MethodOptions) - if ctx.IsOptions() { + ctx.RespWriter.Header().Set("Allow", http.MethodGet+", "+http.MethodHead+", "+http.MethodOptions) // duplic 1 + if ctx.IsMethod(http.MethodOptions) { ctx.Response().StatusCode = http.StatusNoContent return } @@ -132,7 +131,7 @@ func Handler(mainDomainSuffix, rawDomain []byte, } log.Debug().Msg("preparations") - if rawDomain != nil && bytes.Equal(trimmedHost, rawDomain) { + if rawDomain != "" && strings.EqualFold(trimmedHost, rawDomain) { // Serve raw content from RawDomain log.Debug().Msg("raw domain") @@ -166,7 +165,7 @@ func Handler(mainDomainSuffix, rawDomain []byte, return } log.Debug().Msg("missing branch") - html.ReturnErrorPage(ctx, http.StatusFailedDependency) + html.ReturnErrorPage(ctx, "", http.StatusFailedDependency) return } @@ -181,12 +180,12 @@ func Handler(mainDomainSuffix, rawDomain []byte, canonicalDomainCache) return - } else if bytes.HasSuffix(trimmedHost, mainDomainSuffix) { + } else if strings.HasSuffix(trimmedHost, mainDomainSuffix) { // Serve pages from subdomains of MainDomainSuffix log.Debug().Msg("main domain suffix") pathElements := strings.Split(strings.Trim(ctx.Path(), "/"), "/") - targetOwner = string(bytes.TrimSuffix(trimmedHost, mainDomainSuffix)) + targetOwner = strings.TrimSuffix(trimmedHost, mainDomainSuffix) targetRepo = pathElements[0] targetPath = strings.Trim(strings.Join(pathElements[1:], "/"), "/") @@ -215,7 +214,7 @@ func Handler(mainDomainSuffix, rawDomain []byte, targetOptions, targetOwner, targetRepo, targetBranch, targetPath, canonicalDomainCache) } else { - html.ReturnErrorPage(ctx, http.StatusFailedDependency) + html.ReturnErrorPage(ctx, "", http.StatusFailedDependency) } return } @@ -231,7 +230,7 @@ func Handler(mainDomainSuffix, rawDomain []byte, targetOptions, targetOwner, targetRepo, targetBranch, targetPath, canonicalDomainCache) } else { - html.ReturnErrorPage(ctx, http.StatusFailedDependency) + html.ReturnErrorPage(ctx, "", http.StatusFailedDependency) } return } @@ -262,7 +261,7 @@ func Handler(mainDomainSuffix, rawDomain []byte, } // Couldn't find a valid repo/branch - html.ReturnErrorPage(ctx, http.StatusFailedDependency) + html.ReturnErrorPage(ctx, "", http.StatusFailedDependency) return } else { trimmedHostStr := string(trimmedHost) @@ -270,7 +269,7 @@ func Handler(mainDomainSuffix, rawDomain []byte, // Serve pages from external domains targetOwner, targetRepo, targetBranch = dns.GetTargetFromDNS(trimmedHostStr, string(mainDomainSuffix), dnsLookupCache) if targetOwner == "" { - html.ReturnErrorPage(ctx, http.StatusFailedDependency) + html.ReturnErrorPage(ctx, "", http.StatusFailedDependency) return } @@ -288,7 +287,7 @@ func Handler(mainDomainSuffix, rawDomain []byte, targetRepo, targetBranch, pathElements, canonicalLink) { canonicalDomain, valid := upstream.CheckCanonicalDomain(giteaClient, targetOwner, targetRepo, targetBranch, trimmedHostStr, string(mainDomainSuffix), canonicalDomainCache) if !valid { - html.ReturnErrorPage(ctx, http.StatusMisdirectedRequest) + html.ReturnErrorPage(ctx, "", http.StatusMisdirectedRequest) return } else if canonicalDomain != trimmedHostStr { // only redirect if the target is also a codeberg page! @@ -298,7 +297,7 @@ func Handler(mainDomainSuffix, rawDomain []byte, return } - html.ReturnErrorPage(ctx, http.StatusFailedDependency) + html.ReturnErrorPage(ctx, "", http.StatusFailedDependency) return } @@ -309,7 +308,7 @@ func Handler(mainDomainSuffix, rawDomain []byte, return } - html.ReturnErrorPage(ctx, http.StatusFailedDependency) + html.ReturnErrorPage(ctx, "", http.StatusFailedDependency) return } } diff --git a/server/handler_fasthttp.go b/server/handler_fasthttp.go index 5d102d3..628455a 100644 --- a/server/handler_fasthttp.go +++ b/server/handler_fasthttp.go @@ -20,7 +20,7 @@ import ( ) // Handler handles a single HTTP request to the web server. -func Handler(mainDomainSuffix, rawDomain []byte, +func Handler(mainDomainSuffix, rawDomain string, giteaClient *gitea.Client, giteaRoot, rawInfoPage string, blacklistedPaths, allowedCorsDomains []string, @@ -37,7 +37,7 @@ func Handler(mainDomainSuffix, rawDomain []byte, // Enable browser caching for up to 10 minutes ctx.Response.Header.Set("Cache-Control", "public, max-age=600") - trimmedHost := utils.TrimHostPort(ctx.Request.Host()) + trimmedHost := utils.TrimHostPort(string(ctx.Request.Host())) // Add HSTS for RawDomain and MainDomainSuffix if hsts := GetHSTSHeader(trimmedHost, mainDomainSuffix, rawDomain); hsts != "" { @@ -53,7 +53,7 @@ func Handler(mainDomainSuffix, rawDomain []byte, // Block blacklisted paths (like ACME challenges) for _, blacklistedPath := range blacklistedPaths { - if bytes.HasPrefix(ctx.Path(), []byte(blacklistedPath)) { + if strings.HasPrefix(string(ctx.Path()), blacklistedPath) { html.ReturnErrorPage(ctx, fasthttp.StatusForbidden) return } @@ -62,7 +62,7 @@ func Handler(mainDomainSuffix, rawDomain []byte, // Allow CORS for specified domains allowCors := false for _, allowedCorsDomain := range allowedCorsDomains { - if bytes.Equal(trimmedHost, []byte(allowedCorsDomain)) { + if strings.EqualFold(trimmedHost, allowedCorsDomain) { allowCors = true break } @@ -123,7 +123,7 @@ func Handler(mainDomainSuffix, rawDomain []byte, } log.Debug().Msg("preparations") - if rawDomain != nil && bytes.Equal(trimmedHost, rawDomain) { + if rawDomain != "" && strings.EqualFold(trimmedHost, rawDomain) { // Serve raw content from RawDomain log.Debug().Msg("raw domain") @@ -172,12 +172,12 @@ func Handler(mainDomainSuffix, rawDomain []byte, canonicalDomainCache) return - } else if bytes.HasSuffix(trimmedHost, mainDomainSuffix) { + } else if strings.HasSuffix(trimmedHost, mainDomainSuffix) { // Serve pages from subdomains of MainDomainSuffix log.Debug().Msg("main domain suffix") - pathElements := strings.Split(string(bytes.Trim(ctx.Request.URI().Path(), "/")), "/") - targetOwner = string(bytes.TrimSuffix(trimmedHost, mainDomainSuffix)) + pathElements := strings.Split(strings.Trim(string(ctx.Request.URI().Path()), "/"), "/") + targetOwner = strings.TrimSuffix(trimmedHost, mainDomainSuffix) targetRepo = pathElements[0] targetPath = strings.Trim(strings.Join(pathElements[1:], "/"), "/") diff --git a/server/helpers.go b/server/helpers.go index 6d55ddf..221bf6e 100644 --- a/server/helpers.go +++ b/server/helpers.go @@ -1,13 +1,13 @@ package server import ( - "bytes" + "strings" ) // GetHSTSHeader returns a HSTS header with includeSubdomains & preload for MainDomainSuffix and RawDomain, or an empty // string for custom domains. -func GetHSTSHeader(host, mainDomainSuffix, rawDomain []byte) string { - if bytes.HasSuffix(host, mainDomainSuffix) || bytes.Equal(host, rawDomain) { +func GetHSTSHeader(host, mainDomainSuffix, rawDomain string) string { + if strings.HasSuffix(host, mainDomainSuffix) || strings.EqualFold(host, rawDomain) { return "max-age=63072000; includeSubdomains; preload" } else { return "" diff --git a/server/setup.go b/server/setup.go index 421f047..b0110d0 100644 --- a/server/setup.go +++ b/server/setup.go @@ -3,53 +3,32 @@ package server import ( - "bytes" - "fmt" "net/http" - "time" - - "github.com/rs/zerolog/log" - "github.com/valyala/fasthttp" + "strings" "codeberg.org/codeberg/pages/server/cache" + "codeberg.org/codeberg/pages/server/context" "codeberg.org/codeberg/pages/server/utils" ) -type fasthttpLogger struct{} - -func (fasthttpLogger) Printf(format string, args ...interface{}) { - log.Printf("FastHTTP: %s", fmt.Sprintf(format, args...)) +func SetupServer(handler http.HandlerFunc) http.HandlerFunc { + // TODO: enagle gzip compression + return handler } -func SetupServer(handler fasthttp.RequestHandler) *fasthttp.Server { - // Enable compression by wrapping the handler with the compression function provided by FastHTTP - compressedHandler := fasthttp.CompressHandlerBrotliLevel(handler, fasthttp.CompressBrotliBestSpeed, fasthttp.CompressBestSpeed) +func SetupHTTPACMEChallengeServer(challengeCache cache.SetGetKey) http.HandlerFunc { + challengePath := "/.well-known/acme-challenge/" - return &fasthttp.Server{ - Handler: compressedHandler, - DisablePreParseMultipartForm: true, - NoDefaultServerHeader: true, - NoDefaultDate: true, - ReadTimeout: 30 * time.Second, // needs to be this high for ACME certificates with ZeroSSL & HTTP-01 challenge - Logger: fasthttpLogger{}, - } -} - -func SetupHTTPACMEChallengeServer(challengeCache cache.SetGetKey) *fasthttp.Server { - challengePath := []byte("/.well-known/acme-challenge/") - - return &fasthttp.Server{ - Handler: func(ctx *fasthttp.RequestCtx) { - if bytes.HasPrefix(ctx.Path(), challengePath) { - challenge, ok := challengeCache.Get(string(utils.TrimHostPort(ctx.Host())) + "/" + string(bytes.TrimPrefix(ctx.Path(), challengePath))) - if !ok || challenge == nil { - ctx.SetStatusCode(http.StatusNotFound) - ctx.SetBodyString("no challenge for this token") - } - ctx.SetBodyString(challenge.(string)) - } else { - ctx.Redirect("https://"+string(ctx.Host())+string(ctx.RequestURI()), http.StatusMovedPermanently) + return func(w http.ResponseWriter, req *http.Request) { + ctx := context.New(w, req) + if strings.HasPrefix(ctx.Path(), challengePath) { + challenge, ok := challengeCache.Get(utils.TrimHostPort(ctx.Host()) + "/" + string(strings.TrimPrefix(ctx.Path(), challengePath))) + if !ok || challenge == nil { + ctx.String("no challenge for this token", http.StatusNotFound) } - }, + ctx.String(challenge.(string)) + } else { + ctx.Redirect("https://"+string(ctx.Host())+string(ctx.Path()), http.StatusMovedPermanently) + } } } diff --git a/server/setup_fasthttp.go b/server/setup_fasthttp.go index 8269e8a..98aab45 100644 --- a/server/setup_fasthttp.go +++ b/server/setup_fasthttp.go @@ -3,8 +3,8 @@ package server import ( - "bytes" "net/http" + "strings" "time" "github.com/valyala/fasthttp" @@ -27,12 +27,12 @@ func SetupServer(handler fasthttp.RequestHandler) *fasthttp.Server { } func SetupHTTPACMEChallengeServer(challengeCache cache.SetGetKey) *fasthttp.Server { - challengePath := []byte("/.well-known/acme-challenge/") + challengePath := "/.well-known/acme-challenge/" return &fasthttp.Server{ Handler: func(ctx *fasthttp.RequestCtx) { - if bytes.HasPrefix(ctx.Path(), challengePath) { - challenge, ok := challengeCache.Get(string(utils.TrimHostPort(ctx.Host())) + "/" + string(bytes.TrimPrefix(ctx.Path(), challengePath))) + if strings.HasPrefix(string(ctx.Path()), challengePath) { + challenge, ok := challengeCache.Get(utils.TrimHostPort(string(ctx.Host())) + "/" + strings.TrimPrefix(string(ctx.Path()), challengePath)) if !ok || challenge == nil { ctx.SetStatusCode(http.StatusNotFound) ctx.SetBodyString("no challenge for this token") diff --git a/server/try.go b/server/try.go index d65d1b2..656446c 100644 --- a/server/try.go +++ b/server/try.go @@ -3,7 +3,6 @@ package server import ( - "bytes" "net/http" "strings" @@ -16,7 +15,7 @@ import ( // tryUpstream forwards the target request to the Gitea API, and shows an error page on failure. func tryUpstream(ctx *context.Context, giteaClient *gitea.Client, - mainDomainSuffix, trimmedHost []byte, + mainDomainSuffix, trimmedHost string, targetOptions *upstream.Options, targetOwner, targetRepo, targetBranch, targetPath string, @@ -24,7 +23,7 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client, canonicalDomainCache cache.SetGetKey, ) { // check if a canonical domain exists on a request on MainDomain - if bytes.HasSuffix(trimmedHost, mainDomainSuffix) { + if strings.HasSuffix(trimmedHost, mainDomainSuffix) { canonicalDomain, _ := upstream.CheckCanonicalDomain(giteaClient, targetOwner, targetRepo, targetBranch, "", string(mainDomainSuffix), canonicalDomainCache) if !strings.HasSuffix(strings.SplitN(canonicalDomain, "/", 2)[0], string(mainDomainSuffix)) { canonicalPath := ctx.Req.RequestURI @@ -47,6 +46,6 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client, // Try to request the file from the Gitea API if !targetOptions.Upstream(ctx, giteaClient) { - html.ReturnErrorPage(w, ctx.Response().StatusCode) + html.ReturnErrorPage(ctx, "", ctx.Response().StatusCode) } } diff --git a/server/try_fasthttp.go b/server/try_fasthttp.go index 1bc0af2..c4913a1 100644 --- a/server/try_fasthttp.go +++ b/server/try_fasthttp.go @@ -3,7 +3,6 @@ package server import ( - "bytes" "strings" "github.com/valyala/fasthttp" @@ -16,7 +15,7 @@ import ( // tryUpstream forwards the target request to the Gitea API, and shows an error page on failure. func tryUpstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client, - mainDomainSuffix, trimmedHost []byte, + mainDomainSuffix, trimmedHost string, targetOptions *upstream.Options, targetOwner, targetRepo, targetBranch, targetPath string, @@ -24,7 +23,7 @@ func tryUpstream(ctx *fasthttp.RequestCtx, giteaClient *gitea.Client, canonicalDomainCache cache.SetGetKey, ) { // check if a canonical domain exists on a request on MainDomain - if bytes.HasSuffix(trimmedHost, mainDomainSuffix) { + if strings.HasSuffix(trimmedHost, mainDomainSuffix) { canonicalDomain, _ := upstream.CheckCanonicalDomain(giteaClient, targetOwner, targetRepo, targetBranch, "", string(mainDomainSuffix), canonicalDomainCache) if !strings.HasSuffix(strings.SplitN(canonicalDomain, "/", 2)[0], string(mainDomainSuffix)) { canonicalPath := string(ctx.RequestURI()) diff --git a/server/upstream/upstream_std.go b/server/upstream/upstream_std.go index eb34ebf..72ca66c 100644 --- a/server/upstream/upstream_std.go +++ b/server/upstream/upstream_std.go @@ -31,7 +31,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (fin branch := GetBranchTimestamp(giteaClient, o.TargetOwner, o.TargetRepo, o.TargetBranch) if branch == nil { - html.ReturnErrorPage(ctx, http.StatusFailedDependency) + html.ReturnErrorPage(ctx, "", http.StatusFailedDependency) return true } o.TargetBranch = branch.Branch @@ -39,7 +39,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (fin } if o.TargetOwner == "" || o.TargetRepo == "" || o.TargetBranch == "" { - html.ReturnErrorPage(ctx, http.StatusBadRequest) + html.ReturnErrorPage(ctx, "", http.StatusBadRequest) return true } @@ -94,7 +94,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (fin } if res != nil && (err != nil || res.StatusCode != http.StatusOK) { log.Printf("Couldn't fetch contents (status code %d): %v\n", res.StatusCode, err) - html.ReturnErrorPage(ctx, http.StatusInternalServerError) + html.ReturnErrorPage(ctx, "", http.StatusInternalServerError) return true } @@ -136,7 +136,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (fin _, err := io.Copy(ctx.RespWriter, reader) if err != nil { log.Printf("Couldn't write body: %s\n", err) - html.ReturnErrorPage(ctx, http.StatusInternalServerError) + html.ReturnErrorPage(ctx, "", http.StatusInternalServerError) return true } } diff --git a/server/utils/utils.go b/server/utils/utils.go index 7be330f..30f948d 100644 --- a/server/utils/utils.go +++ b/server/utils/utils.go @@ -1,9 +1,11 @@ package utils -import "bytes" +import ( + "strings" +) -func TrimHostPort(host []byte) []byte { - i := bytes.IndexByte(host, ':') +func TrimHostPort(host string) string { + i := strings.IndexByte(host, ':') if i >= 0 { return host[:i] }