Merge remote-tracking branch 'origin/master'

pull/2796/head
Muhammed Efe Cetin 2024-01-13 18:26:07 +03:00
commit 1588b6b602
No known key found for this signature in database
GPG Key ID: 0AA4D45CBAA86F73
27 changed files with 529 additions and 285 deletions

View File

@ -0,0 +1,74 @@
name: "📝 Feature Proposal for v3"
title: "📝 [v3 Proposal]: "
description: Propose a feature or improvement for Fiber v3.
labels: ["📝 Proposal", "v3"]
body:
- type: markdown
id: notice
attributes:
value: |
### Notice
- For questions, join our [Discord server](https://gofiber.io/discord).
- Please write in clear, understandable English.
- Ensure your proposal aligns with Express design principles and HTTP RFC standards.
- Describe features expected to remain stable and not require changes in the foreseeable future.
- type: textarea
id: description
attributes:
label: "Feature Proposal Description"
description: "A clear and detailed description of the feature you are proposing for Fiber v3. How should it work, and what API endpoints and methods would it involve?"
placeholder: "Describe your feature proposal clearly and in detail, including API endpoints and methods."
validations:
required: true
- type: textarea
id: express-alignment
attributes:
label: "Alignment with Express API"
description: "Explain how your proposal aligns with the design and API of Express.js. Provide comparative examples if possible."
placeholder: "Outline how the feature aligns with Express.js design principles and API standards."
validations:
required: true
- type: textarea
id: standards-compliance
attributes:
label: "HTTP RFC Standards Compliance"
description: "Confirm that the feature complies with HTTP RFC standards, and describe any relevant aspects."
placeholder: "Detail how the feature adheres to HTTP RFC standards."
validations:
required: true
- type: textarea
id: stability
attributes:
label: "API Stability"
description: "Discuss the expected stability of the feature and its API. How do you ensure that it will not require changes or deprecations in the near future?"
placeholder: "Describe measures taken to ensure the feature's API stability over time."
validations:
required: true
- type: textarea
id: examples
attributes:
label: "Feature Examples"
description: "Provide concrete examples and code snippets to illustrate how the proposed feature should function."
placeholder: "Share code snippets that exemplify the proposed feature and its usage."
render: go
validations:
required: true
- type: checkboxes
id: terms
attributes:
label: "Checklist:"
description: "By submitting this issue, you confirm that:"
options:
- label: "I agree to follow Fiber's [Code of Conduct](https://github.com/gofiber/fiber/blob/master/.github/CODE_OF_CONDUCT.md)."
required: true
- label: "I have searched for existing issues that describe my proposal before opening this one."
required: true
- label: "I understand that a proposal that does not meet these guidelines may be closed without explanation."
required: true

View File

@ -0,0 +1,54 @@
name: "🧹 v3 Maintenance Task"
title: "🧹 [v3 Maintenance]: "
description: Describe a maintenance task for the v3 of the Fiber project.
labels: ["🧹 Updates", "v3"]
body:
- type: markdown
id: notice
attributes:
value: |
### Notice
- Before submitting a maintenance task, please check if a similar task has already been filed.
- Clearly outline the purpose of the maintenance task and its impact on the project.
- Use clear and understandable English.
- type: textarea
id: task-description
attributes:
label: "Maintenance Task Description"
description: "Provide a detailed description of the maintenance task. Include any specific areas of the codebase that require attention, and the desired outcomes of this task."
placeholder: "Detail the maintenance task, specifying what needs to be done and why it is necessary."
validations:
required: true
- type: textarea
id: impact
attributes:
label: "Impact on the Project"
description: "Explain the impact this maintenance will have on the project. Include benefits and potential risks if applicable."
placeholder: "Describe how completing this task will benefit the project, or the risks of not addressing it."
validations:
required: false
- type: textarea
id: additional-context
attributes:
label: "Additional Context (optional)"
description: "Any additional information or context regarding the maintenance task that might be helpful."
placeholder: "Provide any additional information that may be relevant to the task at hand."
validations:
required: false
- type: checkboxes
id: terms
attributes:
label: "Checklist:"
description: "Please confirm the following:"
options:
- label: "I have confirmed that this maintenance task is currently not being addressed."
required: true
- label: "I understand that this task will be evaluated by the maintainers and prioritized accordingly."
required: true
- label: "I am available to provide further information if needed."
required: true

View File

@ -0,0 +1,45 @@
## Description
Please provide a clear and concise description of the changes you've made and the problem they address. Include the purpose of the change, any relevant issues it solves, and the benefits it brings to the project. If this change introduces new features or adjustments, highlight them here.
Related to # (issue)
## Changes Introduced
List the new features or adjustments introduced in this pull request. Provide details on benchmarks, documentation updates, changelog entries, and if applicable, the migration guide.
- [ ] Benchmarks: Describe any performance benchmarks and improvements related to the changes.
- [ ] Documentation Update: Detail the updates made to the documentation and links to the changed files.
- [ ] Changelog/What's New: Include a summary of the additions for the upcoming release notes.
- [ ] Migration Guide: If necessary, provide a guide or steps for users to migrate their existing code to accommodate these changes.
- [ ] API Alignment with Express: Explain how the changes align with the Express API.
- [ ] API Longevity: Discuss the steps taken to ensure that the new or updated APIs are consistent and not prone to breaking changes.
- [ ] Examples: Provide examples demonstrating the new features or changes in action.
## Type of Change
Please delete options that are not relevant.
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Enhancement (improvement to existing features and functionality)
- [ ] Documentation update (changes to documentation)
- [ ] Performance improvement (non-breaking change which improves efficiency)
- [ ] Code consistency (non-breaking change which improves code reliability and robustness)
## Checklist
Before you submit your pull request, please make sure you meet these requirements:
- [ ] Followed the inspiration of the Express.js framework for new functionalities, making them similar in usage.
- [ ] Conducted a self-review of the code and provided comments for complex or critical parts.
- [ ] Updated the documentation in the `/docs/` directory for [Fiber's documentation](https://docs.gofiber.io/).
- [ ] Added or updated unit tests to validate the effectiveness of the changes or new features.
- [ ] Ensured that new and existing unit tests pass locally with the changes.
- [ ] Verified that any new dependencies are essential and have been agreed upon by the maintainers/community.
- [ ] Aimed for optimal performance with minimal allocations in the new code.
- [ ] Provided benchmarks for the new code to analyze and improve upon.
## Commit Formatting
Please use emojis in commit messages for an easy way to identify the purpose or intention of a commit. You can refer to the emoji cheatsheet here: https://gitmoji.carloscuesta.me/

View File

@ -1,4 +1,5 @@
#!/usr/bin/env bash
set -e
# Some env variables
BRANCH="main"

View File

@ -459,12 +459,16 @@ func (a *Agent) BodyStream(bodyStream io.Reader, bodySize int) *Agent {
}
// JSON sends a JSON request.
func (a *Agent) JSON(v any) *Agent {
func (a *Agent) JSON(v any, ctype ...string) *Agent {
if a.jsonEncoder == nil {
a.jsonEncoder = json.Marshal
}
a.req.Header.SetContentType(MIMEApplicationJSON)
if len(ctype) > 0 {
a.req.Header.SetContentType(ctype[0])
} else {
a.req.Header.SetContentType(MIMEApplicationJSON)
}
if body, err := a.jsonEncoder(v); err != nil {
a.errs = append(a.errs, err)

View File

@ -697,6 +697,7 @@ func Test_Client_Agent_RetryIf(t *testing.T) {
func Test_Client_Agent_Json(t *testing.T) {
t.Parallel()
// Test without ctype parameter
handler := func(c Ctx) error {
require.Equal(t, MIMEApplicationJSON, string(c.Request().Header.ContentType()))
@ -708,6 +709,19 @@ func Test_Client_Agent_Json(t *testing.T) {
}
testAgent(t, handler, wrapAgent, `{"success":true}`)
// Test with ctype parameter
handler = func(c Ctx) error {
require.Equal(t, "application/problem+json", string(c.Request().Header.ContentType()))
return c.Send(c.Request().Body())
}
wrapAgent = func(a *Agent) {
a.JSON(data{Success: true}, "application/problem+json")
}
testAgent(t, handler, wrapAgent, `{"success":true}`)
}
func Test_Client_Agent_Json_Error(t *testing.T) {

70
ctx.go
View File

@ -79,10 +79,13 @@ func (t *TLSHandler) GetClientInfo(info *tls.ClientHelloInfo) (*tls.Certificate,
// Range data for c.Range
type Range struct {
Type string
Ranges []struct {
Start int
End int
}
Ranges []RangeSet
}
// RangeSet represents a single content range from a request.
type RangeSet struct {
Start int
End int
}
// Cookie data for c.Cookie
@ -732,14 +735,20 @@ func (c *DefaultCtx) Is(extension string) bool {
// Array and slice values encode as JSON arrays,
// except that []byte encodes as a base64-encoded string,
// and a nil slice encodes as the null JSON value.
// This method also sets the content header to application/json.
func (c *DefaultCtx) JSON(data any) error {
// If the ctype parameter is given, this method will set the
// Content-Type header equal to ctype. If ctype is not given,
// The Content-Type header will be set to application/json.
func (c *DefaultCtx) JSON(data any, ctype ...string) error {
raw, err := c.app.config.JSONEncoder(data)
if err != nil {
return err
}
c.fasthttp.Response.SetBodyRaw(raw)
c.fasthttp.Response.Header.SetContentType(MIMEApplicationJSON)
if len(ctype) > 0 {
c.fasthttp.Response.Header.SetContentType(ctype[0])
} else {
c.fasthttp.Response.Header.SetContentType(MIMEApplicationJSON)
}
return nil
}
@ -1093,25 +1102,44 @@ func (c *DefaultCtx) QueryFloat(key string, defaultValue ...float64) float64 {
// Range returns a struct containing the type and a slice of ranges.
func (c *DefaultCtx) Range(size int) (Range, error) {
var rangeData Range
var (
rangeData Range
ranges string
)
rangeStr := c.Get(HeaderRange)
if rangeStr == "" || !strings.Contains(rangeStr, "=") {
i := strings.IndexByte(rangeStr, '=')
if i == -1 || strings.Contains(rangeStr[i+1:], "=") {
return rangeData, ErrRangeMalformed
}
data := strings.Split(rangeStr, "=")
const expectedDataParts = 2
if len(data) != expectedDataParts {
return rangeData, ErrRangeMalformed
}
rangeData.Type = data[0]
arr := strings.Split(data[1], ",")
for i := 0; i < len(arr); i++ {
item := strings.Split(arr[i], "-")
if len(item) == 1 {
rangeData.Type = rangeStr[:i]
ranges = rangeStr[i+1:]
var (
singleRange string
moreRanges = ranges
)
for moreRanges != "" {
singleRange = moreRanges
if i := strings.IndexByte(moreRanges, ','); i >= 0 {
singleRange = moreRanges[:i]
moreRanges = moreRanges[i+1:]
} else {
moreRanges = ""
}
var (
startStr, endStr string
i int
)
if i = strings.IndexByte(singleRange, '-'); i == -1 {
return rangeData, ErrRangeMalformed
}
start, startErr := strconv.Atoi(item[0])
end, endErr := strconv.Atoi(item[1])
startStr = singleRange[:i]
endStr = singleRange[i+1:]
start, startErr := fasthttp.ParseUint(utils.UnsafeBytes(startStr))
end, endErr := fasthttp.ParseUint(utils.UnsafeBytes(endStr))
if startErr != nil { // -nnn
start = size - end
end = size - 1

View File

@ -164,8 +164,10 @@ type Ctx interface {
// Array and slice values encode as JSON arrays,
// except that []byte encodes as a base64-encoded string,
// and a nil slice encodes as the null JSON value.
// This method also sets the content header to application/json.
JSON(data any) error
// If the ctype parameter is given, this method will set the
// Content-Type header equal to ctype. If ctype is not given,
// The Content-Type header will be set to application/json.
JSON(data any, ctype ...string) error
// JSONP sends a JSON response with JSONP support.
// This method is identical to JSON, except that it opts-in to JSONP callback support.

View File

@ -2277,39 +2277,67 @@ func Test_Ctx_Range(t *testing.T) {
app := New()
c := app.NewCtx(&fasthttp.RequestCtx{})
var (
result Range
err error
)
_, err = c.Range(1000)
require.True(t, err != nil)
c.Request().Header.Set(HeaderRange, "bytes=500")
_, err = c.Range(1000)
require.True(t, err != nil)
c.Request().Header.Set(HeaderRange, "bytes=500=")
_, err = c.Range(1000)
require.True(t, err != nil)
c.Request().Header.Set(HeaderRange, "bytes=500-300")
_, err = c.Range(1000)
require.True(t, err != nil)
testRange := func(header string, start, end int) {
testRange := func(header string, ranges ...RangeSet) {
c.Request().Header.Set(HeaderRange, header)
result, err = c.Range(1000)
require.NoError(t, err)
require.Equal(t, "bytes", result.Type)
require.Equal(t, start, result.Ranges[0].Start)
require.Equal(t, end, result.Ranges[0].End)
result, err := c.Range(1000)
if len(ranges) == 0 {
require.Error(t, err)
} else {
require.Equal(t, "bytes", result.Type)
require.NoError(t, err)
}
require.Equal(t, len(ranges), len(result.Ranges))
for i := range ranges {
require.Equal(t, ranges[i], result.Ranges[i])
}
}
testRange("bytes=a-700", 300, 999)
testRange("bytes=500-b", 500, 999)
testRange("bytes=500-1000", 500, 999)
testRange("bytes=500-700", 500, 700)
testRange("bytes=500")
testRange("bytes=")
testRange("bytes=500=")
testRange("bytes=500-300")
testRange("bytes=a-700", RangeSet{300, 999})
testRange("bytes=500-b", RangeSet{500, 999})
testRange("bytes=500-1000", RangeSet{500, 999})
testRange("bytes=500-700", RangeSet{500, 700})
testRange("bytes=0-0,2-1000", RangeSet{0, 0}, RangeSet{2, 999})
testRange("bytes=0-99,450-549,-100", RangeSet{0, 99}, RangeSet{450, 549}, RangeSet{900, 999})
testRange("bytes=500-700,601-999", RangeSet{500, 700}, RangeSet{601, 999})
}
// go test -v -run=^$ -bench=Benchmark_Ctx_Range -benchmem -count=4
func Benchmark_Ctx_Range(b *testing.B) {
app := New()
c := app.NewCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
testCases := []struct {
str string
start int
end int
}{
{"bytes=-700", 300, 999},
{"bytes=500-", 500, 999},
{"bytes=500-1000", 500, 999},
{"bytes=0-700,800-1000", 0, 700},
}
for _, tc := range testCases {
b.Run(tc.str, func(b *testing.B) {
c.Request().Header.Set(HeaderRange, tc.str)
var (
result Range
err error
)
for n := 0; n < b.N; n++ {
result, err = c.Range(1000)
}
require.NoError(b, err)
require.Equal(b, "bytes", result.Type)
require.Equal(b, tc.start, result.Ranges[0].Start)
require.Equal(b, tc.end, result.Ranges[0].End)
})
}
}
// go test -run Test_Ctx_Route
@ -2652,6 +2680,7 @@ func Test_Ctx_JSON(t *testing.T) {
require.True(t, c.JSON(complex(1, 1)) != nil)
// Test without ctype
err := c.JSON(Map{ // map has no order
"Name": "Grame",
"Age": 20,
@ -2660,7 +2689,16 @@ func Test_Ctx_JSON(t *testing.T) {
require.Equal(t, `{"Age":20,"Name":"Grame"}`, string(c.Response().Body()))
require.Equal(t, "application/json", string(c.Response().Header.Peek("content-type")))
testEmpty := func(v any, r string) {
// Test with ctype
err = c.JSON(Map{ // map has no order
"Name": "Grame",
"Age": 20,
}, "application/problem+json")
require.NoError(t, err)
require.Equal(t, `{"Age":20,"Name":"Grame"}`, string(c.Response().Body()))
require.Equal(t, "application/problem+json", string(c.Response().Header.Peek("content-type")))
testEmpty := func(v interface{}, r string) {
err := c.JSON(v)
require.NoError(t, err)
require.Equal(t, r, string(c.Response().Body()))
@ -2714,6 +2752,30 @@ func Benchmark_Ctx_JSON(b *testing.B) {
require.Equal(b, `{"Name":"Grame","Age":20}`, string(c.Response().Body()))
}
// go test -run=^$ -bench=Benchmark_Ctx_JSON_Ctype -benchmem -count=4
func Benchmark_Ctx_JSON_Ctype(b *testing.B) {
app := New()
// TODO: Check extra allocs because of the interface stuff
c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
type SomeStruct struct {
Name string
Age uint8
}
data := SomeStruct{
Name: "Grame",
Age: 20,
}
var err error
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
err = c.JSON(data, "application/problem+json")
}
require.NoError(b, err)
require.Equal(b, `{"Name":"Grame","Age":20}`, string(c.Response().Body()))
require.Equal(b, "application/problem+json", string(c.Response().Header.Peek("content-type")))
}
// go test -run Test_Ctx_JSONP
func Test_Ctx_JSONP(t *testing.T) {
t.Parallel()

View File

@ -268,10 +268,10 @@ agent.BodyStream(strings.NewReader("body=stream"), -1)
### JSON
JSON sends a JSON request by setting the Content-Type header to `application/json`.
JSON sends a JSON request by setting the Content-Type header to the `ctype` parameter. If no `ctype` is passed in, the header is set to `application/json`.
```go title="Signature"
func (a *Agent) JSON(v interface{}) *Agent
func (a *Agent) JSON(v interface{}, ctype ...string) *Agent
```
```go title="Example"

View File

@ -862,11 +862,11 @@ app.Get("/", func(c fiber.Ctx) error {
Converts any **interface** or **string** to JSON using the [encoding/json](https://pkg.go.dev/encoding/json) package.
:::info
JSON also sets the content header to **application/json**.
JSON also sets the content header to the `ctype` parameter. If no `ctype` is passed in, the header is set to `application/json`.
:::
```go title="Signature"
func (c *Ctx) JSON(data interface{}) error
func (c *Ctx) JSON(data interface{}, ctype ...string) error
```
```go title="Example"
@ -892,6 +892,22 @@ app.Get("/json", func(c fiber.Ctx) error {
})
// => Content-Type: application/json
// => "{"name": "Grame", "age": 20}"
return c.JSON(fiber.Map{
"type": "https://example.com/probs/out-of-credit",
"title": "You do not have enough credit.",
"status": 403,
"detail": "Your current balance is 30, but that costs 50.",
"instance": "/account/12345/msgs/abc",
}, "application/problem+json")
// => Content-Type: application/problem+json
// => "{
// => "type": "https://example.com/probs/out-of-credit",
// => "title": "You do not have enough credit.",
// => "status": 403,
// => "detail": "Your current balance is 30, but that costs 50.",
// => "instance": "/account/12345/msgs/abc",
// => }"
})
```

View File

@ -4,9 +4,11 @@ id: csrf
# CSRF
The CSRF middleware for [Fiber](https://github.com/gofiber/fiber) provides protection against [Cross-Site Request Forgery](https://en.wikipedia.org/wiki/Cross-site_request_forgery) (CSRF) attacks using tokens. These tokens verify requests made using methods other than those defined as "safe" by [RFC9110#section-9.2.1](https://datatracker.ietf.org/doc/html/rfc9110.html#section-9.2.1) (Safe-Methods: GET, HEAD, OPTIONS, and TRACE). If a potential attack is detected this middleware will, by default, return a 403 Forbidden error.
The CSRF middleware for [Fiber](https://github.com/gofiber/fiber) provides protection against [Cross-Site Request Forgery](https://en.wikipedia.org/wiki/Cross-site_request_forgery) (CSRF) attacks. Requests made using methods other than those defined as 'safe' by [RFC9110#section-9.2.1](https://datatracker.ietf.org/doc/html/rfc9110.html#section-9.2.1) (GET, HEAD, OPTIONS, and TRACE) are validated using tokens. If a potential attack is detected, the middleware will return a default 403 Forbidden error.
This middleware can be used with or without a user session and offers two token validation patterns. In addition, it implements strict referer checking for HTTPS requests, ensuring the security of your application. For HTTPS requests, even if a subdomain can set or modify cookies on your domain, it can't force a user to post to your application since that request won't come from your own exact domain.
This middleware offers two [Token Validation Patterns](#token-validation-patterns): the [Double Submit Cookie Pattern (default)](#double-submit-cookie-pattern-default), and the [Synchronizer Token Pattern (with Session)](#synchronizer-token-pattern-session).
As a [Defense In Depth](#defense-in-depth) measure, this middleware performs [Referer Checking](#referer-checking) for HTTPS requests.
## Token Generation
@ -24,26 +26,26 @@ Never use 'safe' methods to mutate data, for example, never use a GET request to
#### Double Submit Cookie Pattern (Default)
In the default configuration, the middleware generates and stores tokens using the `fiber.Storage` interface. These tokens are not associated with a user session, and a Double Submit Cookie pattern is used to validate the token. The token is stored in a cookie and sent as a header on requests. The middleware compares the cookie value with the header value to validate the token. This is a secure pattern that does not require a user session.
By default, the middleware generates and stores tokens using the `fiber.Storage` interface. These tokens are not linked to any particular user session, and they are validated using the Double Submit Cookie pattern. The token is stored in a cookie, and then sent as a header on requests. The middleware compares the cookie value with the header value to validate the token. This is a secure pattern that does not require a user session.
When using this pattern, it's important to delete the token when the authorization status changes, see: [Token Lifecycle](#token-lifecycle) for more information.
When the authorization status changes, the previously issued token MUST be deleted, and a new one generated. See [Token Lifecycle](#token-lifecycle) [Deleting Tokens](#deleting-tokens) for more information.
:::caution
When using this method, it's important to set the `CookieSameSite` option to `Lax` or `Strict` and ensure that the Extractor is not `CsrfFromCookie`, and KeyLookup is not `cookie:<name>`.
When using this pattern, it's important to set the `CookieSameSite` option to `Lax` or `Strict` and ensure that the Extractor is not `CsrfFromCookie`, and KeyLookup is not `cookie:<name>`.
:::
:::note
When using this pattern, this middleware uses our [Storage](https://github.com/gofiber/storage) package to support various databases through a single interface. The default configuration for Storage saves data to memory. See [Custom Storage/Database](#custom-storagedatabase) for customizing the storage.
:::
#### Synchronizer Token Pattern (Session)
#### Synchronizer Token Pattern (with Session)
When using this middleware with a user session, the middleware can be configured to store the token in the session. This method is recommended when using a user session, as it is generally more secure than the Double Submit Cookie Pattern.
When using this middleware with a user session, the middleware can be configured to store the token within the session. This method is recommended when using a user session, as it is generally more secure than the Double Submit Cookie Pattern.
When using this pattern it's important to regenerate the session when the authorization status changes, this will also delete the token. See: [Token Lifecycle](#token-lifecycle) for more information.
:::caution
When using this method, pre-sessions are required and will be created if a session is not already present. This means the middleware will create a session for every safe request, even if the request does not require a session. Therefore, the existence of a session should not be used to indicate that a user is logged in or authenticated; a session value should be used for this purpose.
Pre-sessions are required and will be created automatically if not present. Use a session value to indicate authentication instead of relying on presence of a session.
:::
### Defense In Depth
@ -51,7 +53,7 @@ When using this method, pre-sessions are required and will be created if a sessi
When using this middleware, it's recommended to serve your pages over HTTPS, set the `CookieSecure` option to `true`, and set the `CookieSameSite` option to `Lax` or `Strict`. This ensures that the cookie is only sent over HTTPS and not on requests from external sites.
:::note
Cookie prefixes __Host- and __Secure- can be used to further secure the cookie. However, these prefixes are not supported by all browsers and there are some other limitations. See [MDN#Set-Cookie#cookie_prefixes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie_prefixes) for more information.
Cookie prefixes `__Host-` and `__Secure-` can be used to further secure the cookie. Note that these prefixes are not supported by all browsers and there are other limitations. See [MDN#Set-Cookie#cookie_prefixes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie_prefixes) for more information.
To use these prefixes, set the `CookieName` option to `__Host-csrf_` or `__Secure-csrf_`.
:::
@ -61,7 +63,9 @@ To use these prefixes, set the `CookieName` option to `__Host-csrf_` or `__Secur
For HTTPS requests, this middleware performs strict referer checking. Even if a subdomain can set or modify cookies on your domain, it can't force a user to post to your application since that request won't come from your own exact domain.
:::caution
Referer checking is required for https requests protected by CSRF. All modern browsers will automatically include the Referer header in requests, including those made with the JS Fetch API. However, if you are using this middleware with a custom client you must ensure that the client sends a valid Referer header.
When HTTPS requests are protected by CSRF, referer checking is always carried out.
The Referer header is automatically included in requests by all modern browsers, including those made using the JS Fetch API. However, if you're making use of this middleware with a custom client, it's important to ensure that the client sends a valid Referer header.
:::
@ -74,7 +78,7 @@ Tokens are valid until they expire or until they are deleted. By default, tokens
By default, tokens may be used multiple times. If you want to delete the token after it has been used, you can set the `SingleUseToken` option to `true`. This will delete the token after it has been used, and a new token will be generated on the next request.
:::info
Using `SingleUseToken` comes with usability trade-offs and is not enabled by default. It can interfere with the user experience if the user has multiple tabs open or uses the back button.
Using `SingleUseToken` comes with usability trade-offs and is not enabled by default. For example, it can interfere with the user experience if the user has multiple tabs open or uses the back button.
:::
#### Deleting Tokens
@ -209,13 +213,14 @@ var ConfigDefault = Config{
### Recommended Config (with session)
It's recommended to use this middleware with [fiber/middleware/session](https://docs.gofiber.io/api/middleware/session) to store the CSRF token in the session. This is generally more secure than the default configuration.
It's recommended to use this middleware with [fiber/middleware/session](https://docs.gofiber.io/api/middleware/session) to store the CSRF token within the session. This is generally more secure than the default configuration.
```go
var ConfigDefault = Config{
KeyLookup: "header:" + HeaderName,
CookieName: "csrf_",
CookieName: "__Host-csrf_",
CookieSameSite: "Lax",
CookieSecure: true,
CookieSessionOnly: true,
CookieHTTPOnly: true,
Expiration: 1 * time.Hour,
@ -246,7 +251,7 @@ The CSRF middleware utilizes a set of sentinel errors to handle various scenario
- `ErrNoReferer`: Indicates that the referer was not supplied.
- `ErrBadReferer`: Indicates that the referer is invalid.
If you are using the default error handler, it will return a 403 Forbidden error for any of these errors without providing any additional information to the client.
If you use the default error handler, the client will receive a 403 Forbidden error without any additional information.
## Custom Error Handler

12
go.mod
View File

@ -4,22 +4,22 @@ go 1.20
require (
github.com/gofiber/utils/v2 v2.0.0-beta.3
github.com/google/uuid v1.4.0
github.com/google/uuid v1.5.0
github.com/mattn/go-colorable v0.1.13
github.com/mattn/go-isatty v0.0.19
github.com/mattn/go-isatty v0.0.20
github.com/stretchr/testify v1.8.4
github.com/tinylib/msgp v1.1.8
github.com/valyala/bytebufferpool v1.0.0
github.com/valyala/fasthttp v1.50.0
github.com/valyala/fasthttp v1.51.0
)
require (
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/klauspost/compress v1.17.2 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/philhofer/fwd v1.1.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/sys v0.16.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

12
go.sum
View File

@ -1,18 +1,26 @@
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gofiber/utils/v2 v2.0.0-beta.3 h1:pfOhUDDVjBJpkWv6C5jaDyYLvpui7zQ97zpyFFsUOKw=
github.com/gofiber/utils/v2 v2.0.0-beta.3/go.mod h1:jsl17+MsKfwJjM3ONCE9Rzji/j8XNbwjhUVTjzgfDCo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -25,6 +33,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@ -49,6 +59,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=

View File

@ -64,41 +64,89 @@ func Test_Utils_GetOffer(t *testing.T) {
// go test -v -run=^$ -bench=Benchmark_Utils_GetOffer -benchmem -count=4
func Benchmark_Utils_GetOffer(b *testing.B) {
headers := []string{
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"application/json",
"utf-8, iso-8859-1;q=0.5",
"gzip, deflate",
testCases := []struct {
description string
accept string
offers []string
}{
{
description: "simple",
accept: "application/json",
offers: []string{"application/json"},
},
{
description: "6 offers",
accept: "text/plain",
offers: []string{"junk/a", "junk/b", "junk/c", "junk/d", "junk/e", "text/plain"},
},
{
description: "1 parameter",
accept: "application/json; version=1",
offers: []string{"application/json;version=1"},
},
{
description: "2 parameters",
accept: "application/json; version=1; foo=bar",
offers: []string{"application/json;version=1;foo=bar"},
},
{
// 1 alloc:
// The implementation uses a slice of length 2 allocated on the stack,
// so a third parameters causes a heap allocation.
description: "3 parameters",
accept: "application/json; version=1; foo=bar; charset=utf-8",
offers: []string{"application/json;version=1;foo=bar;charset=utf-8"},
},
{
description: "10 parameters",
accept: "text/plain;a=1;b=2;c=3;d=4;e=5;f=6;g=7;h=8;i=9;j=10",
offers: []string{"text/plain;a=1;b=2;c=3;d=4;e=5;f=6;g=7;h=8;i=9;j=10"},
},
{
description: "6 offers w/params",
accept: "text/plain; format=flowed",
offers: []string{
"junk/a;a=b",
"junk/b;b=c",
"junk/c;c=d",
"text/plain; format=justified",
"text/plain; format=flat",
"text/plain; format=flowed",
},
},
{
description: "mime extension",
accept: "utf-8, iso-8859-1;q=0.5",
offers: []string{"utf-8"},
},
{
description: "mime extension",
accept: "utf-8, iso-8859-1;q=0.5",
offers: []string{"iso-8859-1"},
},
{
description: "mime extension",
accept: "utf-8, iso-8859-1;q=0.5",
offers: []string{"iso-8859-1", "utf-8"},
},
{
description: "mime extension",
accept: "gzip, deflate",
offers: []string{"deflate"},
},
{
description: "web browser",
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
offers: []string{"text/html", "application/xml", "application/xml+xhtml"},
},
}
offers := [][]string{
{"text/html", "application/xml", "application/xml+xhtml"},
{"application/json"},
{"utf-8"},
{"deflate"},
}
for n := 0; n < b.N; n++ {
for i, header := range headers {
getOffer(header, acceptsOfferType, offers[i]...)
}
}
}
// go test -v -run=^$ -bench=Benchmark_Utils_GetOffer_WithParams -benchmem -count=4
func Benchmark_Utils_GetOffer_WithParams(b *testing.B) {
headers := []string{
"text/html;p=1,application/xhtml+xml;p=1;b=2,application/xml;a=2;q=0.9,*/*;q=0.8",
"application/json; version=1",
"utf-8, iso-8859-1;q=0.5",
}
offers := [][]string{
{"text/html;p=1", "application/xml;a=2", "application/xml+xhtml; p=1; b=2"},
{"application/json; version=2"},
{`utf-8;charset="utf-16"`},
}
for n := 0; n < b.N; n++ {
for i, header := range headers {
getOffer(header, acceptsOfferType, offers[i]...)
}
for _, tc := range testCases {
b.Run(tc.description, func(b *testing.B) {
for n := 0; n < b.N; n++ {
getOffer(tc.accept, acceptsOfferType, tc.offers...)
}
})
}
}

View File

@ -29,7 +29,7 @@ type Config struct {
//
// Ignored if an Extractor is explicitly set.
//
// Optional. Default: "header:X-CSRF-Token"
// Optional. Default: "header:X-Csrf-Token"
KeyLookup string
// Name of the session cookie. This cookie will store session key.

View File

@ -186,7 +186,7 @@ func Test_CSRF_ExpiredToken(t *testing.T) {
require.Equal(t, 200, ctx.Response.StatusCode())
// Wait for the token to expire
time.Sleep(1 * time.Second)
time.Sleep(1250 * time.Millisecond)
// Expired CSRF token
ctx.Request.Reset()
@ -280,7 +280,7 @@ func Test_CSRF_MultiUseToken(t *testing.T) {
app := fiber.New()
app.Use(New(Config{
KeyLookup: "header:X-CSRF-Token",
KeyLookup: "header:X-Csrf-Token",
}))
app.Post("/", func(c fiber.Ctx) error {
@ -292,7 +292,7 @@ func Test_CSRF_MultiUseToken(t *testing.T) {
// Invalid CSRF token
ctx.Request.Header.SetMethod(fiber.MethodPost)
ctx.Request.Header.Set("X-CSRF-Token", "johndoe")
ctx.Request.Header.Set("X-Csrf-Token", "johndoe")
h(ctx)
require.Equal(t, 403, ctx.Response.StatusCode())
@ -307,7 +307,7 @@ func Test_CSRF_MultiUseToken(t *testing.T) {
ctx.Request.Reset()
ctx.Response.Reset()
ctx.Request.Header.SetMethod(fiber.MethodPost)
ctx.Request.Header.Set("X-CSRF-Token", token)
ctx.Request.Header.Set("X-Csrf-Token", token)
ctx.Request.Header.SetCookie(ConfigDefault.CookieName, token)
h(ctx)
newToken := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie))

View File

@ -11,7 +11,6 @@ import (
"github.com/valyala/fasthttp"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/utils"
)
// go test -run Test_Middleware_Favicon
@ -93,15 +92,15 @@ func Test_Custom_Favicon_URL(t *testing.T) {
resp, err := app.Test(httptest.NewRequest(http.MethodGet, customURL, nil))
utils.AssertEqual(t, nil, err, "app.Test(req)")
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode, "Status code")
utils.AssertEqual(t, "image/x-icon", resp.Header.Get(fiber.HeaderContentType))
require.Equal(t, nil, err, "app.Test(req)")
require.Equal(t, fiber.StatusOK, resp.StatusCode, "Status code")
require.Equal(t, "image/x-icon", resp.Header.Get(fiber.HeaderContentType))
}
// go test -run Test_Custom_Favicon_Data
func Test_Custom_Favicon_Data(t *testing.T) {
data, err := os.ReadFile("../../.github/testdata/favicon.ico")
utils.AssertEqual(t, nil, err)
require.Equal(t, nil, err)
app := fiber.New()
@ -114,10 +113,10 @@ func Test_Custom_Favicon_Data(t *testing.T) {
})
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/favicon.ico", nil))
utils.AssertEqual(t, nil, err, "app.Test(req)")
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode, "Status code")
utils.AssertEqual(t, "image/x-icon", resp.Header.Get(fiber.HeaderContentType))
utils.AssertEqual(t, "public, max-age=31536000", resp.Header.Get(fiber.HeaderCacheControl), "CacheControl Control")
require.NoError(t, err, "app.Test(req)")
require.Equal(t, fiber.StatusOK, resp.StatusCode, "Status code")
require.Equal(t, "image/x-icon", resp.Header.Get(fiber.HeaderContentType))
require.Equal(t, "public, max-age=31536000", resp.Header.Get(fiber.HeaderCacheControl), "CacheControl Control")
}
// go test -run Test_Middleware_Favicon_FileSystem

View File

@ -217,7 +217,7 @@ func Test_Limiter_Sliding_Window_No_Skip_Choices(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 429, resp.StatusCode)
time.Sleep(4 * time.Second)
time.Sleep(4*time.Second + 500*time.Millisecond)
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/success", nil))
require.NoError(t, err)
@ -257,7 +257,7 @@ func Test_Limiter_Sliding_Window_Custom_Storage_No_Skip_Choices(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 429, resp.StatusCode)
time.Sleep(4 * time.Second)
time.Sleep(4*time.Second + 500*time.Millisecond)
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/success", nil))
require.NoError(t, err)
@ -372,7 +372,7 @@ func Test_Limiter_Sliding_Window_Skip_Failed_Requests(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 429, resp.StatusCode)
time.Sleep(4 * time.Second)
time.Sleep(4*time.Second + 500*time.Millisecond)
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/success", nil))
require.NoError(t, err)
@ -411,7 +411,7 @@ func Test_Limiter_Sliding_Window_Custom_Storage_Skip_Failed_Requests(t *testing.
require.NoError(t, err)
require.Equal(t, 429, resp.StatusCode)
time.Sleep(4 * time.Second)
time.Sleep(4*time.Second + 500*time.Millisecond)
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/success", nil))
require.NoError(t, err)
@ -532,7 +532,7 @@ func Test_Limiter_Sliding_Window_Skip_Successful_Requests(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 429, resp.StatusCode)
time.Sleep(4 * time.Second)
time.Sleep(4*time.Second + 500*time.Millisecond)
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/fail", nil))
require.NoError(t, err)
@ -573,7 +573,7 @@ func Test_Limiter_Sliding_Window_Custom_Storage_Skip_Successful_Requests(t *test
require.NoError(t, err)
require.Equal(t, 429, resp.StatusCode)
time.Sleep(4 * time.Second)
time.Sleep(4*time.Second + 500*time.Millisecond)
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/fail", nil))
require.NoError(t, err)

View File

@ -17,7 +17,6 @@ import (
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/requestid"
"github.com/gofiber/fiber/v3/utils"
"github.com/stretchr/testify/require"
"github.com/valyala/bytebufferpool"
"github.com/valyala/fasthttp"
@ -303,14 +302,14 @@ func Test_Logger_WithLatency_DefaultFormat(t *testing.T) {
// Create a new HTTP request to the test route
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil), int(2*time.Second))
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
require.NoError(t, err)
require.Equal(t, fiber.StatusOK, resp.StatusCode)
// Assert that the log output contains the expected latency value in the current time unit
// parse out the latency value from the log output
latency := bytes.Split(buff.Bytes(), []byte(" | "))[2]
// Assert that the latency value is in the current time unit
utils.AssertEqual(t, bytes.HasSuffix(latency, []byte(tu.unit)), true, fmt.Sprintf("Expected latency to be in %s, got %s", tu.unit, latency))
require.Equal(t, bytes.HasSuffix(latency, []byte(tu.unit)), true, fmt.Sprintf("Expected latency to be in %s, got %s", tu.unit, latency))
// Reset the buffer
buff.Reset()

View File

@ -28,6 +28,9 @@ func New(config ...Config) fiber.Handler {
pprofThreadcreate = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Handler("threadcreate").ServeHTTP)
)
// Construct actual prefix
prefix := cfg.Prefix + "/debug/pprof"
// Return new handler
return func(c fiber.Ctx) error {
// Don't execute middleware if Next returns true
@ -37,39 +40,40 @@ func New(config ...Config) fiber.Handler {
path := c.Path()
// We are only interested in /debug/pprof routes
if len(path) < 12 || !strings.HasPrefix(path, cfg.Prefix+"/debug/pprof") {
path, found := cutPrefix(path, prefix)
if !found {
return c.Next()
}
// Switch to original path without stripped slashes
// Switch on trimmed path against constant strings
switch path {
case cfg.Prefix + "/debug/pprof/":
case "/":
pprofIndex(c.Context())
case cfg.Prefix + "/debug/pprof/cmdline":
case "/cmdline":
pprofCmdline(c.Context())
case cfg.Prefix + "/debug/pprof/profile":
case "/profile":
pprofProfile(c.Context())
case cfg.Prefix + "/debug/pprof/symbol":
case "/symbol":
pprofSymbol(c.Context())
case cfg.Prefix + "/debug/pprof/trace":
case "/trace":
pprofTrace(c.Context())
case cfg.Prefix + "/debug/pprof/allocs":
case "/allocs":
pprofAllocs(c.Context())
case cfg.Prefix + "/debug/pprof/block":
case "/block":
pprofBlock(c.Context())
case cfg.Prefix + "/debug/pprof/goroutine":
case "/goroutine":
pprofGoroutine(c.Context())
case cfg.Prefix + "/debug/pprof/heap":
case "/heap":
pprofHeap(c.Context())
case cfg.Prefix + "/debug/pprof/mutex":
case "/mutex":
pprofMutex(c.Context())
case cfg.Prefix + "/debug/pprof/threadcreate":
case "/threadcreate":
pprofThreadcreate(c.Context())
default:
// pprof index only works with trailing slash
if strings.HasSuffix(path, "/") {
path = strings.TrimRight(path, "/")
} else {
path = cfg.Prefix + "/debug/pprof/"
path = prefix + "/"
}
return c.Redirect().To(path)
@ -77,3 +81,14 @@ func New(config ...Config) fiber.Handler {
return nil
}
}
// cutPrefix is a copy of [strings.CutPrefix] added in Go 1.20.
// Remove this function when we drop support for Go 1.19.
//
//nolint:nonamedreturns // Align with its original form in std.
func cutPrefix(s, prefix string) (after string, found bool) {
if !strings.HasPrefix(s, prefix) {
return s, false
}
return s[len(prefix):], true
}

View File

@ -514,10 +514,6 @@ func Test_Session_Reset(t *testing.T) {
// Check that the session id is not in the header or cookie anymore
require.Equal(t, "", string(ctx.Response().Header.Peek(store.sessionName)))
require.Equal(t, "", string(ctx.Request().Header.Peek(store.sessionName)))
// But the new session id should be in the header or cookie
require.Equal(t, acquiredSession.ID(), string(ctx.Response().Header.Peek(store.sessionName)))
require.Equal(t, acquiredSession.ID(), string(ctx.Request().Header.Peek(store.sessionName)))
})
}

View File

@ -1,68 +0,0 @@
// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
// 🤖 Github Repository: https://github.com/gofiber/fiber
// 📌 API Documentation: https://docs.gofiber.io
package utils
import (
"bytes"
"fmt"
"log"
"path/filepath"
"reflect"
"runtime"
"testing"
"text/tabwriter"
)
// AssertEqual checks if values are equal
func AssertEqual(tb testing.TB, expected, actual interface{}, description ...string) { //nolint:thelper // TODO: Verify if tb can be nil
if tb != nil {
tb.Helper()
}
if reflect.DeepEqual(expected, actual) {
return
}
aType := "<nil>"
bType := "<nil>"
if expected != nil {
aType = reflect.TypeOf(expected).String()
}
if actual != nil {
bType = reflect.TypeOf(actual).String()
}
testName := "AssertEqual"
if tb != nil {
testName = tb.Name()
}
_, file, line, _ := runtime.Caller(1)
var buf bytes.Buffer
const pad = 5
w := tabwriter.NewWriter(&buf, 0, 0, pad, ' ', 0)
_, _ = fmt.Fprintf(w, "\nTest:\t%s", testName)
_, _ = fmt.Fprintf(w, "\nTrace:\t%s:%d", filepath.Base(file), line)
if len(description) > 0 {
_, _ = fmt.Fprintf(w, "\nDescription:\t%s", description[0])
}
_, _ = fmt.Fprintf(w, "\nExpect:\t%v\t(%s)", expected, aType)
_, _ = fmt.Fprintf(w, "\nResult:\t%v\t(%s)", actual, bType)
var result string
if err := w.Flush(); err != nil {
result = err.Error()
} else {
result = buf.String()
}
if tb != nil {
tb.Fatal(result)
} else {
log.Fatal(result) //nolint:revive // tb might be nil, so we need a fallback
}
}

View File

@ -1,12 +0,0 @@
//go:build go1.20
package utils
import (
"unsafe"
)
// UnsafeString returns a string pointer without allocation
func UnsafeString(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
}

View File

@ -1,14 +0,0 @@
//go:build !go1.20
package utils
import (
"unsafe"
)
// UnsafeString returns a string pointer without allocation
//
//nolint:gosec // unsafe is used for better performance here
func UnsafeString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}

View File

@ -1,12 +0,0 @@
//go:build go1.20
package utils
import (
"unsafe"
)
// UnsafeBytes returns a byte pointer without allocation.
func UnsafeBytes(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
}

View File

@ -1,24 +0,0 @@
//go:build !go1.20
package utils
import (
"reflect"
"unsafe"
)
const MaxStringLen = 0x7fff0000 // Maximum string length for UnsafeBytes. (decimal: 2147418112)
// UnsafeBytes returns a byte pointer without allocation.
// String length shouldn't be more than 2147418112.
//
//nolint:gosec // unsafe is used for better performance here
func UnsafeBytes(s string) []byte {
if s == "" {
return nil
}
return (*[MaxStringLen]byte)(unsafe.Pointer(
(*reflect.StringHeader)(unsafe.Pointer(&s)).Data),
)[:len(s):len(s)]
}