1
0
Fork 0

Adding upstream version 2.52.6.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-17 06:50:16 +02:00
parent a960158181
commit 6d002e9543
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
441 changed files with 95392 additions and 0 deletions

44
middleware/etag/config.go Normal file
View file

@ -0,0 +1,44 @@
package etag
import (
"github.com/gofiber/fiber/v2"
)
// Config defines the config for middleware.
type Config struct {
// Weak indicates that a weak validator is used. Weak etags are easy
// to generate, but are far less useful for comparisons. Strong
// validators are ideal for comparisons but can be very difficult
// to generate efficiently. Weak ETag values of two representations
// of the same resources might be semantically equivalent, but not
// byte-for-byte identical. This means weak etags prevent caching
// when byte range requests are used, but strong etags mean range
// requests can still be cached.
Weak bool
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: nil
Next func(c *fiber.Ctx) bool
}
// ConfigDefault is the default config
var ConfigDefault = Config{
Weak: false,
Next: nil,
}
// Helper function to set default values
func configDefault(config ...Config) Config {
// Return default config if nothing provided
if len(config) < 1 {
return ConfigDefault
}
// Override default config
cfg := config[0]
// Set default values
return cfg
}

116
middleware/etag/etag.go Normal file
View file

@ -0,0 +1,116 @@
package etag
import (
"bytes"
"hash/crc32"
"github.com/gofiber/fiber/v2"
"github.com/valyala/bytebufferpool"
)
// New creates a new middleware handler
func New(config ...Config) fiber.Handler {
// Set default config
cfg := configDefault(config...)
var (
normalizedHeaderETag = []byte("Etag")
weakPrefix = []byte("W/")
)
const crcPol = 0xD5828281
crc32q := crc32.MakeTable(crcPol)
// Return new handler
return func(c *fiber.Ctx) error {
// Don't execute middleware if Next returns true
if cfg.Next != nil && cfg.Next(c) {
return c.Next()
}
// Return err if next handler returns one
if err := c.Next(); err != nil {
return err
}
// Don't generate ETags for invalid responses
if c.Response().StatusCode() != fiber.StatusOK {
return nil
}
body := c.Response().Body()
// Skips ETag if no response body is present
if len(body) == 0 {
return nil
}
// Skip ETag if header is already present
if c.Response().Header.PeekBytes(normalizedHeaderETag) != nil {
return nil
}
// Generate ETag for response
bb := bytebufferpool.Get()
defer bytebufferpool.Put(bb)
// Enable weak tag
if cfg.Weak {
_, _ = bb.Write(weakPrefix) //nolint:errcheck // This will never fail
}
_ = bb.WriteByte('"') //nolint:errcheck // This will never fail
bb.B = appendUint(bb.Bytes(), uint32(len(body)))
_ = bb.WriteByte('-') //nolint:errcheck // This will never fail
bb.B = appendUint(bb.Bytes(), crc32.Checksum(body, crc32q))
_ = bb.WriteByte('"') //nolint:errcheck // This will never fail
etag := bb.Bytes()
// Get ETag header from request
clientEtag := c.Request().Header.Peek(fiber.HeaderIfNoneMatch)
// Check if client's ETag is weak
if bytes.HasPrefix(clientEtag, weakPrefix) {
// Check if server's ETag is weak
if bytes.Equal(clientEtag[2:], etag) || bytes.Equal(clientEtag[2:], etag[2:]) {
// W/1 == 1 || W/1 == W/1
c.Context().ResetBody()
return c.SendStatus(fiber.StatusNotModified)
}
// W/1 != W/2 || W/1 != 2
c.Response().Header.SetCanonical(normalizedHeaderETag, etag)
return nil
}
if bytes.Contains(clientEtag, etag) {
// 1 == 1
c.Context().ResetBody()
return c.SendStatus(fiber.StatusNotModified)
}
// 1 != 2
c.Response().Header.SetCanonical(normalizedHeaderETag, etag)
return nil
}
}
// appendUint appends n to dst and returns the extended dst.
func appendUint(dst []byte, n uint32) []byte {
var b [20]byte
buf := b[:]
i := len(buf)
var q uint32
for n >= 10 {
i--
q = n / 10
buf[i] = '0' + byte(n-q*10)
n = q
}
i--
buf[i] = '0' + byte(n)
dst = append(dst, buf[i:]...)
return dst
}

