mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-06-22 10:02:15 +00:00
First, why was this header here in the first place? Cloudflare! Cloudflare had a optimization setting called "auto-minfy" and would minify HTML,JS,CSS - this included removing extra whitespaces from `<code>` elements. That's a problem because files are shown per-line with a `<code>` element and thus results in indentation being completely gone. Gitea added a FAQ entry for this [1], but on the same day decided to add the workaround in Gitea, the `no-transform` header [2]. I can't find a reference of this option and some posts suggests it's been removed. Thus it no longer serves a need to be present in Forgejo. That wasn't my intentional motivation to remove this. This header is also causing that HAProxy will not compress responses [3] from Forgejo which is not ideal for Codeberg, this behavior cannot be turned off or be worked around. Potential risk, some other CDN or some other Cloudflare option might still do this removal of whitespace in `<code>` HTML tags, it seems better to disable the feature than to have Forgejo add a header which is also causing other side-effects. I'm not aware of this another CDN of Cloudflare option so I don't want to mark it as breaking. [1]: https://github.com/go-gitea/gitea/pull/20430 [2]: https://github.com/go-gitea/gitea/pull/20432 [3]: https://docs.haproxy.org/3.3/configuration.html#:~:text=the%20response%20contains%20the%20%22no-transform%22%20value%20in%20the%20%22Cache-control%22%20%20%20%20%20header Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12905 Reviewed-by: Otto <otto@codeberg.org> Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org> Reviewed-by: 0ko <0ko@noreply.codeberg.org>
101 lines
3.3 KiB
Go
101 lines
3.3 KiB
Go
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package httpcache
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"forgejo.org/modules/setting"
|
|
)
|
|
|
|
// SetCacheControlInHeader sets suitable cache-control headers in the response
|
|
func SetCacheControlInHeader(h http.Header, maxAge time.Duration) {
|
|
directives := make([]string, 0, 2)
|
|
|
|
// "max-age=0 + must-revalidate" (aka "no-cache") is preferred instead of "no-store"
|
|
// because browsers may restore some input fields after navigate-back / reload a page.
|
|
if setting.IsProd {
|
|
if maxAge == 0 {
|
|
directives = append(directives, "max-age=0", "private", "must-revalidate")
|
|
} else {
|
|
directives = append(directives, "private", "max-age="+strconv.Itoa(int(maxAge.Seconds())))
|
|
}
|
|
} else {
|
|
directives = append(directives, "max-age=0", "private", "must-revalidate")
|
|
|
|
// to remind users they are using non-prod setting.
|
|
h.Set("X-Gitea-Debug", "RUN_MODE="+setting.RunMode)
|
|
h.Set("X-Forgejo-Debug", "RUN_MODE="+setting.RunMode)
|
|
}
|
|
|
|
h.Set("Cache-Control", strings.Join(directives, ", "))
|
|
}
|
|
|
|
func ServeContentWithCacheControl(w http.ResponseWriter, req *http.Request, name string, modTime time.Time, content io.ReadSeeker) {
|
|
SetCacheControlInHeader(w.Header(), setting.StaticCacheTime)
|
|
http.ServeContent(w, req, name, modTime, content)
|
|
}
|
|
|
|
// HandleGenericETagCache handles ETag-based caching for a HTTP request.
|
|
// It returns true if the request was handled.
|
|
func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag string) (handled bool) {
|
|
if len(etag) > 0 {
|
|
w.Header().Set("Etag", etag)
|
|
if checkIfNoneMatchIsValid(req, etag) {
|
|
w.WriteHeader(http.StatusNotModified)
|
|
return true
|
|
}
|
|
}
|
|
SetCacheControlInHeader(w.Header(), setting.StaticCacheTime)
|
|
return false
|
|
}
|
|
|
|
// checkIfNoneMatchIsValid tests if the header If-None-Match matches the ETag
|
|
func checkIfNoneMatchIsValid(req *http.Request, etag string) bool {
|
|
ifNoneMatch := req.Header.Get("If-None-Match")
|
|
if len(ifNoneMatch) > 0 {
|
|
for item := range strings.SplitSeq(ifNoneMatch, ",") {
|
|
item = strings.TrimPrefix(strings.TrimSpace(item), "W/") // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag#directives
|
|
if item == etag {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// HandleGenericETagTimeCache handles ETag-based caching with Last-Modified caching for a HTTP request.
|
|
// It returns true if the request was handled.
|
|
func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag string, lastModified *time.Time) (handled bool) {
|
|
if len(etag) > 0 {
|
|
w.Header().Set("Etag", etag)
|
|
}
|
|
if lastModified != nil && !lastModified.IsZero() {
|
|
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
|
|
w.Header().Set("Last-Modified", lastModified.UTC().Format(http.TimeFormat))
|
|
}
|
|
|
|
if len(etag) > 0 {
|
|
if checkIfNoneMatchIsValid(req, etag) {
|
|
w.WriteHeader(http.StatusNotModified)
|
|
return true
|
|
}
|
|
}
|
|
if lastModified != nil && !lastModified.IsZero() {
|
|
ifModifiedSince := req.Header.Get("If-Modified-Since")
|
|
if ifModifiedSince != "" {
|
|
t, err := time.Parse(http.TimeFormat, ifModifiedSince)
|
|
if err == nil && lastModified.Unix() <= t.Unix() {
|
|
w.WriteHeader(http.StatusNotModified)
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
SetCacheControlInHeader(w.Header(), setting.StaticCacheTime)
|
|
return false
|
|
}
|