mirror of https://github.com/gin-gonic/gin.git
Merge branch 'master' into feat/cookie-binding
This commit is contained in:
commit
7e982b25b9
|
@ -30,7 +30,7 @@ func main() {
|
||||||
|
|
||||||
<!-- Your expectation result of 'curl' command, like -->
|
<!-- Your expectation result of 'curl' command, like -->
|
||||||
```
|
```
|
||||||
$ curl http://localhost:8201/hello/world
|
$ curl http://localhost:9000/hello/world
|
||||||
Hello world
|
Hello world
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ Hello world
|
||||||
|
|
||||||
<!-- Actual result showing the problem -->
|
<!-- Actual result showing the problem -->
|
||||||
```
|
```
|
||||||
$ curl -i http://localhost:8201/hello/world
|
$ curl -i http://localhost:9000/hello/world
|
||||||
<YOUR RESULT>
|
<YOUR RESULT>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,12 @@ name: "CodeQL"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [master]
|
||||||
pull_request:
|
pull_request:
|
||||||
# The branches below must be a subset of the branches above
|
# The branches below must be a subset of the branches above
|
||||||
branches: [ master ]
|
branches: [master]
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 17 * * 5'
|
- cron: "0 17 * * 5"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
|
@ -29,15 +29,15 @@ jobs:
|
||||||
# Override automatic language detection by changing the below list
|
# Override automatic language detection by changing the below list
|
||||||
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
|
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
|
||||||
# TODO: Enable for javascript later
|
# TODO: Enable for javascript later
|
||||||
language: [ 'go']
|
language: ["go"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
@ -46,4 +46,4 @@ jobs:
|
||||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v3
|
||||||
|
|
|
@ -15,24 +15,28 @@ jobs:
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Setup go
|
- name: Checkout
|
||||||
uses: actions/setup-go@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
go-version: '^1.16'
|
fetch-depth: 0
|
||||||
- name: Checkout repository
|
- name: Set up Go
|
||||||
uses: actions/checkout@v3
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: "go.mod"
|
||||||
|
check-latest: true
|
||||||
- name: Setup golangci-lint
|
- name: Setup golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3.4.0
|
uses: golangci/golangci-lint-action@v4
|
||||||
with:
|
with:
|
||||||
version: v1.48.0
|
version: v1.56.2
|
||||||
args: --verbose
|
args: --verbose
|
||||||
test:
|
test:
|
||||||
needs: lint
|
needs: lint
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-latest]
|
os: [ubuntu-latest, macos-latest]
|
||||||
go: ['1.16', '1.17', '1.18', '1.19', '1.20']
|
go: ["1.18", "1.19", "1.20", "1.21", "1.22"]
|
||||||
test-tags: ['', '-tags nomsgpack', '-tags "sonic avx"', '-tags go_json']
|
test-tags:
|
||||||
|
["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"]
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
go-build: ~/.cache/go-build
|
go-build: ~/.cache/go-build
|
||||||
|
@ -46,16 +50,17 @@ jobs:
|
||||||
GOPROXY: https://proxy.golang.org
|
GOPROXY: https://proxy.golang.org
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go ${{ matrix.go }}
|
- name: Set up Go ${{ matrix.go }}
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
|
cache: false
|
||||||
|
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.ref }}
|
ref: ${{ github.ref }}
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
${{ matrix.go-build }}
|
${{ matrix.go-build }}
|
||||||
|
@ -68,24 +73,10 @@ jobs:
|
||||||
run: make test
|
run: make test
|
||||||
|
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v4
|
||||||
with:
|
with:
|
||||||
flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}
|
flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}
|
||||||
|
|
||||||
- name: Format
|
- name: Format
|
||||||
if: matrix.go-version == '1.19.x'
|
if: matrix.go-version == '1.22.x'
|
||||||
run: diff -u <(echo -n) <(gofmt -d .)
|
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
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ name: Goreleaser
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- '*'
|
- "*"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
@ -12,23 +12,20 @@ jobs:
|
||||||
goreleaser:
|
goreleaser:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
-
|
- name: Checkout
|
||||||
name: Checkout
|
uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
-
|
- name: Set up Go
|
||||||
name: Set up Go
|
uses: actions/setup-go@v5
|
||||||
uses: actions/setup-go@v3
|
|
||||||
with:
|
with:
|
||||||
go-version: 1.17
|
go-version: "^1"
|
||||||
-
|
- name: Run GoReleaser
|
||||||
name: Run GoReleaser
|
uses: goreleaser/goreleaser-action@v5
|
||||||
uses: goreleaser/goreleaser-action@v4
|
|
||||||
with:
|
with:
|
||||||
# either 'goreleaser' (default) or 'goreleaser-pro'
|
# either 'goreleaser' (default) or 'goreleaser-pro'
|
||||||
distribution: goreleaser
|
distribution: goreleaser
|
||||||
version: latest
|
version: latest
|
||||||
args: release --rm-dist
|
args: release --clean
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
|
@ -5,4 +5,7 @@ count.out
|
||||||
test
|
test
|
||||||
profile.out
|
profile.out
|
||||||
tmp.out
|
tmp.out
|
||||||
.idea
|
|
||||||
|
# Develop tools
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
|
@ -3,7 +3,6 @@ run:
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
- asciicheck
|
- asciicheck
|
||||||
- depguard
|
|
||||||
- dogsled
|
- dogsled
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- errcheck
|
- errcheck
|
||||||
|
@ -19,6 +18,22 @@ linters:
|
||||||
- nolintlint
|
- nolintlint
|
||||||
- revive
|
- revive
|
||||||
- wastedassign
|
- 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:
|
issues:
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
- linters:
|
- linters:
|
||||||
|
@ -37,3 +52,6 @@ issues:
|
||||||
- path: _test\.go
|
- path: _test\.go
|
||||||
linters:
|
linters:
|
||||||
- gosec # security is not make sense in tests
|
- gosec # security is not make sense in tests
|
||||||
|
- linters:
|
||||||
|
- revive
|
||||||
|
path: _test\.go
|
||||||
|
|
100
CHANGELOG.md
100
CHANGELOG.md
|
@ -1,13 +1,70 @@
|
||||||
# Gin ChangeLog
|
# 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
|
## Gin v1.8.2
|
||||||
|
|
||||||
### Bugs
|
### BUG FIXES
|
||||||
|
|
||||||
* fix(route): redirectSlash bug ([#3227]((https://github.com/gin-gonic/gin/pull/3227)))
|
* 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)))
|
* 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)))
|
* Fix the GO-2022-1144 vulnerability ([#3432]((https://github.com/gin-gonic/gin/pull/3432)))
|
||||||
|
|
||||||
|
@ -19,12 +76,12 @@
|
||||||
|
|
||||||
## Gin v1.8.0
|
## 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`
|
* 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)
|
* 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)
|
* 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)
|
* Fix: wrong when wildcard follows named param [#2983](https://github.com/gin-gonic/gin/pull/2983)
|
||||||
|
@ -61,7 +118,7 @@
|
||||||
|
|
||||||
## Gin v1.7.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).
|
* 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).
|
* 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
|
## Gin v1.7.6
|
||||||
|
|
||||||
### BUGFIXES
|
### BUG FIXES
|
||||||
|
|
||||||
* bump new release to fix v1.7.5 release error by using v1.7.4 codes.
|
* bump new release to fix v1.7.5 release error by using v1.7.4 codes.
|
||||||
|
|
||||||
## Gin v1.7.4
|
## Gin v1.7.4
|
||||||
|
|
||||||
### BUGFIXES
|
### BUG FIXES
|
||||||
|
|
||||||
* bump new release to fix checksum mismatch
|
* bump new release to fix checksum mismatch
|
||||||
|
|
||||||
## Gin v1.7.3
|
## 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)
|
* 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
|
## 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).
|
* 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
|
## 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))
|
* 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
|
## 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 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))
|
* 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
|
## Gin v1.6.2
|
||||||
|
|
||||||
### BUGFIXES
|
### BUG FIXES
|
||||||
|
|
||||||
* fix missing initial sync.RWMutex [#2305](https://github.com/gin-gonic/gin/pull/2305)
|
* fix missing initial sync.RWMutex [#2305](https://github.com/gin-gonic/gin/pull/2305)
|
||||||
|
|
||||||
### ENHANCEMENTS
|
### ENHANCEMENTS
|
||||||
|
|
||||||
* Add set samesite in cookie. [#2306](https://github.com/gin-gonic/gin/pull/2306)
|
* Add set samesite in cookie. [#2306](https://github.com/gin-gonic/gin/pull/2306)
|
||||||
|
|
||||||
## Gin v1.6.1
|
## Gin v1.6.1
|
||||||
|
|
||||||
### BUGFIXES
|
### BUG FIXES
|
||||||
|
|
||||||
* Revert "fix accept incoming network connections" [#2294](https://github.com/gin-gonic/gin/pull/2294)
|
* Revert "fix accept incoming network connections" [#2294](https://github.com/gin-gonic/gin/pull/2294)
|
||||||
|
|
||||||
## Gin v1.6.0
|
## Gin v1.6.0
|
||||||
|
|
||||||
### BREAKING
|
### BREAKING
|
||||||
|
|
||||||
* chore(performance): Improve performance for adding RemoveExtraSlash flag [#2159](https://github.com/gin-gonic/gin/pull/2159)
|
* 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)
|
* 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)
|
* Added support for SameSite cookie flag [#1615](https://github.com/gin-gonic/gin/pull/1615)
|
||||||
|
|
||||||
### FEATURES
|
### FEATURES
|
||||||
|
|
||||||
* add yaml negotiation [#2220](https://github.com/gin-gonic/gin/pull/2220)
|
* add yaml negotiation [#2220](https://github.com/gin-gonic/gin/pull/2220)
|
||||||
* FileFromFS [#2112](https://github.com/gin-gonic/gin/pull/2112)
|
* 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)
|
* 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)
|
* 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)
|
* 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)
|
* 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)
|
* [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)
|
* Add mutex for protect Context.Keys map [#1391](https://github.com/gin-gonic/gin/pull/1391)
|
||||||
|
|
||||||
### ENHANCEMENTS
|
### ENHANCEMENTS
|
||||||
|
|
||||||
* Add mitigation for log injection [#2277](https://github.com/gin-gonic/gin/pull/2277)
|
* 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: 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)
|
* 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)
|
* 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)
|
* 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)
|
* Add build tag nomsgpack [#1852](https://github.com/gin-gonic/gin/pull/1852)
|
||||||
|
|
||||||
### DOCS
|
### DOCS
|
||||||
|
|
||||||
* docs(path): improve comments [#2223](https://github.com/gin-gonic/gin/pull/2223)
|
* 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)
|
* 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)
|
* 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)
|
* 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)
|
* 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)
|
* Changed wording for clarity in README.md [#2122](https://github.com/gin-gonic/gin/pull/2122)
|
||||||
|
|
||||||
### MISC
|
### MISC
|
||||||
|
|
||||||
* ci support go1.14 [#2262](https://github.com/gin-gonic/gin/pull/2262)
|
* 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)
|
* 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)
|
* Drop support go1.10 [#2147](https://github.com/gin-gonic/gin/pull/2147)
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
[![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin)
|
[![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)
|
[![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)
|
[![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)
|
[![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)
|
[![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)
|
[![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
|
### 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
|
### Getting Gin
|
||||||
|
|
||||||
|
|
10
any.go
10
any.go
|
@ -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{}
|
|
25
auth.go
25
auth.go
|
@ -16,6 +16,9 @@ import (
|
||||||
// AuthUserKey is the cookie name for user credential in basic auth.
|
// AuthUserKey is the cookie name for user credential in basic auth.
|
||||||
const AuthUserKey = "user"
|
const AuthUserKey = "user"
|
||||||
|
|
||||||
|
// AuthProxyUserKey is the cookie name for proxy_user credential in basic auth for proxy.
|
||||||
|
const AuthProxyUserKey = "proxy_user"
|
||||||
|
|
||||||
// Accounts defines a key/value for user/pass list of authorized logins.
|
// Accounts defines a key/value for user/pass list of authorized logins.
|
||||||
type Accounts map[string]string
|
type Accounts map[string]string
|
||||||
|
|
||||||
|
@ -89,3 +92,25 @@ func authorizationHeader(user, password string) string {
|
||||||
base := user + ":" + password
|
base := user + ":" + password
|
||||||
return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base))
|
return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BasicAuthForProxy returns a Basic HTTP Proxy-Authorization middleware.
|
||||||
|
// If the realm is empty, "Proxy Authorization Required" will be used by default.
|
||||||
|
func BasicAuthForProxy(accounts Accounts, realm string) HandlerFunc {
|
||||||
|
if realm == "" {
|
||||||
|
realm = "Proxy Authorization Required"
|
||||||
|
}
|
||||||
|
realm = "Basic realm=" + strconv.Quote(realm)
|
||||||
|
pairs := processAccounts(accounts)
|
||||||
|
return func(c *Context) {
|
||||||
|
proxyUser, found := pairs.searchCredential(c.requestHeader("Proxy-Authorization"))
|
||||||
|
if !found {
|
||||||
|
// Credentials doesn't match, we return 407 and abort handlers chain.
|
||||||
|
c.Header("Proxy-Authenticate", realm)
|
||||||
|
c.AbortWithStatus(http.StatusProxyAuthRequired)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// The proxy_user credentials was found, set proxy_user's id to key AuthProxyUserKey in this context, the proxy_user's id can be read later using
|
||||||
|
// c.MustGet(gin.AuthProxyUserKey).
|
||||||
|
c.Set(AuthProxyUserKey, proxyUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
37
auth_test.go
37
auth_test.go
|
@ -137,3 +137,40 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) {
|
||||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||||
assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.Header().Get("WWW-Authenticate"))
|
assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.Header().Get("WWW-Authenticate"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBasicAuthForProxySucceed(t *testing.T) {
|
||||||
|
accounts := Accounts{"admin": "password"}
|
||||||
|
router := New()
|
||||||
|
router.Use(BasicAuthForProxy(accounts, ""))
|
||||||
|
router.Any("/*proxyPath", func(c *Context) {
|
||||||
|
c.String(http.StatusOK, c.MustGet(AuthProxyUserKey).(string))
|
||||||
|
})
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("GET", "/test", nil)
|
||||||
|
req.Header.Set("Proxy-Authorization", authorizationHeader("admin", "password"))
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Equal(t, "admin", w.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicAuthForProxy407(t *testing.T) {
|
||||||
|
called := false
|
||||||
|
accounts := Accounts{"foo": "bar"}
|
||||||
|
router := New()
|
||||||
|
router.Use(BasicAuthForProxy(accounts, ""))
|
||||||
|
router.Any("/*proxyPath", func(c *Context) {
|
||||||
|
called = true
|
||||||
|
c.String(http.StatusOK, c.MustGet(AuthProxyUserKey).(string))
|
||||||
|
})
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("GET", "/test", nil)
|
||||||
|
req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password")))
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.False(t, called)
|
||||||
|
assert.Equal(t, http.StatusProxyAuthRequired, w.Code)
|
||||||
|
assert.Equal(t, "Basic realm=\"Proxy Authorization Required\"", w.Header().Get("Proxy-Authenticate"))
|
||||||
|
}
|
||||||
|
|
|
@ -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{}
|
|
|
@ -3,7 +3,6 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !nomsgpack
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
|
||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
|
@ -22,6 +21,7 @@ const (
|
||||||
MIMEMSGPACK = "application/x-msgpack"
|
MIMEMSGPACK = "application/x-msgpack"
|
||||||
MIMEMSGPACK2 = "application/msgpack"
|
MIMEMSGPACK2 = "application/msgpack"
|
||||||
MIMEYAML = "application/x-yaml"
|
MIMEYAML = "application/x-yaml"
|
||||||
|
MIMEYAML2 = "application/yaml"
|
||||||
MIMETOML = "application/toml"
|
MIMETOML = "application/toml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ func Default(method, contentType string) Binding {
|
||||||
return ProtoBuf
|
return ProtoBuf
|
||||||
case MIMEMSGPACK, MIMEMSGPACK2:
|
case MIMEMSGPACK, MIMEMSGPACK2:
|
||||||
return MsgPack
|
return MsgPack
|
||||||
case MIMEYAML:
|
case MIMEYAML, MIMEYAML2:
|
||||||
return YAML
|
return YAML
|
||||||
case MIMETOML:
|
case MIMETOML:
|
||||||
return TOML
|
return TOML
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !nomsgpack
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
|
||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build nomsgpack
|
//go:build nomsgpack
|
||||||
// +build nomsgpack
|
|
||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
|
@ -20,6 +19,7 @@ const (
|
||||||
MIMEMultipartPOSTForm = "multipart/form-data"
|
MIMEMultipartPOSTForm = "multipart/form-data"
|
||||||
MIMEPROTOBUF = "application/x-protobuf"
|
MIMEPROTOBUF = "application/x-protobuf"
|
||||||
MIMEYAML = "application/x-yaml"
|
MIMEYAML = "application/x-yaml"
|
||||||
|
MIMEYAML2 = "application/yaml"
|
||||||
MIMETOML = "application/toml"
|
MIMETOML = "application/toml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ func Default(method, contentType string) Binding {
|
||||||
return XML
|
return XML
|
||||||
case MIMEPROTOBUF:
|
case MIMEPROTOBUF:
|
||||||
return ProtoBuf
|
return ProtoBuf
|
||||||
case MIMEYAML:
|
case MIMEYAML, MIMEYAML2:
|
||||||
return YAML
|
return YAML
|
||||||
case MIMEMultipartPOSTForm:
|
case MIMEMultipartPOSTForm:
|
||||||
return FormMultipart
|
return FormMultipart
|
||||||
|
|
|
@ -164,6 +164,8 @@ func TestBindingDefault(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, YAML, Default("POST", MIMEYAML))
|
assert.Equal(t, YAML, Default("POST", MIMEYAML))
|
||||||
assert.Equal(t, YAML, Default("PUT", MIMEYAML))
|
assert.Equal(t, YAML, Default("PUT", MIMEYAML))
|
||||||
|
assert.Equal(t, YAML, Default("POST", MIMEYAML2))
|
||||||
|
assert.Equal(t, YAML, Default("PUT", MIMEYAML2))
|
||||||
|
|
||||||
assert.Equal(t, TOML, Default("POST", MIMETOML))
|
assert.Equal(t, TOML, Default("POST", MIMETOML))
|
||||||
assert.Equal(t, TOML, Default("PUT", MIMETOML))
|
assert.Equal(t, TOML, Default("PUT", MIMETOML))
|
||||||
|
|
|
@ -54,7 +54,10 @@ func (v *defaultValidator) ValidateStruct(obj any) error {
|
||||||
value := reflect.ValueOf(obj)
|
value := reflect.ValueOf(obj)
|
||||||
switch value.Kind() {
|
switch value.Kind() {
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
|
if value.Elem().Kind() != reflect.Struct {
|
||||||
return v.ValidateStruct(value.Elem().Interface())
|
return v.ValidateStruct(value.Elem().Interface())
|
||||||
|
}
|
||||||
|
return v.validateStruct(obj)
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
return v.validateStruct(obj)
|
return v.validateStruct(obj)
|
||||||
case reflect.Slice, reflect.Array:
|
case reflect.Slice, reflect.Array:
|
||||||
|
|
|
@ -7,6 +7,7 @@ package binding
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"mime/multipart"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -235,10 +236,17 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel
|
||||||
switch value.Interface().(type) {
|
switch value.Interface().(type) {
|
||||||
case time.Time:
|
case time.Time:
|
||||||
return setTimeField(val, field, value)
|
return setTimeField(val, field, value)
|
||||||
|
case multipart.FileHeader:
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
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:
|
default:
|
||||||
return errUnknownType
|
return errUnknownType
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"mime/multipart"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -43,6 +44,7 @@ func TestMappingBaseTypes(t *testing.T) {
|
||||||
{"zero value", struct{ F uint }{}, "", uint(0)},
|
{"zero value", struct{ F uint }{}, "", uint(0)},
|
||||||
{"zero value", struct{ F bool }{}, "", false},
|
{"zero value", struct{ F bool }{}, "", false},
|
||||||
{"zero value", struct{ F float32 }{}, "", float32(0)},
|
{"zero value", struct{ F float32 }{}, "", float32(0)},
|
||||||
|
{"file value", struct{ F *multipart.FileHeader }{}, "", &multipart.FileHeader{}},
|
||||||
} {
|
} {
|
||||||
tp := reflect.TypeOf(tt.value)
|
tp := reflect.TypeOf(tt.value)
|
||||||
testName := tt.name + ":" + tp.Field(0).Type.String()
|
testName := tt.name + ":" + tp.Field(0).Type.String()
|
||||||
|
@ -269,6 +271,39 @@ func TestMappingStructField(t *testing.T) {
|
||||||
assert.Equal(t, 9, s.J.I)
|
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) {
|
func TestMappingMapField(t *testing.T) {
|
||||||
var s struct {
|
var s struct {
|
||||||
M map[string]int
|
M map[string]int
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
|
|
||||||
// EnableDecoderUseNumber is used to call the UseNumber method on the JSON
|
// EnableDecoderUseNumber is used to call the UseNumber method on the JSON
|
||||||
// Decoder instance. UseNumber causes the Decoder to unmarshal a number into an
|
// 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
|
var EnableDecoderUseNumber = false
|
||||||
|
|
||||||
// EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method
|
// EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !nomsgpack
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
|
||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !nomsgpack
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
|
||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,9 @@ func (queryBinding) Name() string {
|
||||||
return "query"
|
return "query"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (queryBinding) Bind(req *http.Request, obj any) error {
|
func (q queryBinding) Bind(req *http.Request, obj any) error {
|
||||||
values := req.URL.Query()
|
values := req.URL.Query()
|
||||||
if err := mapForm(obj, values); err != nil {
|
if err := mapFormByTag(obj, values, q.Name()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return validate(obj)
|
return validate(obj)
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQueryBinding(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Foo string `query:"foo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &http.Request{URL: &url.URL{RawQuery: "foo=BAR"}}
|
||||||
|
|
||||||
|
err := queryBinding{}.Bind(request, &s)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "BAR", s.Foo)
|
||||||
|
}
|
|
@ -192,6 +192,30 @@ func TestValidatePrimitives(t *testing.T) {
|
||||||
assert.Equal(t, "value", str)
|
assert.Equal(t, "value", str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type structModifyValidation struct {
|
||||||
|
Integer int
|
||||||
|
}
|
||||||
|
|
||||||
|
func toZero(sl validator.StructLevel) {
|
||||||
|
var s *structModifyValidation = sl.Top().Interface().(*structModifyValidation)
|
||||||
|
s.Integer = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateAndModifyStruct(t *testing.T) {
|
||||||
|
// This validates that pointers to structs are passed to the validator
|
||||||
|
// giving us the ability to modify the struct being validated.
|
||||||
|
engine, ok := Validator.Engine().(*validator.Validate)
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
engine.RegisterStructValidation(toZero, structModifyValidation{})
|
||||||
|
|
||||||
|
s := structModifyValidation{Integer: 1}
|
||||||
|
errs := validate(&s)
|
||||||
|
|
||||||
|
assert.Nil(t, errs)
|
||||||
|
assert.Equal(t, s, structModifyValidation{Integer: 0})
|
||||||
|
}
|
||||||
|
|
||||||
// structCustomValidation is a helper struct we use to check that
|
// structCustomValidation is a helper struct we use to check that
|
||||||
// custom validation can be registered on it.
|
// custom validation can be registered on it.
|
||||||
// The `notone` binding directive is for custom validation and registered later.
|
// The `notone` binding directive is for custom validation and registered later.
|
||||||
|
|
14
codecov.yml
14
codecov.yml
|
@ -1,5 +1,13 @@
|
||||||
coverage:
|
coverage:
|
||||||
notify:
|
require_ci_to_pass: true
|
||||||
gitter:
|
|
||||||
|
status:
|
||||||
|
project:
|
||||||
default:
|
default:
|
||||||
url: https://webhooks.gitter.im/e/d90dcdeeab2f1e357165
|
target: 99%
|
||||||
|
threshold: 99%
|
||||||
|
|
||||||
|
patch:
|
||||||
|
default:
|
||||||
|
target: 99%
|
||||||
|
threshold: 95%
|
59
context.go
59
context.go
|
@ -43,6 +43,10 @@ const BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
|
||||||
// ContextKey is the key that a Context returns itself for.
|
// ContextKey is the key that a Context returns itself for.
|
||||||
const ContextKey = "_gin-gonic/gin/contextkey"
|
const ContextKey = "_gin-gonic/gin/contextkey"
|
||||||
|
|
||||||
|
type ContextKeyType int
|
||||||
|
|
||||||
|
const ContextRequestKey ContextKeyType = 0
|
||||||
|
|
||||||
// abortIndex represents a typical value used in abort functions.
|
// abortIndex represents a typical value used in abort functions.
|
||||||
const abortIndex int8 = math.MaxInt8 >> 1
|
const abortIndex int8 = math.MaxInt8 >> 1
|
||||||
|
|
||||||
|
@ -113,20 +117,27 @@ func (c *Context) Copy() *Context {
|
||||||
cp := Context{
|
cp := Context{
|
||||||
writermem: c.writermem,
|
writermem: c.writermem,
|
||||||
Request: c.Request,
|
Request: c.Request,
|
||||||
Params: c.Params,
|
|
||||||
engine: c.engine,
|
engine: c.engine,
|
||||||
}
|
}
|
||||||
|
|
||||||
cp.writermem.ResponseWriter = nil
|
cp.writermem.ResponseWriter = nil
|
||||||
cp.Writer = &cp.writermem
|
cp.Writer = &cp.writermem
|
||||||
cp.index = abortIndex
|
cp.index = abortIndex
|
||||||
cp.handlers = nil
|
cp.handlers = nil
|
||||||
cp.Keys = map[string]any{}
|
cp.fullPath = c.fullPath
|
||||||
for k, v := range c.Keys {
|
|
||||||
|
cKeys := c.Keys
|
||||||
|
cp.Keys = make(map[string]any, len(cKeys))
|
||||||
|
c.mu.RLock()
|
||||||
|
for k, v := range cKeys {
|
||||||
cp.Keys[k] = v
|
cp.Keys[k] = v
|
||||||
}
|
}
|
||||||
paramCopy := make([]Param, len(cp.Params))
|
c.mu.RUnlock()
|
||||||
copy(paramCopy, cp.Params)
|
|
||||||
cp.Params = paramCopy
|
cParams := c.Params
|
||||||
|
cp.Params = make([]Param, len(cParams))
|
||||||
|
copy(cp.Params, cParams)
|
||||||
|
|
||||||
return &cp
|
return &cp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,7 +397,7 @@ func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string)
|
||||||
//
|
//
|
||||||
// router.GET("/user/:id", func(c *gin.Context) {
|
// router.GET("/user/:id", func(c *gin.Context) {
|
||||||
// // a GET request to /user/john
|
// // a GET request to /user/john
|
||||||
// id := c.Param("id") // id == "/john"
|
// id := c.Param("id") // id == "john"
|
||||||
// // a GET request to /user/john/
|
// // a GET request to /user/john/
|
||||||
// id := c.Param("id") // id == "/john/"
|
// id := c.Param("id") // id == "/john/"
|
||||||
// })
|
// })
|
||||||
|
@ -652,7 +663,7 @@ func (c *Context) BindYAML(obj any) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML).
|
// 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)
|
return c.MustBindWith(obj, binding.TOML)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -717,7 +728,7 @@ func (c *Context) ShouldBindYAML(obj any) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML).
|
// 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)
|
return c.ShouldBindWith(obj, binding.TOML)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -878,6 +889,9 @@ func (c *Context) GetHeader(key string) string {
|
||||||
|
|
||||||
// GetRawData returns stream data.
|
// GetRawData returns stream data.
|
||||||
func (c *Context) GetRawData() ([]byte, error) {
|
func (c *Context) GetRawData() ([]byte, error) {
|
||||||
|
if c.Request.Body == nil {
|
||||||
|
return nil, errors.New("cannot read nil body")
|
||||||
|
}
|
||||||
return io.ReadAll(c.Request.Body)
|
return io.ReadAll(c.Request.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1000,7 +1014,7 @@ func (c *Context) YAML(code int, obj any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TOML serializes the given struct as TOML into the response body.
|
// 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})
|
c.Render(code, render.TOML{Data: obj})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1057,11 +1071,17 @@ func (c *Context) FileFromFS(filepath string, fs http.FileSystem) {
|
||||||
http.FileServer(fs).ServeHTTP(c.Writer, c.Request)
|
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
|
// 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
|
// On the client side, the file will typically be downloaded with the given filename
|
||||||
func (c *Context) FileAttachment(filepath, filename string) {
|
func (c *Context) FileAttachment(filepath, filename string) {
|
||||||
if isASCII(filename) {
|
if isASCII(filename) {
|
||||||
c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+filename+`"`)
|
c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+escapeQuotes(filename)+`"`)
|
||||||
} else {
|
} else {
|
||||||
c.Writer.Header().Set("Content-Disposition", `attachment; filename*=UTF-8''`+url.QueryEscape(filename))
|
c.Writer.Header().Set("Content-Disposition", `attachment; filename*=UTF-8''`+url.QueryEscape(filename))
|
||||||
}
|
}
|
||||||
|
@ -1179,9 +1199,16 @@ func (c *Context) SetAccepted(formats ...string) {
|
||||||
/***** GOLANG.ORG/X/NET/CONTEXT *****/
|
/***** 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.
|
// Deadline returns that there is no deadline (ok==false) when c.Request has no Context.
|
||||||
func (c *Context) Deadline() (deadline time.Time, ok bool) {
|
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
|
||||||
}
|
}
|
||||||
return c.Request.Context().Deadline()
|
return c.Request.Context().Deadline()
|
||||||
|
@ -1189,7 +1216,7 @@ func (c *Context) Deadline() (deadline time.Time, ok bool) {
|
||||||
|
|
||||||
// Done returns nil (chan which will wait forever) when c.Request has no Context.
|
// Done returns nil (chan which will wait forever) when c.Request has no Context.
|
||||||
func (c *Context) Done() <-chan struct{} {
|
func (c *Context) Done() <-chan struct{} {
|
||||||
if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
|
if !c.hasRequestContext() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return c.Request.Context().Done()
|
return c.Request.Context().Done()
|
||||||
|
@ -1197,7 +1224,7 @@ func (c *Context) Done() <-chan struct{} {
|
||||||
|
|
||||||
// Err returns nil when c.Request has no Context.
|
// Err returns nil when c.Request has no Context.
|
||||||
func (c *Context) Err() error {
|
func (c *Context) Err() error {
|
||||||
if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
|
if !c.hasRequestContext() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return c.Request.Context().Err()
|
return c.Request.Context().Err()
|
||||||
|
@ -1207,7 +1234,7 @@ func (c *Context) Err() error {
|
||||||
// if no value is associated with key. Successive calls to Value with
|
// if no value is associated with key. Successive calls to Value with
|
||||||
// the same key returns the same result.
|
// the same key returns the same result.
|
||||||
func (c *Context) Value(key any) any {
|
func (c *Context) Value(key any) any {
|
||||||
if key == 0 {
|
if key == ContextRequestKey {
|
||||||
return c.Request
|
return c.Request
|
||||||
}
|
}
|
||||||
if key == ContextKey {
|
if key == ContextKey {
|
||||||
|
@ -1218,7 +1245,7 @@ func (c *Context) Value(key any) any {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
|
if !c.hasRequestContext() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return c.Request.Context().Value(key)
|
return c.Request.Context().Value(key)
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -2,8 +2,7 @@
|
||||||
// Use of this source code is governed by a MIT style
|
// Use of this source code is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !go1.17
|
//go:build !go1.19
|
||||||
// +build !go1.17
|
|
||||||
|
|
||||||
package gin
|
package gin
|
||||||
|
|
||||||
|
@ -17,15 +16,22 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestContextFormFileFailed16(t *testing.T) {
|
func TestContextFormFileFailed18(t *testing.T) {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
mw := multipart.NewWriter(buf)
|
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, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||||
c.Request.Header.Set("Content-Type", mw.FormDataContentType())
|
c.Request.Header.Set("Content-Type", mw.FormDataContentType())
|
||||||
c.engine.MaxMultipartMemory = 8 << 20
|
c.engine.MaxMultipartMemory = 8 << 20
|
||||||
|
assert.Panics(t, func() {
|
||||||
f, err := c.FormFile("file")
|
f, err := c.FormFile("file")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, f)
|
assert.Nil(t, f)
|
||||||
|
})
|
||||||
}
|
}
|
|
@ -3,7 +3,6 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build go1.19
|
//go:build go1.19
|
||||||
// +build go1.19
|
|
||||||
|
|
||||||
package gin
|
package gin
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build appengine
|
//go:build appengine
|
||||||
// +build appengine
|
|
||||||
|
|
||||||
package gin
|
package gin
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ var errTestRender = errors.New("TestRender")
|
||||||
// Unit tests TODO
|
// Unit tests TODO
|
||||||
// func (c *Context) File(filepath string) {
|
// func (c *Context) File(filepath string) {
|
||||||
// func (c *Context) Negotiate(code int, config Negotiate) {
|
// 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)
|
// test that information is not leaked when reusing Contexts (using the Pool)
|
||||||
|
|
||||||
func createMultipartRequest() *http.Request {
|
func createMultipartRequest() *http.Request {
|
||||||
|
@ -324,6 +324,7 @@ func TestContextCopy(t *testing.T) {
|
||||||
c.handlers = HandlersChain{func(c *Context) {}}
|
c.handlers = HandlersChain{func(c *Context) {}}
|
||||||
c.Params = Params{Param{Key: "foo", Value: "bar"}}
|
c.Params = Params{Param{Key: "foo", Value: "bar"}}
|
||||||
c.Set("foo", "bar")
|
c.Set("foo", "bar")
|
||||||
|
c.fullPath = "/hola"
|
||||||
|
|
||||||
cp := c.Copy()
|
cp := c.Copy()
|
||||||
assert.Nil(t, cp.handlers)
|
assert.Nil(t, cp.handlers)
|
||||||
|
@ -336,6 +337,7 @@ func TestContextCopy(t *testing.T) {
|
||||||
assert.Equal(t, cp.Params, c.Params)
|
assert.Equal(t, cp.Params, c.Params)
|
||||||
cp.Set("foo", "notBar")
|
cp.Set("foo", "notBar")
|
||||||
assert.False(t, cp.Keys["foo"] == c.Keys["foo"])
|
assert.False(t, cp.Keys["foo"] == c.Keys["foo"])
|
||||||
|
assert.Equal(t, cp.fullPath, c.fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextHandlerName(t *testing.T) {
|
func TestContextHandlerName(t *testing.T) {
|
||||||
|
@ -998,7 +1000,7 @@ func TestContextRenderFile(t *testing.T) {
|
||||||
c.File("./gin.go")
|
c.File("./gin.go")
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "func New() *Engine {")
|
assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {")
|
||||||
// Content-Type='text/plain; charset=utf-8' when go version <= 1.16,
|
// Content-Type='text/plain; charset=utf-8' when go version <= 1.16,
|
||||||
// else, Content-Type='text/x-go; charset=utf-8'
|
// else, Content-Type='text/x-go; charset=utf-8'
|
||||||
assert.NotEqual(t, "", w.Header().Get("Content-Type"))
|
assert.NotEqual(t, "", w.Header().Get("Content-Type"))
|
||||||
|
@ -1012,7 +1014,7 @@ func TestContextRenderFileFromFS(t *testing.T) {
|
||||||
c.FileFromFS("./gin.go", Dir(".", false))
|
c.FileFromFS("./gin.go", Dir(".", false))
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "func New() *Engine {")
|
assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {")
|
||||||
// Content-Type='text/plain; charset=utf-8' when go version <= 1.16,
|
// Content-Type='text/plain; charset=utf-8' when go version <= 1.16,
|
||||||
// else, Content-Type='text/x-go; charset=utf-8'
|
// else, Content-Type='text/x-go; charset=utf-8'
|
||||||
assert.NotEqual(t, "", w.Header().Get("Content-Type"))
|
assert.NotEqual(t, "", w.Header().Get("Content-Type"))
|
||||||
|
@ -1028,10 +1030,24 @@ func TestContextRenderAttachment(t *testing.T) {
|
||||||
c.FileAttachment("./gin.go", newFilename)
|
c.FileAttachment("./gin.go", newFilename)
|
||||||
|
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "func New() *Engine {")
|
assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {")
|
||||||
assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.Header().Get("Content-Disposition"))
|
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(opts ...OptionFunc) *Engine {")
|
||||||
|
assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", actualEscapedResponseFilename), w.Header().Get("Content-Disposition"))
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextRenderUTF8Attachment(t *testing.T) {
|
func TestContextRenderUTF8Attachment(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
|
@ -1041,12 +1057,12 @@ func TestContextRenderUTF8Attachment(t *testing.T) {
|
||||||
c.FileAttachment("./gin.go", newFilename)
|
c.FileAttachment("./gin.go", newFilename)
|
||||||
|
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "func New() *Engine {")
|
assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {")
|
||||||
assert.Equal(t, `attachment; filename*=UTF-8''`+url.QueryEscape(newFilename), w.Header().Get("Content-Disposition"))
|
assert.Equal(t, `attachment; filename*=UTF-8''`+url.QueryEscape(newFilename), w.Header().Get("Content-Disposition"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestContextRenderYAML tests that the response is serialized as YAML
|
// TestContextRenderYAML tests that the response is serialized as YAML
|
||||||
// and Content-Type is set to application/x-yaml
|
// and Content-Type is set to application/yaml
|
||||||
func TestContextRenderYAML(t *testing.T) {
|
func TestContextRenderYAML(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
|
@ -1055,7 +1071,7 @@ func TestContextRenderYAML(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, http.StatusCreated, w.Code)
|
assert.Equal(t, http.StatusCreated, w.Code)
|
||||||
assert.Equal(t, "foo: bar\n", w.Body.String())
|
assert.Equal(t, "foo: bar\n", w.Body.String())
|
||||||
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestContextRenderTOML tests that the response is serialized as TOML
|
// TestContextRenderTOML tests that the response is serialized as TOML
|
||||||
|
@ -1203,7 +1219,7 @@ func TestContextNegotiationWithYAML(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Equal(t, "foo: bar\n", w.Body.String())
|
assert.Equal(t, "foo: bar\n", w.Body.String())
|
||||||
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextNegotiationWithTOML(t *testing.T) {
|
func TestContextNegotiationWithTOML(t *testing.T) {
|
||||||
|
@ -1555,6 +1571,12 @@ func TestContextClientIP(t *testing.T) {
|
||||||
c.Request.Header.Del("CF-Connecting-IP")
|
c.Request.Header.Del("CF-Connecting-IP")
|
||||||
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||||
|
|
||||||
|
c.engine.TrustedPlatform = PlatformFlyIO
|
||||||
|
assert.Equal(t, "70.70.70.70", c.ClientIP())
|
||||||
|
|
||||||
|
c.Request.Header.Del("Fly-Client-IP")
|
||||||
|
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||||
|
|
||||||
c.engine.TrustedPlatform = ""
|
c.engine.TrustedPlatform = ""
|
||||||
|
|
||||||
// no port
|
// no port
|
||||||
|
@ -1567,6 +1589,7 @@ func resetContextForClientIPTests(c *Context) {
|
||||||
c.Request.Header.Set("X-Forwarded-For", " 20.20.20.20, 30.30.30.30")
|
c.Request.Header.Set("X-Forwarded-For", " 20.20.20.20, 30.30.30.30")
|
||||||
c.Request.Header.Set("X-Appengine-Remote-Addr", "50.50.50.50")
|
c.Request.Header.Set("X-Appengine-Remote-Addr", "50.50.50.50")
|
||||||
c.Request.Header.Set("CF-Connecting-IP", "60.60.60.60")
|
c.Request.Header.Set("CF-Connecting-IP", "60.60.60.60")
|
||||||
|
c.Request.Header.Set("Fly-Client-IP", "70.70.70.70")
|
||||||
c.Request.RemoteAddr = " 40.40.40.40:42123 "
|
c.Request.RemoteAddr = " 40.40.40.40:42123 "
|
||||||
c.engine.TrustedPlatform = ""
|
c.engine.TrustedPlatform = ""
|
||||||
c.engine.trustedCIDRs = defaultTrustedCIDRs
|
c.engine.trustedCIDRs = defaultTrustedCIDRs
|
||||||
|
@ -1983,7 +2006,7 @@ func TestContextGolangContext(t *testing.T) {
|
||||||
ti, ok := c.Deadline()
|
ti, ok := c.Deadline()
|
||||||
assert.Equal(t, ti, time.Time{})
|
assert.Equal(t, ti, time.Time{})
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
assert.Equal(t, c.Value(0), c.Request)
|
assert.Equal(t, c.Value(ContextRequestKey), c.Request)
|
||||||
assert.Equal(t, c.Value(ContextKey), c)
|
assert.Equal(t, c.Value(ContextKey), c)
|
||||||
assert.Nil(t, c.Value("foo"))
|
assert.Nil(t, c.Value("foo"))
|
||||||
|
|
||||||
|
@ -2183,6 +2206,24 @@ func TestRemoteIPFail(t *testing.T) {
|
||||||
assert.False(t, trust)
|
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) {
|
func TestContextWithFallbackDeadlineFromRequestContext(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
// enable ContextWithFallback feature flag
|
// enable ContextWithFallback feature flag
|
||||||
|
@ -2395,3 +2436,42 @@ func TestCreateTestContextWithRouteParams(t *testing.T) {
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Equal(t, "hello gin", w.Body.String())
|
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"))
|
||||||
|
}
|
||||||
|
|
18
debug.go
18
debug.go
|
@ -12,7 +12,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ginSupportMinGoVer = 16
|
const ginSupportMinGoVer = 18
|
||||||
|
|
||||||
// IsDebugging returns true if the framework is running in debug mode.
|
// IsDebugging returns true if the framework is running in debug mode.
|
||||||
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
||||||
|
@ -23,6 +23,9 @@ func IsDebugging() bool {
|
||||||
// DebugPrintRouteFunc indicates debug log output format.
|
// DebugPrintRouteFunc indicates debug log output format.
|
||||||
var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int)
|
var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int)
|
||||||
|
|
||||||
|
// DebugPrintFunc indicates debug log output format.
|
||||||
|
var DebugPrintFunc func(format string, values ...interface{})
|
||||||
|
|
||||||
func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) {
|
func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) {
|
||||||
if IsDebugging() {
|
if IsDebugging() {
|
||||||
nuHandlers := len(handlers)
|
nuHandlers := len(handlers)
|
||||||
|
@ -48,12 +51,19 @@ func debugPrintLoadTemplate(tmpl *template.Template) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func debugPrint(format string, values ...any) {
|
func debugPrint(format string, values ...any) {
|
||||||
if IsDebugging() {
|
if !IsDebugging() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if DebugPrintFunc != nil {
|
||||||
|
DebugPrintFunc(format, values...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !strings.HasSuffix(format, "\n") {
|
if !strings.HasSuffix(format, "\n") {
|
||||||
format += "\n"
|
format += "\n"
|
||||||
}
|
}
|
||||||
fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...)
|
fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMinVer(v string) (uint64, error) {
|
func getMinVer(v string) (uint64, error) {
|
||||||
|
@ -67,7 +77,7 @@ func getMinVer(v string) (uint64, error) {
|
||||||
|
|
||||||
func debugPrintWARNINGDefault() {
|
func debugPrintWARNINGDefault() {
|
||||||
if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer {
|
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+.
|
||||||
|
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import (
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
// func debugRoute(httpMethod, absolutePath string, handlers HandlersChain) {
|
// func debugRoute(httpMethod, absolutePath string, handlers HandlersChain) {
|
||||||
// func debugPrint(format string, values ...interface{}) {
|
// func debugPrint(format string, values ...any) {
|
||||||
|
|
||||||
func TestIsDebugging(t *testing.T) {
|
func TestIsDebugging(t *testing.T) {
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
|
@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
|
||||||
})
|
})
|
||||||
m, e := getMinVer(runtime.Version())
|
m, e := getMinVer(runtime.Version())
|
||||||
if e == nil && m < ginSupportMinGoVer {
|
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 {
|
} else {
|
||||||
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,10 @@ import (
|
||||||
|
|
||||||
// BindWith binds the passed struct pointer using the specified binding engine.
|
// BindWith binds the passed struct pointer using the specified binding engine.
|
||||||
// See the binding package.
|
// See the binding package.
|
||||||
|
//
|
||||||
|
// Deprecated: Use MustBindWith or ShouldBindWith.
|
||||||
func (c *Context) BindWith(obj any, b binding.Binding) error {
|
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
|
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
|
want HTTP 400 to be automatically returned if any error occur, or use
|
||||||
ShouldBindWith() if you need to manage the error.`)
|
ShouldBindWith() if you need to manage the error.`)
|
||||||
|
|
58
docs/doc.md
58
docs/doc.md
|
@ -426,7 +426,7 @@ func main() {
|
||||||
r.Use(gin.Logger())
|
r.Use(gin.Logger())
|
||||||
|
|
||||||
// Recovery middleware recovers from any panics and writes a 500 if there was one.
|
// 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 {
|
if err, ok := recovered.(string); ok {
|
||||||
c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err))
|
c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err))
|
||||||
}
|
}
|
||||||
|
@ -509,6 +509,44 @@ Sample Output
|
||||||
::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "
|
::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Skip logging
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
router := gin.New()
|
||||||
|
|
||||||
|
// skip logging for desired paths by setting SkipPaths in LoggerConfig
|
||||||
|
loggerConfig := gin.LoggerConfig{SkipPaths: []string{"/metrics"}}
|
||||||
|
|
||||||
|
// skip logging based on your logic by setting Skip func in LoggerConfig
|
||||||
|
loggerConfig.Skip = func(c *gin.Context) bool {
|
||||||
|
// as an example skip non server side errors
|
||||||
|
return c.Writer.Status() < http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.Use(gin.LoggerWithConfig(loggerConfig))
|
||||||
|
router.Use(gin.Recovery())
|
||||||
|
|
||||||
|
// skipped
|
||||||
|
router.GET("/metrics", func(c *gin.Context) {
|
||||||
|
c.Status(http.StatusNotImplemented)
|
||||||
|
})
|
||||||
|
|
||||||
|
// skipped
|
||||||
|
router.GET("/ping", func(c *gin.Context) {
|
||||||
|
c.String(http.StatusOK, "pong")
|
||||||
|
})
|
||||||
|
|
||||||
|
// not skipped
|
||||||
|
router.GET("/data", func(c *gin.Context) {
|
||||||
|
c.Status(http.StatusNotImplemented)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
### Controlling Log output coloring
|
### Controlling Log output coloring
|
||||||
|
|
||||||
By default, logs output on console should be colorized depending on the detected TTY.
|
By default, logs output on console should be colorized depending on the detected TTY.
|
||||||
|
@ -1036,7 +1074,7 @@ curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:
|
||||||
func main() {
|
func main() {
|
||||||
r := gin.Default()
|
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) {
|
r.GET("/someJSON", func(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
|
c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
|
||||||
})
|
})
|
||||||
|
@ -2001,7 +2039,7 @@ func (customerBinding) Name() string {
|
||||||
return "form"
|
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 {
|
if err := req.ParseForm(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -2016,7 +2054,7 @@ func (customerBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
return validate(obj)
|
return validate(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validate(obj interface{}) error {
|
func validate(obj any) error {
|
||||||
if binding.Validator == nil {
|
if binding.Validator == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -2216,10 +2254,16 @@ import (
|
||||||
func main() {
|
func main() {
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
// Use predefined header gin.PlatformXXX
|
// Use predefined header gin.PlatformXXX
|
||||||
|
// Google App Engine
|
||||||
router.TrustedPlatform = gin.PlatformGoogleAppEngine
|
router.TrustedPlatform = gin.PlatformGoogleAppEngine
|
||||||
// Or set your own trusted request header for another trusted proxy service
|
// Cloudflare
|
||||||
// Don't set it to any suspect request header, it's unsafe
|
router.TrustedPlatform = gin.PlatformCloudflare
|
||||||
router.TrustedPlatform = "X-CDN-IP"
|
// Fly.io
|
||||||
|
router.TrustedPlatform = gin.PlatformFlyIO
|
||||||
|
// Or, you can set your own trusted request header. But be sure your CDN
|
||||||
|
// prevents users from passing this header! For example, if your CDN puts
|
||||||
|
// the client IP in X-CDN-Client-IP:
|
||||||
|
router.TrustedPlatform = "X-CDN-Client-IP"
|
||||||
|
|
||||||
router.GET("/", func(c *gin.Context) {
|
router.GET("/", func(c *gin.Context) {
|
||||||
// If you set TrustedPlatform, ClientIP() will resolve the
|
// If you set TrustedPlatform, ClientIP() will resolve the
|
||||||
|
|
2
fs.go
2
fs.go
|
@ -39,7 +39,7 @@ func (fs onlyFilesFS) Open(name string) (http.File, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Readdir overrides the http.File default implementation.
|
// 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
|
// this disables directory listing
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
46
gin.go
46
gin.go
|
@ -11,6 +11,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
@ -40,9 +41,15 @@ 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.
|
// HandlerFunc defines the handler used by gin middleware as return value.
|
||||||
type HandlerFunc func(*Context)
|
type HandlerFunc func(*Context)
|
||||||
|
|
||||||
|
// OptionFunc defines the function to change the default configuration
|
||||||
|
type OptionFunc func(*Engine)
|
||||||
|
|
||||||
// HandlersChain defines a HandlerFunc slice.
|
// HandlersChain defines a HandlerFunc slice.
|
||||||
type HandlersChain []HandlerFunc
|
type HandlersChain []HandlerFunc
|
||||||
|
|
||||||
|
@ -73,6 +80,8 @@ const (
|
||||||
// PlatformCloudflare when using Cloudflare's CDN. Trust CF-Connecting-IP for determining
|
// PlatformCloudflare when using Cloudflare's CDN. Trust CF-Connecting-IP for determining
|
||||||
// the client's IP
|
// the client's IP
|
||||||
PlatformCloudflare = "CF-Connecting-IP"
|
PlatformCloudflare = "CF-Connecting-IP"
|
||||||
|
// PlatformFlyIO when running on Fly.io. Trust Fly-Client-IP for determining the client's IP
|
||||||
|
PlatformFlyIO = "Fly-Client-IP"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
||||||
|
@ -176,7 +185,7 @@ var _ IRouter = (*Engine)(nil)
|
||||||
// - ForwardedByClientIP: true
|
// - ForwardedByClientIP: true
|
||||||
// - UseRawPath: false
|
// - UseRawPath: false
|
||||||
// - UnescapePathValues: true
|
// - UnescapePathValues: true
|
||||||
func New() *Engine {
|
func New(opts ...OptionFunc) *Engine {
|
||||||
debugPrintWARNINGNew()
|
debugPrintWARNINGNew()
|
||||||
engine := &Engine{
|
engine := &Engine{
|
||||||
RouterGroup: RouterGroup{
|
RouterGroup: RouterGroup{
|
||||||
|
@ -205,15 +214,15 @@ func New() *Engine {
|
||||||
engine.pool.New = func() any {
|
engine.pool.New = func() any {
|
||||||
return engine.allocateContext(engine.maxParams)
|
return engine.allocateContext(engine.maxParams)
|
||||||
}
|
}
|
||||||
return engine
|
return engine.With(opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
|
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
|
||||||
func Default() *Engine {
|
func Default(opts ...OptionFunc) *Engine {
|
||||||
debugPrintWARNINGDefault()
|
debugPrintWARNINGDefault()
|
||||||
engine := New()
|
engine := New()
|
||||||
engine.Use(Logger(), Recovery())
|
engine.Use(Logger(), Recovery())
|
||||||
return engine
|
return engine.With(opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) Handler() http.Handler {
|
func (engine *Engine) Handler() http.Handler {
|
||||||
|
@ -307,6 +316,15 @@ func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
|
||||||
return engine
|
return engine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// With returns a new Engine instance with the provided options.
|
||||||
|
func (engine *Engine) With(opts ...OptionFunc) *Engine {
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(engine)
|
||||||
|
}
|
||||||
|
|
||||||
|
return engine
|
||||||
|
}
|
||||||
|
|
||||||
func (engine *Engine) rebuild404Handlers() {
|
func (engine *Engine) rebuild404Handlers() {
|
||||||
engine.allNoRoute = engine.combineHandlers(engine.noRoute)
|
engine.allNoRoute = engine.combineHandlers(engine.noRoute)
|
||||||
}
|
}
|
||||||
|
@ -330,7 +348,6 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
||||||
}
|
}
|
||||||
root.addRoute(path, handlers)
|
root.addRoute(path, handlers)
|
||||||
|
|
||||||
// Update maxParams
|
|
||||||
if paramsCount := countParams(path); paramsCount > engine.maxParams {
|
if paramsCount := countParams(path); paramsCount > engine.maxParams {
|
||||||
engine.maxParams = paramsCount
|
engine.maxParams = paramsCount
|
||||||
}
|
}
|
||||||
|
@ -511,7 +528,7 @@ func (engine *Engine) RunUnix(file string) (err error) {
|
||||||
|
|
||||||
if engine.isUnsafeTrustedProxies() {
|
if engine.isUnsafeTrustedProxies() {
|
||||||
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
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)
|
listener, err := net.Listen("unix", file)
|
||||||
|
@ -534,7 +551,7 @@ func (engine *Engine) RunFd(fd int) (err error) {
|
||||||
|
|
||||||
if engine.isUnsafeTrustedProxies() {
|
if engine.isUnsafeTrustedProxies() {
|
||||||
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
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))
|
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
|
||||||
|
@ -555,7 +572,7 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) {
|
||||||
|
|
||||||
if engine.isUnsafeTrustedProxies() {
|
if engine.isUnsafeTrustedProxies() {
|
||||||
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
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())
|
err = http.Serve(listener, engine.Handler())
|
||||||
|
@ -630,17 +647,25 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if engine.HandleMethodNotAllowed {
|
if engine.HandleMethodNotAllowed {
|
||||||
|
// According to RFC 7231 section 6.5.5, MUST generate an Allow header field in response
|
||||||
|
// containing a list of the target resource's currently supported methods.
|
||||||
|
allowed := make([]string, 0, len(t)-1)
|
||||||
for _, tree := range engine.trees {
|
for _, tree := range engine.trees {
|
||||||
if tree.method == httpMethod {
|
if tree.method == httpMethod {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
|
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
|
||||||
|
allowed = append(allowed, tree.method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(allowed) > 0 {
|
||||||
c.handlers = engine.allNoMethod
|
c.handlers = engine.allNoMethod
|
||||||
|
c.writermem.Header().Set("Allow", strings.Join(allowed, ", "))
|
||||||
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
c.handlers = engine.allNoRoute
|
c.handlers = engine.allNoRoute
|
||||||
serveError(c, http.StatusNotFound, default404Body)
|
serveError(c, http.StatusNotFound, default404Body)
|
||||||
}
|
}
|
||||||
|
@ -668,6 +693,9 @@ func redirectTrailingSlash(c *Context) {
|
||||||
req := c.Request
|
req := c.Request
|
||||||
p := req.URL.Path
|
p := req.URL.Path
|
||||||
if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." {
|
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
|
p = prefix + "/" + req.URL.Path
|
||||||
}
|
}
|
||||||
req.URL.Path = p + "/"
|
req.URL.Path = p + "/"
|
||||||
|
|
34
gin_test.go
34
gin_test.go
|
@ -696,3 +696,37 @@ func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo)
|
||||||
|
|
||||||
func handlerTest1(c *Context) {}
|
func handlerTest1(c *Context) {}
|
||||||
func handlerTest2(c *Context) {}
|
func handlerTest2(c *Context) {}
|
||||||
|
|
||||||
|
func TestNewOptionFunc(t *testing.T) {
|
||||||
|
var fc = func(e *Engine) {
|
||||||
|
e.GET("/test1", handlerTest1)
|
||||||
|
e.GET("/test2", handlerTest2)
|
||||||
|
|
||||||
|
e.Use(func(c *Context) {
|
||||||
|
c.Next()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
r := New(fc)
|
||||||
|
|
||||||
|
routes := r.Routes()
|
||||||
|
assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest1"})
|
||||||
|
assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest2"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithOptionFunc(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
|
||||||
|
r.With(func(e *Engine) {
|
||||||
|
e.GET("/test1", handlerTest1)
|
||||||
|
e.GET("/test2", handlerTest2)
|
||||||
|
|
||||||
|
e.Use(func(c *Context) {
|
||||||
|
c.Next()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
routes := r.Routes()
|
||||||
|
assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest1"})
|
||||||
|
assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest2"})
|
||||||
|
}
|
||||||
|
|
39
go.mod
39
go.mod
|
@ -1,36 +1,37 @@
|
||||||
module github.com/gin-gonic/gin
|
module github.com/gin-gonic/gin
|
||||||
|
|
||||||
go 1.18
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/sonic v1.7.1
|
github.com/bytedance/sonic v1.11.0
|
||||||
github.com/gin-contrib/sse v0.1.0
|
github.com/gin-contrib/sse v0.1.0
|
||||||
github.com/go-playground/validator/v10 v10.11.2
|
github.com/go-playground/validator/v10 v10.18.0
|
||||||
github.com/goccy/go-json v0.10.0
|
github.com/goccy/go-json v0.10.2
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/mattn/go-isatty v0.0.17
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/pelletier/go-toml/v2 v2.0.6
|
github.com/pelletier/go-toml/v2 v2.1.1
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/ugorji/go/codec v1.2.9
|
github.com/ugorji/go/codec v1.2.12
|
||||||
golang.org/x/net v0.5.0
|
golang.org/x/net v0.21.0
|
||||||
google.golang.org/protobuf v1.28.1
|
google.golang.org/protobuf v1.33.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.0.14 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect
|
golang.org/x/arch v0.7.0 // indirect
|
||||||
golang.org/x/crypto v0.5.0 // indirect
|
golang.org/x/crypto v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.4.0 // indirect
|
golang.org/x/sys v0.17.0 // indirect
|
||||||
golang.org/x/text v0.6.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
88
go.sum
88
go.sum
|
@ -1,13 +1,19 @@
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
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.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||||
github.com/bytedance/sonic v1.7.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
github.com/bytedance/sonic v1.11.0 h1:FwNNv6Vu4z2Onf1++LNzxB/QhitD8wuTdpZzMTGITWo=
|
||||||
|
github.com/bytedance/sonic v1.11.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
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/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/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
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/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=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
@ -15,69 +21,65 @@ 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/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 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
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.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
|
||||||
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
|
github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
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 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
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/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.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.14 h1:QRqdp6bb9M9S5yyKeYteXKuoKE4p0tGlra81fKOpWH8=
|
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.14/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
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.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/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.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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
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.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.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.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.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/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 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
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.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
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.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
||||||
golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
// Use of this source code is governed by a MIT style
|
// Use of this source code is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !go1.20
|
||||||
|
|
||||||
package bytesconv
|
package bytesconv
|
||||||
|
|
||||||
import (
|
import (
|
|
@ -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))
|
||||||
|
}
|
|
@ -3,7 +3,6 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build go_json
|
//go:build go_json
|
||||||
// +build go_json
|
|
||||||
|
|
||||||
package json
|
package json
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,6 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !jsoniter && !go_json && !(sonic && avx && (linux || windows || darwin) && amd64)
|
//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
|
package json
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build jsoniter
|
//go:build jsoniter
|
||||||
// +build jsoniter
|
|
||||||
|
|
||||||
package json
|
package json
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,6 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build sonic && avx && (linux || windows || darwin) && amd64
|
//go:build sonic && avx && (linux || windows || darwin) && amd64
|
||||||
// +build sonic
|
|
||||||
// +build avx
|
|
||||||
// +build linux windows darwin
|
|
||||||
// +build amd64
|
|
||||||
|
|
||||||
package json
|
package json
|
||||||
|
|
||||||
|
|
17
logger.go
17
logger.go
|
@ -47,8 +47,15 @@ type LoggerConfig struct {
|
||||||
// SkipPaths is an url path array which logs are not written.
|
// SkipPaths is an url path array which logs are not written.
|
||||||
// Optional.
|
// Optional.
|
||||||
SkipPaths []string
|
SkipPaths []string
|
||||||
|
|
||||||
|
// Skip is a Skipper that indicates which logs should not be written.
|
||||||
|
// Optional.
|
||||||
|
Skip Skipper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skipper is a function to skip logs based on provided Context
|
||||||
|
type Skipper func(c *Context) bool
|
||||||
|
|
||||||
// LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter
|
// LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter
|
||||||
type LogFormatter func(params LogFormatterParams) string
|
type LogFormatter func(params LogFormatterParams) string
|
||||||
|
|
||||||
|
@ -83,6 +90,8 @@ func (p *LogFormatterParams) StatusCodeColor() string {
|
||||||
code := p.StatusCode
|
code := p.StatusCode
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
case code >= http.StatusContinue && code < http.StatusOK:
|
||||||
|
return white
|
||||||
case code >= http.StatusOK && code < http.StatusMultipleChoices:
|
case code >= http.StatusOK && code < http.StatusMultipleChoices:
|
||||||
return green
|
return green
|
||||||
case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
|
case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
|
||||||
|
@ -239,8 +248,11 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
|
||||||
// Process request
|
// Process request
|
||||||
c.Next()
|
c.Next()
|
||||||
|
|
||||||
// Log only when path is not being skipped
|
// Log only when it is not being skipped
|
||||||
if _, ok := skip[path]; !ok {
|
if _, ok := skip[path]; ok || (conf.Skip != nil && conf.Skip(c)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
param := LogFormatterParams{
|
param := LogFormatterParams{
|
||||||
Request: c.Request,
|
Request: c.Request,
|
||||||
isTerm: isTerm,
|
isTerm: isTerm,
|
||||||
|
@ -266,5 +278,4 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
|
||||||
|
|
||||||
fmt.Fprint(out, formatter(param))
|
fmt.Fprint(out, formatter(param))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -310,6 +310,7 @@ func TestColorForStatus(t *testing.T) {
|
||||||
return p.StatusCodeColor()
|
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, green, colorForStatus(http.StatusOK), "2xx should be green")
|
||||||
assert.Equal(t, white, colorForStatus(http.StatusMovedPermanently), "3xx should be white")
|
assert.Equal(t, white, colorForStatus(http.StatusMovedPermanently), "3xx should be white")
|
||||||
assert.Equal(t, yellow, colorForStatus(http.StatusNotFound), "4xx should be yellow")
|
assert.Equal(t, yellow, colorForStatus(http.StatusNotFound), "4xx should be yellow")
|
||||||
|
@ -414,6 +415,26 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) {
|
||||||
assert.Contains(t, buffer.String(), "")
|
assert.Contains(t, buffer.String(), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoggerWithConfigSkipper(t *testing.T) {
|
||||||
|
buffer := new(strings.Builder)
|
||||||
|
router := New()
|
||||||
|
router.Use(LoggerWithConfig(LoggerConfig{
|
||||||
|
Output: buffer,
|
||||||
|
Skip: func(c *Context) bool {
|
||||||
|
return c.Writer.Status() == http.StatusNoContent
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
router.GET("/logged", func(c *Context) { c.Status(http.StatusOK) })
|
||||||
|
router.GET("/skipped", func(c *Context) { c.Status(http.StatusNoContent) })
|
||||||
|
|
||||||
|
PerformRequest(router, "GET", "/logged")
|
||||||
|
assert.Contains(t, buffer.String(), "200")
|
||||||
|
|
||||||
|
buffer.Reset()
|
||||||
|
PerformRequest(router, "GET", "/skipped")
|
||||||
|
assert.Contains(t, buffer.String(), "")
|
||||||
|
}
|
||||||
|
|
||||||
func TestDisableConsoleColor(t *testing.T) {
|
func TestDisableConsoleColor(t *testing.T) {
|
||||||
New()
|
New()
|
||||||
assert.Equal(t, autoColor, consoleColorMode)
|
assert.Equal(t, autoColor, consoleColorMode)
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -80,6 +81,10 @@ func TestPathCleanMallocs(t *testing.T) {
|
||||||
t.Skip("skipping malloc count in short mode")
|
t.Skip("skipping malloc count in short mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if runtime.GOMAXPROCS(0) > 1 {
|
||||||
|
t.Skip("skipping malloc count; GOMAXPROCS>1")
|
||||||
|
}
|
||||||
|
|
||||||
for _, test := range cleanTests {
|
for _, test := range cleanTests {
|
||||||
allocs := testing.AllocsPerRun(100, func() { cleanPath(test.result) })
|
allocs := testing.AllocsPerRun(100, func() { cleanPath(test.result) })
|
||||||
assert.EqualValues(t, allocs, 0)
|
assert.EqualValues(t, allocs, 0)
|
||||||
|
|
|
@ -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)
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ func function(pc uintptr) []byte {
|
||||||
if period := bytes.Index(name, dot); period >= 0 {
|
if period := bytes.Index(name, dot); period >= 0 {
|
||||||
name = name[period+1:]
|
name = name[period+1:]
|
||||||
}
|
}
|
||||||
name = bytes.Replace(name, centerDot, dot, -1)
|
name = bytes.ReplaceAll(name, centerDot, dot)
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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{}
|
|
|
@ -3,7 +3,6 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !nomsgpack
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
|
||||||
|
|
||||||
package render
|
package render
|
||||||
|
|
||||||
|
|
|
@ -15,22 +15,22 @@ type Render interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ Render = JSON{}
|
_ Render = (*JSON)(nil)
|
||||||
_ Render = IndentedJSON{}
|
_ Render = (*IndentedJSON)(nil)
|
||||||
_ Render = SecureJSON{}
|
_ Render = (*SecureJSON)(nil)
|
||||||
_ Render = JsonpJSON{}
|
_ Render = (*JsonpJSON)(nil)
|
||||||
_ Render = XML{}
|
_ Render = (*XML)(nil)
|
||||||
_ Render = String{}
|
_ Render = (*String)(nil)
|
||||||
_ Render = Redirect{}
|
_ Render = (*Redirect)(nil)
|
||||||
_ Render = Data{}
|
_ Render = (*Data)(nil)
|
||||||
_ Render = HTML{}
|
_ Render = (*HTML)(nil)
|
||||||
_ HTMLRender = HTMLDebug{}
|
_ HTMLRender = (*HTMLDebug)(nil)
|
||||||
_ HTMLRender = HTMLProduction{}
|
_ HTMLRender = (*HTMLProduction)(nil)
|
||||||
_ Render = YAML{}
|
_ Render = (*YAML)(nil)
|
||||||
_ Render = Reader{}
|
_ Render = (*Reader)(nil)
|
||||||
_ Render = AsciiJSON{}
|
_ Render = (*AsciiJSON)(nil)
|
||||||
_ Render = ProtoBuf{}
|
_ Render = (*ProtoBuf)(nil)
|
||||||
_ Render = TOML{}
|
_ Render = (*TOML)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeContentType(w http.ResponseWriter, value []string) {
|
func writeContentType(w http.ResponseWriter, value []string) {
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build !nomsgpack
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
|
||||||
|
|
||||||
package render
|
package render
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/internal/json"
|
||||||
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"google.golang.org/protobuf/proto"
|
"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"))
|
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) {
|
func TestRenderJsonpJSONError2(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
data := map[string]any{
|
data := map[string]any{
|
||||||
|
@ -234,12 +280,12 @@ b:
|
||||||
d: [3, 4]
|
d: [3, 4]
|
||||||
`
|
`
|
||||||
(YAML{data}).WriteContentType(w)
|
(YAML{data}).WriteContentType(w)
|
||||||
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
|
||||||
err := (YAML{data}).Render(w)
|
err := (YAML{data}).Render(w)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "|4-\n a : Easy!\n b:\n \tc: 2\n \td: [3, 4]\n \t\n", w.Body.String())
|
assert.Equal(t, "|4-\n a : Easy!\n b:\n \tc: 2\n \td: [3, 4]\n \t\n", w.Body.String())
|
||||||
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
type fail struct{}
|
type fail struct{}
|
||||||
|
@ -532,3 +578,16 @@ func TestRenderReaderNoContentLength(t *testing.T) {
|
||||||
assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition"))
|
assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition"))
|
||||||
assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id"))
|
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())
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ type YAML struct {
|
||||||
Data any
|
Data any
|
||||||
}
|
}
|
||||||
|
|
||||||
var yamlContentType = []string{"application/x-yaml; charset=utf-8"}
|
var yamlContentType = []string{"application/yaml; charset=utf-8"}
|
||||||
|
|
||||||
// Render (YAML) marshals the given interface object and writes data with custom ContentType.
|
// Render (YAML) marshals the given interface object and writes data with custom ContentType.
|
||||||
func (r YAML) Render(w http.ResponseWriter) error {
|
func (r YAML) Render(w http.ResponseWriter) error {
|
||||||
|
|
|
@ -156,3 +156,33 @@ func TestResponseWriterStatusCode(t *testing.T) {
|
||||||
// status must be 200 although we tried to change it
|
// status must be 200 although we tried to change it
|
||||||
assert.Equal(t, http.StatusOK, w.Status())
|
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")
|
||||||
|
}
|
||||||
|
|
113
routes_test.go
113
routes_test.go
|
@ -180,10 +180,58 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
|
||||||
|
|
||||||
w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"})
|
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, "/api/path2/", w.Header().Get("Location"))
|
||||||
assert.Equal(t, 301, w.Code)
|
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||||
|
|
||||||
w = PerformRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"})
|
w = PerformRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"})
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, http.StatusOK, 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, http.StatusMovedPermanently, 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, http.StatusMovedPermanently, 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, http.StatusMovedPermanently, 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, http.StatusMovedPermanently, 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, http.StatusMovedPermanently, 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, http.StatusMovedPermanently, 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, http.StatusMovedPermanently, 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, http.StatusMovedPermanently, 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, http.StatusMovedPermanently, 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, http.StatusMovedPermanently, 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, http.StatusMovedPermanently, 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, http.StatusMovedPermanently, w.Code)
|
||||||
|
|
||||||
router.RedirectTrailingSlash = false
|
router.RedirectTrailingSlash = false
|
||||||
|
|
||||||
|
@ -289,6 +337,45 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
|
||||||
assert.Equal(t, "/is/super/great", wild)
|
assert.Equal(t, "/is/super/great", wild)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRouteParamsNotEmpty tests that context parameters will be set
|
||||||
|
// even if a route with params/wildcards is registered after the context
|
||||||
|
// initialisation (which happened in a previous requests).
|
||||||
|
func TestRouteParamsNotEmpty(t *testing.T) {
|
||||||
|
name := ""
|
||||||
|
lastName := ""
|
||||||
|
wild := ""
|
||||||
|
router := New()
|
||||||
|
|
||||||
|
w := PerformRequest(router, http.MethodGet, "/test/john/smith/is/super/great")
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
|
|
||||||
|
router.GET("/test/:name/:last_name/*wild", func(c *Context) {
|
||||||
|
name = c.Params.ByName("name")
|
||||||
|
lastName = c.Params.ByName("last_name")
|
||||||
|
var ok bool
|
||||||
|
wild, ok = c.Params.Get("wild")
|
||||||
|
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, name, c.Param("name"))
|
||||||
|
assert.Equal(t, lastName, c.Param("last_name"))
|
||||||
|
|
||||||
|
assert.Empty(t, c.Param("wtf"))
|
||||||
|
assert.Empty(t, c.Params.ByName("wtf"))
|
||||||
|
|
||||||
|
wtf, ok := c.Params.Get("wtf")
|
||||||
|
assert.Empty(t, wtf)
|
||||||
|
assert.False(t, ok)
|
||||||
|
})
|
||||||
|
|
||||||
|
w = PerformRequest(router, http.MethodGet, "/test/john/smith/is/super/great")
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Equal(t, "john", name)
|
||||||
|
assert.Equal(t, "smith", lastName)
|
||||||
|
assert.Equal(t, "/is/super/great", wild)
|
||||||
|
}
|
||||||
|
|
||||||
// TestHandleStaticFile - ensure the static file handles properly
|
// TestHandleStaticFile - ensure the static file handles properly
|
||||||
func TestRouteStaticFile(t *testing.T) {
|
func TestRouteStaticFile(t *testing.T) {
|
||||||
// SETUP file
|
// SETUP file
|
||||||
|
@ -427,6 +514,18 @@ func TestRouteNotAllowedEnabled2(t *testing.T) {
|
||||||
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouteNotAllowedEnabled3(t *testing.T) {
|
||||||
|
router := New()
|
||||||
|
router.HandleMethodNotAllowed = true
|
||||||
|
router.GET("/path", func(c *Context) {})
|
||||||
|
router.POST("/path", func(c *Context) {})
|
||||||
|
w := PerformRequest(router, http.MethodPut, "/path")
|
||||||
|
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||||
|
allowed := w.Header().Get("Allow")
|
||||||
|
assert.Contains(t, allowed, "GET")
|
||||||
|
assert.Contains(t, allowed, "POST")
|
||||||
|
}
|
||||||
|
|
||||||
func TestRouteNotAllowedDisabled(t *testing.T) {
|
func TestRouteNotAllowedDisabled(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
router.HandleMethodNotAllowed = false
|
router.HandleMethodNotAllowed = false
|
||||||
|
@ -520,11 +619,11 @@ func TestRouterNotFound(t *testing.T) {
|
||||||
router = New()
|
router = New()
|
||||||
router.NoRoute(func(c *Context) {
|
router.NoRoute(func(c *Context) {
|
||||||
if c.Request.RequestURI == "/login" {
|
if c.Request.RequestURI == "/login" {
|
||||||
c.String(200, "login")
|
c.String(http.StatusOK, "login")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
router.GET("/logout", func(c *Context) {
|
router.GET("/logout", func(c *Context) {
|
||||||
c.String(200, "logout")
|
c.String(http.StatusOK, "logout")
|
||||||
})
|
})
|
||||||
w = PerformRequest(router, http.MethodGet, "/login")
|
w = PerformRequest(router, http.MethodGet, "/login")
|
||||||
assert.Equal(t, "login", w.Body.String())
|
assert.Equal(t, "login", w.Body.String())
|
||||||
|
@ -536,7 +635,7 @@ func TestRouterStaticFSNotFound(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/")))
|
router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/")))
|
||||||
router.NoRoute(func(c *Context) {
|
router.NoRoute(func(c *Context) {
|
||||||
c.String(404, "non existent")
|
c.String(http.StatusNotFound, "non existent")
|
||||||
})
|
})
|
||||||
|
|
||||||
w := PerformRequest(router, http.MethodGet, "/nonexistent")
|
w := PerformRequest(router, http.MethodGet, "/nonexistent")
|
||||||
|
@ -619,12 +718,12 @@ func TestRouteRawPathNoUnescape(t *testing.T) {
|
||||||
func TestRouteServeErrorWithWriteHeader(t *testing.T) {
|
func TestRouteServeErrorWithWriteHeader(t *testing.T) {
|
||||||
route := New()
|
route := New()
|
||||||
route.Use(func(c *Context) {
|
route.Use(func(c *Context) {
|
||||||
c.Status(421)
|
c.Status(http.StatusMisdirectedRequest)
|
||||||
c.Next()
|
c.Next()
|
||||||
})
|
})
|
||||||
|
|
||||||
w := PerformRequest(route, http.MethodGet, "/NotFound")
|
w := PerformRequest(route, http.MethodGet, "/NotFound")
|
||||||
assert.Equal(t, 421, w.Code)
|
assert.Equal(t, http.StatusMisdirectedRequest, w.Code)
|
||||||
assert.Equal(t, 0, w.Body.Len())
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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{}
|
|
43
tree.go
43
tree.go
|
@ -351,7 +351,10 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
||||||
pathSeg := strings.SplitN(n.children[0].path, "/", 2)[0]
|
pathSeg := ""
|
||||||
|
if len(n.children) != 0 {
|
||||||
|
pathSeg = strings.SplitN(n.children[0].path, "/", 2)[0]
|
||||||
|
}
|
||||||
panic("catch-all wildcard '" + path +
|
panic("catch-all wildcard '" + path +
|
||||||
"' in new path '" + fullPath +
|
"' in new path '" + fullPath +
|
||||||
"' conflicts with existing path segment '" + pathSeg +
|
"' conflicts with existing path segment '" + pathSeg +
|
||||||
|
@ -478,7 +481,7 @@ walk: // Outer loop for walking the tree
|
||||||
// We can recommend to redirect to the same URL without a
|
// We can recommend to redirect to the same URL without a
|
||||||
// trailing slash if a leaf exists for that path.
|
// trailing slash if a leaf exists for that path.
|
||||||
value.tsr = path == "/" && n.handlers != nil
|
value.tsr = path == "/" && n.handlers != nil
|
||||||
return
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle wildcard child, which is always at the end of the array
|
// Handle wildcard child, which is always at the end of the array
|
||||||
|
@ -497,7 +500,14 @@ walk: // Outer loop for walking the tree
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save param value
|
// Save param value
|
||||||
if params != nil && cap(*params) > 0 {
|
if params != nil {
|
||||||
|
// Preallocate capacity if necessary
|
||||||
|
if cap(*params) < int(globalParamsCount) {
|
||||||
|
newParams := make(Params, len(*params), globalParamsCount)
|
||||||
|
copy(newParams, *params)
|
||||||
|
*params = newParams
|
||||||
|
}
|
||||||
|
|
||||||
if value.params == nil {
|
if value.params == nil {
|
||||||
value.params = params
|
value.params = params
|
||||||
}
|
}
|
||||||
|
@ -526,12 +536,12 @@ walk: // Outer loop for walking the tree
|
||||||
|
|
||||||
// ... but we can't
|
// ... but we can't
|
||||||
value.tsr = len(path) == end+1
|
value.tsr = len(path) == end+1
|
||||||
return
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
if value.handlers = n.handlers; value.handlers != nil {
|
if value.handlers = n.handlers; value.handlers != nil {
|
||||||
value.fullPath = n.fullPath
|
value.fullPath = n.fullPath
|
||||||
return
|
return value
|
||||||
}
|
}
|
||||||
if len(n.children) == 1 {
|
if len(n.children) == 1 {
|
||||||
// No handle found. Check if a handle for this path + a
|
// No handle found. Check if a handle for this path + a
|
||||||
|
@ -539,11 +549,18 @@ walk: // Outer loop for walking the tree
|
||||||
n = n.children[0]
|
n = n.children[0]
|
||||||
value.tsr = (n.path == "/" && n.handlers != nil) || (n.path == "" && n.indices == "/")
|
value.tsr = (n.path == "/" && n.handlers != nil) || (n.path == "" && n.indices == "/")
|
||||||
}
|
}
|
||||||
return
|
return value
|
||||||
|
|
||||||
case catchAll:
|
case catchAll:
|
||||||
// Save param value
|
// Save param value
|
||||||
if params != nil {
|
if params != nil {
|
||||||
|
// Preallocate capacity if necessary
|
||||||
|
if cap(*params) < int(globalParamsCount) {
|
||||||
|
newParams := make(Params, len(*params), globalParamsCount)
|
||||||
|
copy(newParams, *params)
|
||||||
|
*params = newParams
|
||||||
|
}
|
||||||
|
|
||||||
if value.params == nil {
|
if value.params == nil {
|
||||||
value.params = params
|
value.params = params
|
||||||
}
|
}
|
||||||
|
@ -564,7 +581,7 @@ walk: // Outer loop for walking the tree
|
||||||
|
|
||||||
value.handlers = n.handlers
|
value.handlers = n.handlers
|
||||||
value.fullPath = n.fullPath
|
value.fullPath = n.fullPath
|
||||||
return
|
return value
|
||||||
|
|
||||||
default:
|
default:
|
||||||
panic("invalid node type")
|
panic("invalid node type")
|
||||||
|
@ -595,7 +612,7 @@ walk: // Outer loop for walking the tree
|
||||||
// Check if this node has a handle registered.
|
// Check if this node has a handle registered.
|
||||||
if value.handlers = n.handlers; value.handlers != nil {
|
if value.handlers = n.handlers; value.handlers != nil {
|
||||||
value.fullPath = n.fullPath
|
value.fullPath = n.fullPath
|
||||||
return
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is no handle for this route, but this route has a
|
// If there is no handle for this route, but this route has a
|
||||||
|
@ -603,12 +620,12 @@ walk: // Outer loop for walking the tree
|
||||||
// additional trailing slash
|
// additional trailing slash
|
||||||
if path == "/" && n.wildChild && n.nType != root {
|
if path == "/" && n.wildChild && n.nType != root {
|
||||||
value.tsr = true
|
value.tsr = true
|
||||||
return
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
if path == "/" && n.nType == static {
|
if path == "/" && n.nType == static {
|
||||||
value.tsr = true
|
value.tsr = true
|
||||||
return
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
// No handle found. Check if a handle for this path + a
|
// No handle found. Check if a handle for this path + a
|
||||||
|
@ -618,11 +635,11 @@ walk: // Outer loop for walking the tree
|
||||||
n = n.children[i]
|
n = n.children[i]
|
||||||
value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
|
value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
|
||||||
(n.nType == catchAll && n.children[0].handlers != nil)
|
(n.nType == catchAll && n.children[0].handlers != nil)
|
||||||
return
|
return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing found. We can recommend to redirect to the same URL with an
|
// Nothing found. We can recommend to redirect to the same URL with an
|
||||||
|
@ -648,7 +665,7 @@ walk: // Outer loop for walking the tree
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
36
tree_test.go
36
tree_test.go
|
@ -417,6 +417,8 @@ func TestTreeWildcardConflict(t *testing.T) {
|
||||||
{"/user_:name", false},
|
{"/user_:name", false},
|
||||||
{"/id:id", false},
|
{"/id:id", false},
|
||||||
{"/id/:id", false},
|
{"/id/:id", false},
|
||||||
|
{"/static/*file", false},
|
||||||
|
{"/static/", true},
|
||||||
}
|
}
|
||||||
testRoutes(t, routes)
|
testRoutes(t, routes)
|
||||||
}
|
}
|
||||||
|
@ -893,9 +895,9 @@ func TestTreeInvalidNodeType(t *testing.T) {
|
||||||
|
|
||||||
func TestTreeInvalidParamsType(t *testing.T) {
|
func TestTreeInvalidParamsType(t *testing.T) {
|
||||||
tree := &node{}
|
tree := &node{}
|
||||||
tree.wildChild = true
|
// add a child with wildcard
|
||||||
tree.children = append(tree.children, &node{})
|
route := "/:path"
|
||||||
tree.children[0].nType = 2
|
tree.addRoute(route, fakeHandler(route))
|
||||||
|
|
||||||
// set invalid Params type
|
// set invalid Params type
|
||||||
params := make(Params, 0)
|
params := make(Params, 0)
|
||||||
|
@ -904,6 +906,34 @@ func TestTreeInvalidParamsType(t *testing.T) {
|
||||||
tree.getValue("/test", ¶ms, getSkippedNodes(), false)
|
tree.getValue("/test", ¶ms, getSkippedNodes(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTreeExpandParamsCapacity(t *testing.T) {
|
||||||
|
data := []struct {
|
||||||
|
path string
|
||||||
|
}{
|
||||||
|
{"/:path"},
|
||||||
|
{"/*path"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range data {
|
||||||
|
tree := &node{}
|
||||||
|
tree.addRoute(item.path, fakeHandler(item.path))
|
||||||
|
params := make(Params, 0)
|
||||||
|
|
||||||
|
value := tree.getValue("/test", ¶ms, getSkippedNodes(), false)
|
||||||
|
|
||||||
|
if value.params == nil {
|
||||||
|
t.Errorf("Expected %s params to be set, but they weren't", item.path)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(*value.params) != 1 {
|
||||||
|
t.Errorf("Wrong number of %s params: got %d, want %d",
|
||||||
|
item.path, len(*value.params), 1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTreeWildcardConflictEx(t *testing.T) {
|
func TestTreeWildcardConflictEx(t *testing.T) {
|
||||||
conflicts := [...]struct {
|
conflicts := [...]struct {
|
||||||
route string
|
route string
|
||||||
|
|
2
utils.go
2
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
|
type H map[string]any
|
||||||
|
|
||||||
// MarshalXML allows type H to be used with xml.Marshal.
|
// MarshalXML allows type H to be used with xml.Marshal.
|
||||||
|
|
|
@ -5,4 +5,4 @@
|
||||||
package gin
|
package gin
|
||||||
|
|
||||||
// Version is the current gin framework's version.
|
// Version is the current gin framework's version.
|
||||||
const Version = "v1.8.2"
|
const Version = "v1.9.1"
|
||||||
|
|
Loading…
Reference in New Issue