diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 6f8288d58a..864787cac0 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -30,7 +30,7 @@ func main() { ``` -$ curl http://localhost:8201/hello/world +$ curl http://localhost:9000/hello/world Hello world ``` @@ -38,7 +38,7 @@ Hello world ``` -$ curl -i http://localhost:8201/hello/world +$ curl -i http://localhost:9000/hello/world ``` diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e27022d109..b717a003fa 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index fac97d4787..645616bcb9 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -16,22 +16,22 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: - go-version: '^1.16' + go-version: '^1.18' - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v3.4.0 + uses: golangci/golangci-lint-action@v3.7.0 with: - version: v1.48.0 + version: v1.52.2 args: --verbose test: needs: lint strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ['1.16', '1.17', '1.18', '1.19', '1.20'] + go: ['1.18', '1.19', '1.20'] test-tags: ['', '-tags nomsgpack', '-tags "sonic avx"', '-tags go_json'] include: - os: ubuntu-latest @@ -46,12 +46,12 @@ jobs: GOPROXY: https://proxy.golang.org steps: - name: Set up Go ${{ matrix.go }} - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go }} - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.ref }} @@ -73,19 +73,5 @@ jobs: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} - name: Format - if: matrix.go-version == '1.19.x' + if: matrix.go-version == '1.20.x' run: diff -u <(echo -n) <(gofmt -d .) - notification-gitter: - needs: test - runs-on: ubuntu-latest - steps: - - name: Notification failure message - if: failure() - run: | - PR_OR_COMPARE="$(if [ "${{ github.event.pull_request }}" != "" ]; then echo "${{ github.event.pull_request.html_url }}"; else echo "${{ github.event.compare }}"; fi)" - curl -d message="GitHub Actions [$GITHUB_REPOSITORY]($PR_OR_COMPARE) ($GITHUB_REF) [normal]($GITHUB_API_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) ($GITHUB_RUN_NUMBER)" -d level=error https://webhooks.gitter.im/e/7f95bf605c4d356372f4 - - name: Notification success message - if: success() - run: | - PR_OR_COMPARE="$(if [ "${{ github.event.pull_request }}" != "" ]; then echo "${{ github.event.pull_request.html_url }}"; else echo "${{ github.event.compare }}"; fi)" - curl -d message="GitHub Actions [$GITHUB_REPOSITORY]($PR_OR_COMPARE) ($GITHUB_REF) [normal]($GITHUB_API_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) ($GITHUB_RUN_NUMBER)" https://webhooks.gitter.im/e/7f95bf605c4d356372f4 diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 3af3a455e1..406092665e 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -14,14 +14,14 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: - go-version: 1.17 + go-version: 1.20 - name: Run GoReleaser uses: goreleaser/goreleaser-action@v4 @@ -29,6 +29,6 @@ jobs: # either 'goreleaser' (default) or 'goreleaser-pro' distribution: goreleaser version: latest - args: release --rm-dist + args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.golangci.yml b/.golangci.yml index c5e1de388a..91dae02c39 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -19,6 +19,22 @@ linters: - nolintlint - revive - wastedassign + +linters-settings: + gosec: + # To select a subset of rules to run. + # Available rules: https://github.com/securego/gosec#available-rules + # Default: [] - means include all rules + includes: + - G102 + - G106 + - G108 + - G109 + - G111 + - G112 + - G201 + - G203 + issues: exclude-rules: - linters: @@ -37,3 +53,6 @@ issues: - path: _test\.go linters: - gosec # security is not make sense in tests + - linters: + - revive + path: _test\.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ab5617924..7968520553 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,70 @@ # Gin ChangeLog +## Gin v1.9.1 + +### BUG FIXES + +* fix Request.Context() checks [#3512](https://github.com/gin-gonic/gin/pull/3512) + +### SECURITY + +* fix lack of escaping of filename in Content-Disposition [#3556](https://github.com/gin-gonic/gin/pull/3556) + +### ENHANCEMENTS + +* refactor: use bytes.ReplaceAll directly [#3455](https://github.com/gin-gonic/gin/pull/3455) +* convert strings and slices using the officially recommended way [#3344](https://github.com/gin-gonic/gin/pull/3344) +* improve render code coverage [#3525](https://github.com/gin-gonic/gin/pull/3525) + +### DOCS + +* docs: changed documentation link for trusted proxies [#3575](https://github.com/gin-gonic/gin/pull/3575) +* chore: improve linting, testing, and GitHub Actions setup [#3583](https://github.com/gin-gonic/gin/pull/3583) + +## Gin v1.9.0 + +### BREAK CHANGES + +* Stop useless panicking in context and render [#2150](https://github.com/gin-gonic/gin/pull/2150) + +### BUG FIXES + +* fix(router): tree bug where loop index is not decremented. [#3460](https://github.com/gin-gonic/gin/pull/3460) +* fix(context): panic on NegotiateFormat - index out of range [#3397](https://github.com/gin-gonic/gin/pull/3397) +* Add escape logic for header [#3500](https://github.com/gin-gonic/gin/pull/3500) and [#3503](https://github.com/gin-gonic/gin/pull/3503) + +### SECURITY + +* Fix the GO-2022-0969 and GO-2022-0288 vulnerabilities [#3333](https://github.com/gin-gonic/gin/pull/3333) +* fix(security): vulnerability GO-2023-1571 [#3505](https://github.com/gin-gonic/gin/pull/3505) + +### ENHANCEMENTS + +* feat: add sonic json support [#3184](https://github.com/gin-gonic/gin/pull/3184) +* chore(file): Creates a directory named path [#3316](https://github.com/gin-gonic/gin/pull/3316) +* fix: modify interface check way [#3327](https://github.com/gin-gonic/gin/pull/3327) +* remove deprecated of package io/ioutil [#3395](https://github.com/gin-gonic/gin/pull/3395) +* refactor: avoid calling strings.ToLower twice [#3343](https://github.com/gin-gonic/gin/pull/3433) +* console logger HTTP status code bug fixed [#3453](https://github.com/gin-gonic/gin/pull/3453) +* chore(yaml): upgrade dependency to v3 version [#3456](https://github.com/gin-gonic/gin/pull/3456) +* chore(router): match method added to routergroup for multiple HTTP methods supporting [#3464](https://github.com/gin-gonic/gin/pull/3464) +* chore(http): add support for go1.20 http.rwUnwrapper to gin.responseWriter [#3489](https://github.com/gin-gonic/gin/pull/3489) + +### DOCS + +* docs: update markdown format [#3260](https://github.com/gin-gonic/gin/pull/3260) +* docs(readme): Add the TOML rendering example [#3400](https://github.com/gin-gonic/gin/pull/3400) +* docs(readme): move more example to docs/doc.md [#3449](https://github.com/gin-gonic/gin/pull/3449) +* docs: update markdown format [#3446](https://github.com/gin-gonic/gin/pull/3446) + ## Gin v1.8.2 -### Bugs +### BUG FIXES * fix(route): redirectSlash bug ([#3227]((https://github.com/gin-gonic/gin/pull/3227))) * fix(engine): missing route params for CreateTestContext ([#2778]((https://github.com/gin-gonic/gin/pull/2778))) ([#2803]((https://github.com/gin-gonic/gin/pull/2803))) -### Security +### SECURITY * Fix the GO-2022-1144 vulnerability ([#3432]((https://github.com/gin-gonic/gin/pull/3432))) @@ -19,12 +76,12 @@ ## Gin v1.8.0 -## Break Changes +### BREAK CHANGES * TrustedProxies: Add default IPv6 support and refactor [#2967](https://github.com/gin-gonic/gin/pull/2967). Please replace `RemoteIP() (net.IP, bool)` with `RemoteIP() net.IP` * gin.Context with fallback value from gin.Context.Request.Context() [#2751](https://github.com/gin-gonic/gin/pull/2751) -### BUGFIXES +### BUG FIXES * Fixed SetOutput() panics on go 1.17 [#2861](https://github.com/gin-gonic/gin/pull/2861) * Fix: wrong when wildcard follows named param [#2983](https://github.com/gin-gonic/gin/pull/2983) @@ -61,7 +118,7 @@ ## Gin v1.7.7 -### BUGFIXES +### BUG FIXES * Fixed X-Forwarded-For unsafe handling of CVE-2020-28483 [#2844](https://github.com/gin-gonic/gin/pull/2844), closed issue [#2862](https://github.com/gin-gonic/gin/issues/2862). * Tree: updated the code logic for `latestNode` [#2897](https://github.com/gin-gonic/gin/pull/2897), closed issue [#2894](https://github.com/gin-gonic/gin/issues/2894) [#2878](https://github.com/gin-gonic/gin/issues/2878). @@ -79,37 +136,37 @@ ## Gin v1.7.6 -### BUGFIXES +### BUG FIXES * bump new release to fix v1.7.5 release error by using v1.7.4 codes. ## Gin v1.7.4 -### BUGFIXES +### BUG FIXES * bump new release to fix checksum mismatch ## Gin v1.7.3 -### BUGFIXES +### BUG FIXES * fix level 1 router match [#2767](https://github.com/gin-gonic/gin/issues/2767), [#2796](https://github.com/gin-gonic/gin/issues/2796) ## Gin v1.7.2 -### BUGFIXES +### BUG FIXES * Fix conflict between param and exact path [#2706](https://github.com/gin-gonic/gin/issues/2706). Close issue [#2682](https://github.com/gin-gonic/gin/issues/2682) [#2696](https://github.com/gin-gonic/gin/issues/2696). ## Gin v1.7.1 -### BUGFIXES +### BUG FIXES * fix: data race with trustedCIDRs from [#2674](https://github.com/gin-gonic/gin/issues/2674)([#2675](https://github.com/gin-gonic/gin/pull/2675)) ## Gin v1.7.0 -### BUGFIXES +### BUG FIXES * fix compile error from [#2572](https://github.com/gin-gonic/gin/pull/2572) ([#2600](https://github.com/gin-gonic/gin/pull/2600)) * fix: print headers without Authorization header on broken pipe ([#2528](https://github.com/gin-gonic/gin/pull/2528)) @@ -148,33 +205,44 @@ ## Gin v1.6.2 -### BUGFIXES +### BUG FIXES + * fix missing initial sync.RWMutex [#2305](https://github.com/gin-gonic/gin/pull/2305) + ### ENHANCEMENTS + * Add set samesite in cookie. [#2306](https://github.com/gin-gonic/gin/pull/2306) ## Gin v1.6.1 -### BUGFIXES +### BUG FIXES + * Revert "fix accept incoming network connections" [#2294](https://github.com/gin-gonic/gin/pull/2294) ## Gin v1.6.0 ### BREAKING + * chore(performance): Improve performance for adding RemoveExtraSlash flag [#2159](https://github.com/gin-gonic/gin/pull/2159) * drop support govendor [#2148](https://github.com/gin-gonic/gin/pull/2148) * Added support for SameSite cookie flag [#1615](https://github.com/gin-gonic/gin/pull/1615) + ### FEATURES + * add yaml negotiation [#2220](https://github.com/gin-gonic/gin/pull/2220) * FileFromFS [#2112](https://github.com/gin-gonic/gin/pull/2112) -### BUGFIXES + +### BUG FIXES + * Unix Socket Handling [#2280](https://github.com/gin-gonic/gin/pull/2280) * Use json marshall in context json to fix breaking new line issue. Fixes #2209 [#2228](https://github.com/gin-gonic/gin/pull/2228) * fix accept incoming network connections [#2216](https://github.com/gin-gonic/gin/pull/2216) * Fixed a bug in the calculation of the maximum number of parameters [#2166](https://github.com/gin-gonic/gin/pull/2166) * [FIX] allow empty headers on DataFromReader [#2121](https://github.com/gin-gonic/gin/pull/2121) * Add mutex for protect Context.Keys map [#1391](https://github.com/gin-gonic/gin/pull/1391) + ### ENHANCEMENTS + * Add mitigation for log injection [#2277](https://github.com/gin-gonic/gin/pull/2277) * tree: range over nodes values [#2229](https://github.com/gin-gonic/gin/pull/2229) * tree: remove duplicate assignment [#2222](https://github.com/gin-gonic/gin/pull/2222) @@ -189,7 +257,9 @@ * upgrade go-validator to v10 [#2149](https://github.com/gin-gonic/gin/pull/2149) * Refactor redirect request in gin.go [#1970](https://github.com/gin-gonic/gin/pull/1970) * Add build tag nomsgpack [#1852](https://github.com/gin-gonic/gin/pull/1852) + ### DOCS + * docs(path): improve comments [#2223](https://github.com/gin-gonic/gin/pull/2223) * Renew README to fit the modification of SetCookie method [#2217](https://github.com/gin-gonic/gin/pull/2217) * Fix spelling [#2202](https://github.com/gin-gonic/gin/pull/2202) @@ -202,7 +272,9 @@ * Add project to README [#2165](https://github.com/gin-gonic/gin/pull/2165) * docs(benchmarks): for gin v1.5 [#2153](https://github.com/gin-gonic/gin/pull/2153) * Changed wording for clarity in README.md [#2122](https://github.com/gin-gonic/gin/pull/2122) + ### MISC + * ci support go1.14 [#2262](https://github.com/gin-gonic/gin/pull/2262) * chore: upgrade depend version [#2231](https://github.com/gin-gonic/gin/pull/2231) * Drop support go1.10 [#2147](https://github.com/gin-gonic/gin/pull/2147) diff --git a/README.md b/README.md index 336155a990..e007bf2fbb 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) [![GoDoc](https://pkg.go.dev/badge/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc) -[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) [![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases) @@ -31,7 +30,7 @@ Gin is a web framework written in [Go](https://go.dev/). It features a martini-l ### Prerequisites -- **[Go](https://go.dev/)**: ~~any one of the **three latest major** [releases](https://go.dev/doc/devel/release)~~ (now version **1.16+** is required). +- **[Go](https://go.dev/)**: any one of the **three latest major** [releases](https://go.dev/doc/devel/release) (we test it with these). ### Getting Gin @@ -176,4 +175,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor Gin is the work of hundreds of contributors. We appreciate your help! -Please see [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. \ No newline at end of file +Please see [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. diff --git a/any.go b/any.go deleted file mode 100644 index 42b1ea46f7..0000000000 --- a/any.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2022 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !go1.18 -// +build !go1.18 - -package gin - -type any = interface{} diff --git a/binding/any.go b/binding/any.go deleted file mode 100644 index d8251a7c20..0000000000 --- a/binding/any.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2022 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !go1.18 -// +build !go1.18 - -package binding - -type any = interface{} diff --git a/binding/binding.go b/binding/binding.go index a58924ed3e..40948529a3 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package binding diff --git a/binding/binding_msgpack_test.go b/binding/binding_msgpack_test.go index 04d9407971..a6cd6aa83d 100644 --- a/binding/binding_msgpack_test.go +++ b/binding/binding_msgpack_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package binding diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go index 7f6a904ab0..93ad8ba304 100644 --- a/binding/binding_nomsgpack.go +++ b/binding/binding_nomsgpack.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build nomsgpack -// +build nomsgpack package binding diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 540bbbb84a..55435b9493 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -239,6 +239,11 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) case reflect.Map: return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) + case reflect.Ptr: + if !value.Elem().IsValid() { + value.Set(reflect.New(value.Type().Elem())) + } + return setWithProperType(val, value.Elem(), field) default: return errUnknownType } diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 93d6a92ff8..acea8f77a7 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -269,6 +269,39 @@ func TestMappingStructField(t *testing.T) { assert.Equal(t, 9, s.J.I) } +func TestMappingPtrField(t *testing.T) { + type ptrStruct struct { + Key int64 `json:"key"` + } + + type ptrRequest struct { + Items []*ptrStruct `json:"items" form:"items"` + } + + var err error + + // With 0 items. + var req0 ptrRequest + err = mappingByPtr(&req0, formSource{}, "form") + assert.NoError(t, err) + assert.Empty(t, req0.Items) + + // With 1 item. + var req1 ptrRequest + err = mappingByPtr(&req1, formSource{"items": {`{"key": 1}`}}, "form") + assert.NoError(t, err) + assert.Len(t, req1.Items, 1) + assert.EqualValues(t, 1, req1.Items[0].Key) + + // With 2 items. + var req2 ptrRequest + err = mappingByPtr(&req2, formSource{"items": {`{"key": 1}`, `{"key": 2}`}}, "form") + assert.NoError(t, err) + assert.Len(t, req2.Items, 2) + assert.EqualValues(t, 1, req2.Items[0].Key) + assert.EqualValues(t, 2, req2.Items[1].Key) +} + func TestMappingMapField(t *testing.T) { var s struct { M map[string]int diff --git a/binding/json.go b/binding/json.go index 36eb27a344..e21c2ee346 100644 --- a/binding/json.go +++ b/binding/json.go @@ -15,7 +15,7 @@ import ( // EnableDecoderUseNumber is used to call the UseNumber method on the JSON // Decoder instance. UseNumber causes the Decoder to unmarshal a number into an -// interface{} as a Number instead of as a float64. +// any as a Number instead of as a float64. var EnableDecoderUseNumber = false // EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method diff --git a/binding/msgpack.go b/binding/msgpack.go index d1f035e44a..22de9b5515 100644 --- a/binding/msgpack.go +++ b/binding/msgpack.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package binding diff --git a/binding/msgpack_test.go b/binding/msgpack_test.go index 11561c843d..df386a6d56 100644 --- a/binding/msgpack_test.go +++ b/binding/msgpack_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package binding diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index c9c9a522da..0000000000 --- a/codecov.yml +++ /dev/null @@ -1,5 +0,0 @@ -coverage: - notify: - gitter: - default: - url: https://webhooks.gitter.im/e/d90dcdeeab2f1e357165 diff --git a/context.go b/context.go index 556f8ac9cc..420ff1678f 100644 --- a/context.go +++ b/context.go @@ -652,7 +652,7 @@ func (c *Context) BindYAML(obj any) error { } // BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML). -func (c *Context) BindTOML(obj interface{}) error { +func (c *Context) BindTOML(obj any) error { return c.MustBindWith(obj, binding.TOML) } @@ -717,7 +717,7 @@ func (c *Context) ShouldBindYAML(obj any) error { } // ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML). -func (c *Context) ShouldBindTOML(obj interface{}) error { +func (c *Context) ShouldBindTOML(obj any) error { return c.ShouldBindWith(obj, binding.TOML) } @@ -995,7 +995,7 @@ func (c *Context) YAML(code int, obj any) { } // TOML serializes the given struct as TOML into the response body. -func (c *Context) TOML(code int, obj interface{}) { +func (c *Context) TOML(code int, obj any) { c.Render(code, render.TOML{Data: obj}) } @@ -1052,11 +1052,17 @@ func (c *Context) FileFromFS(filepath string, fs http.FileSystem) { http.FileServer(fs).ServeHTTP(c.Writer, c.Request) } +var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") + +func escapeQuotes(s string) string { + return quoteEscaper.Replace(s) +} + // FileAttachment writes the specified file into the body stream in an efficient way // On the client side, the file will typically be downloaded with the given filename func (c *Context) FileAttachment(filepath, filename string) { if isASCII(filename) { - c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+filename+`"`) + c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+escapeQuotes(filename)+`"`) } else { c.Writer.Header().Set("Content-Disposition", `attachment; filename*=UTF-8''`+url.QueryEscape(filename)) } @@ -1174,9 +1180,16 @@ func (c *Context) SetAccepted(formats ...string) { /***** GOLANG.ORG/X/NET/CONTEXT *****/ /************************************/ +// hasRequestContext returns whether c.Request has Context and fallback. +func (c *Context) hasRequestContext() bool { + hasFallback := c.engine != nil && c.engine.ContextWithFallback + hasRequestContext := c.Request != nil && c.Request.Context() != nil + return hasFallback && hasRequestContext +} + // Deadline returns that there is no deadline (ok==false) when c.Request has no Context. func (c *Context) Deadline() (deadline time.Time, ok bool) { - if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil { + if !c.hasRequestContext() { return } return c.Request.Context().Deadline() @@ -1184,7 +1197,7 @@ func (c *Context) Deadline() (deadline time.Time, ok bool) { // Done returns nil (chan which will wait forever) when c.Request has no Context. func (c *Context) Done() <-chan struct{} { - if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil { + if !c.hasRequestContext() { return nil } return c.Request.Context().Done() @@ -1192,7 +1205,7 @@ func (c *Context) Done() <-chan struct{} { // Err returns nil when c.Request has no Context. func (c *Context) Err() error { - if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil { + if !c.hasRequestContext() { return nil } return c.Request.Context().Err() @@ -1213,7 +1226,7 @@ func (c *Context) Value(key any) any { return val } } - if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil { + if !c.hasRequestContext() { return nil } return c.Request.Context().Value(key) diff --git a/context_1.17_test.go b/context_1.17_test.go deleted file mode 100644 index 0f8527fe09..0000000000 --- a/context_1.17_test.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2021 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build go1.17 -// +build go1.17 - -package gin - -import ( - "bytes" - "mime/multipart" - "net/http" - "net/http/httptest" - "runtime" - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -type interceptedWriter struct { - ResponseWriter - b *bytes.Buffer -} - -func (i interceptedWriter) WriteHeader(code int) { - i.Header().Del("X-Test") - i.ResponseWriter.WriteHeader(code) -} - -func TestContextFormFileFailed17(t *testing.T) { - if !isGo117OrGo118() { - return - } - buf := new(bytes.Buffer) - mw := multipart.NewWriter(buf) - defer func(mw *multipart.Writer) { - err := mw.Close() - if err != nil { - assert.Error(t, err) - } - }(mw) - c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) - c.Request.Header.Set("Content-Type", mw.FormDataContentType()) - c.engine.MaxMultipartMemory = 8 << 20 - assert.Panics(t, func() { - f, err := c.FormFile("file") - assert.Error(t, err) - assert.Nil(t, f) - }) -} - -func TestInterceptedHeader(t *testing.T) { - w := httptest.NewRecorder() - c, r := CreateTestContext(w) - - r.Use(func(c *Context) { - i := interceptedWriter{ - ResponseWriter: c.Writer, - b: bytes.NewBuffer(nil), - } - c.Writer = i - c.Next() - c.Header("X-Test", "overridden") - c.Writer = i.ResponseWriter - }) - r.GET("/", func(c *Context) { - c.Header("X-Test", "original") - c.Header("X-Test-2", "present") - c.String(http.StatusOK, "hello world") - }) - c.Request = httptest.NewRequest("GET", "/", nil) - r.HandleContext(c) - // Result() has headers frozen when WriteHeaderNow() has been called - // Compared to this time, this is when the response headers will be flushed - // As response is flushed on c.String, the Header cannot be set by the first - // middleware. Assert this - assert.Equal(t, "", w.Result().Header.Get("X-Test")) - assert.Equal(t, "present", w.Result().Header.Get("X-Test-2")) -} - -func isGo117OrGo118() bool { - version := strings.Split(runtime.Version()[2:], ".") - if len(version) >= 2 { - x := version[0] - y := version[1] - if x == "1" && (y == "17" || y == "18") { - return true - } - } - return false -} diff --git a/context_1.16_test.go b/context_1.18_test.go similarity index 66% rename from context_1.16_test.go rename to context_1.18_test.go index 267605072e..6118beaa49 100644 --- a/context_1.16_test.go +++ b/context_1.18_test.go @@ -2,8 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -//go:build !go1.17 -// +build !go1.17 +//go:build !go1.19 package gin @@ -17,15 +16,22 @@ import ( "github.com/stretchr/testify/assert" ) -func TestContextFormFileFailed16(t *testing.T) { +func TestContextFormFileFailed18(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) - mw.Close() + defer func(mw *multipart.Writer) { + err := mw.Close() + if err != nil { + assert.Error(t, err) + } + }(mw) c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) c.engine.MaxMultipartMemory = 8 << 20 - f, err := c.FormFile("file") - assert.Error(t, err) - assert.Nil(t, f) + assert.Panics(t, func() { + f, err := c.FormFile("file") + assert.Error(t, err) + assert.Nil(t, f) + }) } diff --git a/context_1.19_test.go b/context_1.19_test.go index 4b34ea2472..dd75325b18 100644 --- a/context_1.19_test.go +++ b/context_1.19_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build go1.19 -// +build go1.19 package gin diff --git a/context_appengine.go b/context_appengine.go index 931313f613..96b339c488 100644 --- a/context_appengine.go +++ b/context_appengine.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build appengine -// +build appengine package gin diff --git a/context_test.go b/context_test.go index 1ab6b33949..70d4758377 100644 --- a/context_test.go +++ b/context_test.go @@ -37,7 +37,7 @@ var errTestRender = errors.New("TestRender") // Unit tests TODO // func (c *Context) File(filepath string) { // func (c *Context) Negotiate(code int, config Negotiate) { -// BAD case: func (c *Context) Render(code int, render render.Render, obj ...interface{}) { +// BAD case: func (c *Context) Render(code int, render render.Render, obj ...any) { // test that information is not leaked when reusing Contexts (using the Pool) func createMultipartRequest() *http.Request { @@ -1032,6 +1032,20 @@ func TestContextRenderAttachment(t *testing.T) { assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.Header().Get("Content-Disposition")) } +func TestContextRenderAndEscapeAttachment(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + maliciousFilename := "tampering_field.sh\"; \\\"; dummy=.go" + actualEscapedResponseFilename := "tampering_field.sh\\\"; \\\\\\\"; dummy=.go" + + c.Request, _ = http.NewRequest("GET", "/", nil) + c.FileAttachment("./gin.go", maliciousFilename) + + assert.Equal(t, 200, w.Code) + assert.Contains(t, w.Body.String(), "func New() *Engine {") + assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", actualEscapedResponseFilename), w.Header().Get("Content-Disposition")) +} + func TestContextRenderUTF8Attachment(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) @@ -2162,6 +2176,24 @@ func TestRemoteIPFail(t *testing.T) { assert.False(t, trust) } +func TestHasRequestContext(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + assert.False(t, c.hasRequestContext(), "no request, no fallback") + c.engine.ContextWithFallback = true + assert.False(t, c.hasRequestContext(), "no request, has fallback") + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + assert.True(t, c.hasRequestContext(), "has request, has fallback") + c.Request, _ = http.NewRequestWithContext(nil, "", "", nil) //nolint:staticcheck + assert.False(t, c.hasRequestContext(), "has request with nil ctx, has fallback") + c.engine.ContextWithFallback = false + assert.False(t, c.hasRequestContext(), "has request, no fallback") + + c = &Context{} + assert.False(t, c.hasRequestContext(), "no request, no engine") + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + assert.False(t, c.hasRequestContext(), "has request, no engine") +} + func TestContextWithFallbackDeadlineFromRequestContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) // enable ContextWithFallback feature flag @@ -2374,3 +2406,42 @@ func TestCreateTestContextWithRouteParams(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "hello gin", w.Body.String()) } + +type interceptedWriter struct { + ResponseWriter + b *bytes.Buffer +} + +func (i interceptedWriter) WriteHeader(code int) { + i.Header().Del("X-Test") + i.ResponseWriter.WriteHeader(code) +} + +func TestInterceptedHeader(t *testing.T) { + w := httptest.NewRecorder() + c, r := CreateTestContext(w) + + r.Use(func(c *Context) { + i := interceptedWriter{ + ResponseWriter: c.Writer, + b: bytes.NewBuffer(nil), + } + c.Writer = i + c.Next() + c.Header("X-Test", "overridden") + c.Writer = i.ResponseWriter + }) + r.GET("/", func(c *Context) { + c.Header("X-Test", "original") + c.Header("X-Test-2", "present") + c.String(http.StatusOK, "hello world") + }) + c.Request = httptest.NewRequest("GET", "/", nil) + r.HandleContext(c) + // Result() has headers frozen when WriteHeaderNow() has been called + // Compared to this time, this is when the response headers will be flushed + // As response is flushed on c.String, the Header cannot be set by the first + // middleware. Assert this + assert.Equal(t, "", w.Result().Header.Get("X-Test")) + assert.Equal(t, "present", w.Result().Header.Get("X-Test-2")) +} diff --git a/debug.go b/debug.go index cbcedbc98f..1fc0cafe10 100644 --- a/debug.go +++ b/debug.go @@ -12,7 +12,7 @@ import ( "strings" ) -const ginSupportMinGoVer = 16 +const ginSupportMinGoVer = 18 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. @@ -67,7 +67,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.16+. + debugPrint(`[WARNING] Now Gin requires Go 1.18+. `) } diff --git a/debug_test.go b/debug_test.go index ce8b19da7b..2d5e9a5600 100644 --- a/debug_test.go +++ b/debug_test.go @@ -21,7 +21,7 @@ import ( // TODO // func debugRoute(httpMethod, absolutePath string, handlers HandlersChain) { -// func debugPrint(format string, values ...interface{}) { +// func debugPrint(format string, values ...any) { func TestIsDebugging(t *testing.T) { SetMode(DebugMode) @@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m < ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.16+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.18+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } diff --git a/deprecated.go b/deprecated.go index fdad855477..9521308f1d 100644 --- a/deprecated.go +++ b/deprecated.go @@ -13,7 +13,7 @@ import ( // BindWith binds the passed struct pointer using the specified binding engine. // See the binding package. func (c *Context) BindWith(obj any, b binding.Binding) error { - log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to + log.Println(`BindWith(\"any, binding.Binding\") error is going to be deprecated, please check issue #662 and either use MustBindWith() if you want HTTP 400 to be automatically returned if any error occur, or use ShouldBindWith() if you need to manage the error.`) diff --git a/docs/doc.md b/docs/doc.md index 7cebab566e..e48c2ba183 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -425,7 +425,7 @@ func main() { r.Use(gin.Logger()) // Recovery middleware recovers from any panics and writes a 500 if there was one. - r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) { + r.Use(gin.CustomRecovery(func(c *gin.Context, recovered any) { if err, ok := recovered.(string); ok { c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err)) } @@ -996,7 +996,7 @@ curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost: func main() { r := gin.Default() - // gin.H is a shortcut for map[string]interface{} + // gin.H is a shortcut for map[string]any r.GET("/someJSON", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) }) @@ -1961,7 +1961,7 @@ func (customerBinding) Name() string { return "form" } -func (customerBinding) Bind(req *http.Request, obj interface{}) error { +func (customerBinding) Bind(req *http.Request, obj any) error { if err := req.ParseForm(); err != nil { return err } @@ -1976,7 +1976,7 @@ func (customerBinding) Bind(req *http.Request, obj interface{}) error { return validate(obj) } -func validate(obj interface{}) error { +func validate(obj any) error { if binding.Validator == nil { return nil } diff --git a/fs.go b/fs.go index 64274735e2..f17d7434d9 100644 --- a/fs.go +++ b/fs.go @@ -39,7 +39,7 @@ func (fs onlyFilesFS) Open(name string) (http.File, error) { } // Readdir overrides the http.File default implementation. -func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) { +func (f neuteredReaddirFile) Readdir(_ int) ([]os.FileInfo, error) { // this disables directory listing return nil, nil } diff --git a/gin.go b/gin.go index 35159d03fa..5a605cf143 100644 --- a/gin.go +++ b/gin.go @@ -11,6 +11,7 @@ import ( "net/http" "os" "path" + "regexp" "strings" "sync" @@ -40,6 +41,9 @@ var defaultTrustedCIDRs = []*net.IPNet{ }, } +var regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+") +var regRemoveRepeatedChar = regexp.MustCompile("/{2,}") + // HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) @@ -330,7 +334,6 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { } root.addRoute(path, handlers) - // Update maxParams if paramsCount := countParams(path); paramsCount > engine.maxParams { engine.maxParams = paramsCount } @@ -511,7 +514,7 @@ func (engine *Engine) RunUnix(file string) (err error) { if engine.isUnsafeTrustedProxies() { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + - "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } listener, err := net.Listen("unix", file) @@ -534,7 +537,7 @@ func (engine *Engine) RunFd(fd int) (err error) { if engine.isUnsafeTrustedProxies() { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + - "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd)) @@ -555,7 +558,7 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) { if engine.isUnsafeTrustedProxies() { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + - "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } err = http.Serve(listener, engine.Handler()) @@ -668,6 +671,9 @@ func redirectTrailingSlash(c *Context) { req := c.Request p := req.URL.Path if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." { + prefix = regSafePrefix.ReplaceAllString(prefix, "") + prefix = regRemoveRepeatedChar.ReplaceAllString(prefix, "/") + p = prefix + "/" + req.URL.Path } req.URL.Path = p + "/" diff --git a/go.mod b/go.mod index 2ef45cfee6..c365e77ded 100644 --- a/go.mod +++ b/go.mod @@ -1,36 +1,36 @@ module github.com/gin-gonic/gin -go 1.18 +go 1.20 require ( - github.com/bytedance/sonic v1.7.1 + github.com/bytedance/sonic v1.9.1 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.11.2 - github.com/goccy/go-json v0.10.0 + github.com/go-playground/validator/v10 v10.16.0 + github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 - github.com/mattn/go-isatty v0.0.17 - github.com/pelletier/go-toml/v2 v2.0.6 - github.com/stretchr/testify v1.8.1 - github.com/ugorji/go/codec v1.2.9 - golang.org/x/net v0.6.0 - google.golang.org/protobuf v1.28.1 + github.com/mattn/go-isatty v0.0.19 + github.com/pelletier/go-toml/v2 v2.0.8 + github.com/stretchr/testify v1.8.3 + github.com/ugorji/go/codec v1.2.11 + golang.org/x/net v0.18.0 + google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/klauspost/cpuid/v2 v2.0.14 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/leodido/go-urn v1.2.1 // indirect - github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect - golang.org/x/crypto v0.5.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.15.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index b47804fcc0..4bd36dbbea 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,14 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.7.1 h1:UYWEKUHQDye89c2U6zvrvuxWdGCI/wCrZITFQmKGtGc= -github.com/bytedance/sonic v1.7.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -15,10 +16,10 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= -github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= -github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= -github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= +github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -26,57 +27,56 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.14 h1:QRqdp6bb9M9S5yyKeYteXKuoKE4p0tGlra81fKOpWH8= -github.com/klauspost/cpuid/v2 v2.0.14/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= -github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SXUn9Rm81rRkZ0TTmn/cU= -golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/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/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/bytesconv/bytesconv.go b/internal/bytesconv/bytesconv_1.19.go similarity index 96% rename from internal/bytesconv/bytesconv.go rename to internal/bytesconv/bytesconv_1.19.go index 86e4c4d44c..669c9c914e 100644 --- a/internal/bytesconv/bytesconv.go +++ b/internal/bytesconv/bytesconv_1.19.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +//go:build !go1.20 + package bytesconv import ( diff --git a/internal/bytesconv/bytesconv_1.20.go b/internal/bytesconv/bytesconv_1.20.go new file mode 100644 index 0000000000..5b6040a6b3 --- /dev/null +++ b/internal/bytesconv/bytesconv_1.20.go @@ -0,0 +1,23 @@ +// Copyright 2023 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build go1.20 + +package bytesconv + +import ( + "unsafe" +) + +// StringToBytes converts string to byte slice without a memory allocation. +// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077. +func StringToBytes(s string) []byte { + return unsafe.Slice(unsafe.StringData(s), len(s)) +} + +// BytesToString converts byte slice to string without a memory allocation. +// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077. +func BytesToString(b []byte) string { + return unsafe.String(unsafe.SliceData(b), len(b)) +} diff --git a/internal/json/go_json.go b/internal/json/go_json.go index 23f717265e..47c3559831 100644 --- a/internal/json/go_json.go +++ b/internal/json/go_json.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build go_json -// +build go_json package json diff --git a/internal/json/json.go b/internal/json/json.go index c5f3efc88d..c7ee83eb3b 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -3,9 +3,6 @@ // license that can be found in the LICENSE file. //go:build !jsoniter && !go_json && !(sonic && avx && (linux || windows || darwin) && amd64) -// +build !jsoniter -// +build !go_json -// +build !sonic !avx !linux,!windows,!darwin !amd64 package json diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go index 853b1a901e..45ed16ba9f 100644 --- a/internal/json/jsoniter.go +++ b/internal/json/jsoniter.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build jsoniter -// +build jsoniter package json diff --git a/internal/json/sonic.go b/internal/json/sonic.go index 5a9ca4b2d0..529e16d072 100644 --- a/internal/json/sonic.go +++ b/internal/json/sonic.go @@ -3,10 +3,6 @@ // license that can be found in the LICENSE file. //go:build sonic && avx && (linux || windows || darwin) && amd64 -// +build sonic -// +build avx -// +build linux windows darwin -// +build amd64 package json diff --git a/logger.go b/logger.go index cd1e7fa6e9..1e6cf77a62 100644 --- a/logger.go +++ b/logger.go @@ -83,6 +83,8 @@ func (p *LogFormatterParams) StatusCodeColor() string { code := p.StatusCode switch { + case code >= http.StatusContinue && code < http.StatusOK: + return white case code >= http.StatusOK && code < http.StatusMultipleChoices: return green case code >= http.StatusMultipleChoices && code < http.StatusBadRequest: diff --git a/logger_test.go b/logger_test.go index 5f78708f1a..b93e1e0425 100644 --- a/logger_test.go +++ b/logger_test.go @@ -310,6 +310,7 @@ func TestColorForStatus(t *testing.T) { return p.StatusCodeColor() } + assert.Equal(t, white, colorForStatus(http.StatusContinue), "1xx should be white") assert.Equal(t, green, colorForStatus(http.StatusOK), "2xx should be green") assert.Equal(t, white, colorForStatus(http.StatusMovedPermanently), "3xx should be white") assert.Equal(t, yellow, colorForStatus(http.StatusNotFound), "4xx should be yellow") diff --git a/recovery.go b/recovery.go index 2955c03a0e..515f9d2adc 100644 --- a/recovery.go +++ b/recovery.go @@ -103,7 +103,7 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { } } -func defaultHandleRecovery(c *Context, err any) { +func defaultHandleRecovery(c *Context, _ any) { c.AbortWithStatus(http.StatusInternalServerError) } @@ -164,7 +164,7 @@ func function(pc uintptr) []byte { if period := bytes.Index(name, dot); period >= 0 { name = name[period+1:] } - name = bytes.Replace(name, centerDot, dot, -1) + name = bytes.ReplaceAll(name, centerDot, dot) return name } diff --git a/render/any.go b/render/any.go deleted file mode 100644 index b19ad45d9f..0000000000 --- a/render/any.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2021 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !go1.18 -// +build !go1.18 - -package render - -type any = interface{} diff --git a/render/msgpack.go b/render/msgpack.go index e0f30f7a97..d1d8e84b03 100644 --- a/render/msgpack.go +++ b/render/msgpack.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package render diff --git a/render/render_msgpack_test.go b/render/render_msgpack_test.go index 642123614e..db4b71e5ee 100644 --- a/render/render_msgpack_test.go +++ b/render/render_msgpack_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package render diff --git a/render/render_test.go b/render/render_test.go index 192552513f..c9db635f04 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -15,6 +15,7 @@ import ( "strings" "testing" + "github.com/gin-gonic/gin/internal/json" testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "google.golang.org/protobuf/proto" @@ -136,6 +137,51 @@ func TestRenderJsonpJSON(t *testing.T) { assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) } +type errorWriter struct { + bufString string + *httptest.ResponseRecorder +} + +var _ http.ResponseWriter = (*errorWriter)(nil) + +func (w *errorWriter) Write(buf []byte) (int, error) { + if string(buf) == w.bufString { + return 0, errors.New(`write "` + w.bufString + `" error`) + } + return w.ResponseRecorder.Write(buf) +} + +func TestRenderJsonpJSONError(t *testing.T) { + ew := &errorWriter{ + ResponseRecorder: httptest.NewRecorder(), + } + + jsonpJSON := JsonpJSON{ + Callback: "foo", + Data: map[string]string{ + "foo": "bar", + }, + } + + cb := template.JSEscapeString(jsonpJSON.Callback) + ew.bufString = cb + err := jsonpJSON.Render(ew) // error was returned while writing callback + assert.Equal(t, `write "`+cb+`" error`, err.Error()) + + ew.bufString = `(` + err = jsonpJSON.Render(ew) + assert.Equal(t, `write "`+`(`+`" error`, err.Error()) + + data, _ := json.Marshal(jsonpJSON.Data) // error was returned while writing data + ew.bufString = string(data) + err = jsonpJSON.Render(ew) + assert.Equal(t, `write "`+string(data)+`" error`, err.Error()) + + ew.bufString = `);` + err = jsonpJSON.Render(ew) + assert.Equal(t, `write "`+`);`+`" error`, err.Error()) +} + func TestRenderJsonpJSONError2(t *testing.T) { w := httptest.NewRecorder() data := map[string]any{ @@ -532,3 +578,16 @@ func TestRenderReaderNoContentLength(t *testing.T) { assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition")) assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id")) } + +func TestRenderWriteError(t *testing.T) { + data := []interface{}{"value1", "value2"} + prefix := "my-prefix:" + r := SecureJSON{Data: data, Prefix: prefix} + ew := &errorWriter{ + bufString: prefix, + ResponseRecorder: httptest.NewRecorder(), + } + err := r.Render(ew) + assert.NotNil(t, err) + assert.Equal(t, `write "my-prefix:" error`, err.Error()) +} diff --git a/response_writer_test.go b/response_writer_test.go index 9fd5e87cce..964aa3071e 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -156,3 +156,33 @@ func TestResponseWriterStatusCode(t *testing.T) { // status must be 200 although we tried to change it assert.Equal(t, http.StatusOK, w.Status()) } + +// mockPusherResponseWriter is an http.ResponseWriter that implements http.Pusher. +type mockPusherResponseWriter struct { + http.ResponseWriter +} + +func (m *mockPusherResponseWriter) Push(target string, opts *http.PushOptions) error { + return nil +} + +// nonPusherResponseWriter is an http.ResponseWriter that does not implement http.Pusher. +type nonPusherResponseWriter struct { + http.ResponseWriter +} + +func TestPusherWithPusher(t *testing.T) { + rw := &mockPusherResponseWriter{} + w := &responseWriter{ResponseWriter: rw} + + pusher := w.Pusher() + assert.NotNil(t, pusher, "Expected pusher to be non-nil") +} + +func TestPusherWithoutPusher(t *testing.T) { + rw := &nonPusherResponseWriter{} + w := &responseWriter{ResponseWriter: rw} + + pusher := w.Pusher() + assert.Nil(t, pusher, "Expected pusher to be nil") +} diff --git a/routes_test.go b/routes_test.go index d51477e845..7a51f81771 100644 --- a/routes_test.go +++ b/routes_test.go @@ -185,6 +185,54 @@ func TestRouteRedirectTrailingSlash(t *testing.T) { w = PerformRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"}) assert.Equal(t, 200, w.Code) + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../api#?"}) + assert.Equal(t, "/api/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../api"}) + assert.Equal(t, "/api/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "../../api"}) + assert.Equal(t, "/api/path2/", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/../../api"}) + assert.Equal(t, "/api/path2/", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../"}) + assert.Equal(t, "//path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../../"}) + assert.Equal(t, "/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "../../gin-gonic.com"}) + assert.Equal(t, "/gin-goniccom/path2/", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/../../gin-gonic.com"}) + assert.Equal(t, "/gin-goniccom/path2/", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "https://gin-gonic.com/#"}) + assert.Equal(t, "https/gin-goniccom/https/gin-goniccom/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "#api"}) + assert.Equal(t, "api/api/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "/nor-mal/#?a=1"}) + assert.Equal(t, "/nor-mal/a1/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "/nor-mal/%2e%2e/"}) + assert.Equal(t, "/nor-mal/2e2e/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + router.RedirectTrailingSlash = false w = PerformRequest(router, http.MethodGet, "/path/") diff --git a/testdata/protoexample/any.go b/testdata/protoexample/any.go deleted file mode 100644 index 2203f33a32..0000000000 --- a/testdata/protoexample/any.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2021 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !go1.18 -// +build !go1.18 - -package protoexample - -type any = interface{} diff --git a/utils.go b/utils.go index 4021a2ab40..47106a7aea 100644 --- a/utils.go +++ b/utils.go @@ -50,7 +50,7 @@ func WrapH(h http.Handler) HandlerFunc { } } -// H is a shortcut for map[string]interface{} +// H is a shortcut for map[string]any type H map[string]any // MarshalXML allows type H to be used with xml.Marshal. diff --git a/version.go b/version.go index 37e27f27a5..85462e5553 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.8.2" +const Version = "v1.9.1"