View file

@ -0,0 +1,291 @@
package etag
import (
"bytes"
"io"
"net/http/httptest"
"testing"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/utils"
"github.com/valyala/fasthttp"
)
// go test -run Test_ETag_Next
func Test_ETag_Next(t *testing.T) {
t.Parallel()
app := fiber.New()
app.Use(New(Config{
Next: func(_ *fiber.Ctx) bool {
return true
},
}))
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode)
}
// go test -run Test_ETag_SkipError
func Test_ETag_SkipError(t *testing.T) {
t.Parallel()
app := fiber.New()
app.Use(New())
app.Get("/", func(c *fiber.Ctx) error {
return fiber.ErrForbidden
})
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, fiber.StatusForbidden, resp.StatusCode)
}
// go test -run Test_ETag_NotStatusOK
func Test_ETag_NotStatusOK(t *testing.T) {
t.Parallel()
app := fiber.New()
app.Use(New())
app.Get("/", func(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusCreated)
})
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, fiber.StatusCreated, resp.StatusCode)
}
// go test -run Test_ETag_NoBody
func Test_ETag_NoBody(t *testing.T) {
t.Parallel()
app := fiber.New()
app.Use(New())
app.Get("/", func(c *fiber.Ctx) error {
return nil
})
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
}
// go test -run Test_ETag_NewEtag
func Test_ETag_NewEtag(t *testing.T) {
t.Parallel()
t.Run("without HeaderIfNoneMatch", func(t *testing.T) {
t.Parallel()
testETagNewEtag(t, false, false)
})
t.Run("with HeaderIfNoneMatch and not matched", func(t *testing.T) {
t.Parallel()
testETagNewEtag(t, true, false)
})
t.Run("with HeaderIfNoneMatch and matched", func(t *testing.T) {
t.Parallel()
testETagNewEtag(t, true, true)
})
}
func testETagNewEtag(t *testing.T, headerIfNoneMatch, matched bool) { //nolint:revive // We're in a test, so using bools as a flow-control is fine
t.Helper()
app := fiber.New()
app.Use(New())
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, World!")
})
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
if headerIfNoneMatch {
etag := `"non-match"`
if matched {
etag = `"13-1831710635"`
}
req.Header.Set(fiber.HeaderIfNoneMatch, etag)
}
resp, err := app.Test(req)
utils.AssertEqual(t, nil, err)
if !headerIfNoneMatch || !matched {
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
utils.AssertEqual(t, `"13-1831710635"`, resp.Header.Get(fiber.HeaderETag))
return
}
if matched {
utils.AssertEqual(t, fiber.StatusNotModified, resp.StatusCode)
b, err := io.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, 0, len(b))
}
}
// go test -run Test_ETag_WeakEtag
func Test_ETag_WeakEtag(t *testing.T) {
t.Parallel()
t.Run("without HeaderIfNoneMatch", func(t *testing.T) {
t.Parallel()
testETagWeakEtag(t, false, false)
})
t.Run("with HeaderIfNoneMatch and not matched", func(t *testing.T) {
t.Parallel()
testETagWeakEtag(t, true, false)
})
t.Run("with HeaderIfNoneMatch and matched", func(t *testing.T) {
t.Parallel()
testETagWeakEtag(t, true, true)
})
}
func testETagWeakEtag(t *testing.T, headerIfNoneMatch, matched bool) { //nolint:revive // We're in a test, so using bools as a flow-control is fine
t.Helper()
app := fiber.New()
app.Use(New(Config{Weak: true}))
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, World!")
})
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
if headerIfNoneMatch {
etag := `W/"non-match"`
if matched {
etag = `W/"13-1831710635"`
}
req.Header.Set(fiber.HeaderIfNoneMatch, etag)
}
resp, err := app.Test(req)
utils.AssertEqual(t, nil, err)
if !headerIfNoneMatch || !matched {
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
utils.AssertEqual(t, `W/"13-1831710635"`, resp.Header.Get(fiber.HeaderETag))
return
}
if matched {
utils.AssertEqual(t, fiber.StatusNotModified, resp.StatusCode)
b, err := io.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, 0, len(b))
}
}
// go test -run Test_ETag_CustomEtag
func Test_ETag_CustomEtag(t *testing.T) {
t.Parallel()
t.Run("without HeaderIfNoneMatch", func(t *testing.T) {
t.Parallel()
testETagCustomEtag(t, false, false)
})
t.Run("with HeaderIfNoneMatch and not matched", func(t *testing.T) {
t.Parallel()
testETagCustomEtag(t, true, false)
})
t.Run("with HeaderIfNoneMatch and matched", func(t *testing.T) {
t.Parallel()
testETagCustomEtag(t, true, true)
})
}
func testETagCustomEtag(t *testing.T, headerIfNoneMatch, matched bool) { //nolint:revive // We're in a test, so using bools as a flow-control is fine
t.Helper()
app := fiber.New()
app.Use(New())
app.Get("/", func(c *fiber.Ctx) error {
c.Set(fiber.HeaderETag, `"custom"`)
if bytes.Equal(c.Request().Header.Peek(fiber.HeaderIfNoneMatch), []byte(`"custom"`)) {
return c.SendStatus(fiber.StatusNotModified)
}
return c.SendString("Hello, World!")
})
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
if headerIfNoneMatch {
etag := `"non-match"`
if matched {
etag = `"custom"`
}
req.Header.Set(fiber.HeaderIfNoneMatch, etag)
}
resp, err := app.Test(req)
utils.AssertEqual(t, nil, err)
if !headerIfNoneMatch || !matched {
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
utils.AssertEqual(t, `"custom"`, resp.Header.Get(fiber.HeaderETag))
return
}
if matched {
utils.AssertEqual(t, fiber.StatusNotModified, resp.StatusCode)
b, err := io.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, 0, len(b))
}
}
// go test -run Test_ETag_CustomEtagPut
func Test_ETag_CustomEtagPut(t *testing.T) {
t.Parallel()
app := fiber.New()
app.Use(New())
app.Put("/", func(c *fiber.Ctx) error {
c.Set(fiber.HeaderETag, `"custom"`)
if !bytes.Equal(c.Request().Header.Peek(fiber.HeaderIfMatch), []byte(`"custom"`)) {
return c.SendStatus(fiber.StatusPreconditionFailed)
}
return c.SendString("Hello, World!")
})
req := httptest.NewRequest(fiber.MethodPut, "/", nil)
req.Header.Set(fiber.HeaderIfMatch, `"non-match"`)
resp, err := app.Test(req)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, fiber.StatusPreconditionFailed, resp.StatusCode)
}
// go test -v -run=^$ -bench=Benchmark_Etag -benchmem -count=4
func Benchmark_Etag(b *testing.B) {
app := fiber.New()
app.Use(New())
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, World!")
})
h := app.Handler()
fctx := &fasthttp.RequestCtx{}
fctx.Request.Header.SetMethod(fiber.MethodGet)
fctx.Request.SetRequestURI("/")
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
h(fctx)
}
utils.AssertEqual(b, 200, fctx.Response.Header.StatusCode())
utils.AssertEqual(b, `"13-1831710635"`, string(fctx.Response.Header.Peek(fiber.HeaderETag)))
}