Repository: gin-gonic/gin
Pull request #3081 (2022-03-15 09:42:42)

[GIN-001] - Add TOML bining for gin
- With pull requests:
  - Open your pull request against `master`
  - Your pull request should have no more than two commits, if not you should squash them.
  - It should pass all tests in the available continuous integration systems such as GitHub Actions.
  - You should add/modify tests to cover your proposed code changes.
  - If your pull request contains a new feature, please document it on the README.


This PR aims to add a TOML binding to the gin framework

Valentine-Mario: @appleboy can you please help review

efectn: I think you should use v2 of github.com/pelletier/go-toml. It's more performant
Valentine-Mario: > 

The v2 version is still in beta and might still have numerous bugs at the moment
efectn: > > 
> 
> The v2 version is still in beta and might still have numerous bugs at the moment

Hugo uses v2, also i think it doesn't have critical bugs.
https://github.com/gohugoio/hugo/commit/a3701e09313695d4a0f6fb0eb7844c1a4befc07a
Valentine-Mario: > > > 
> > 
> > 
> > The v2 version is still in beta and might still have numerous bugs at the moment
> 
> Hugo uses v2, also i think it doesn't have critical bugs. [gohugoio/hugo@a3701e0](https://github.com/gohugoio/hugo/commit/a3701e09313695d4a0f6fb0eb7844c1a4befc07a)

The TOML parser has been updated to v2. Please review. Thanks
Valentine-Mario: @appleboy fixed issues with `gofmt`
appleboy: Wait for @thinkerou approval.
Valentine-Mario: Resolved merge conflict and updated toml binding to use the type `any`
Valentine-Mario: > @Valentine-Mario sorry, please fix conflicts again, thanks!

Resolved @thinkerou 
Valentine-Mario: @thinkerou not sure why this test is failing, com planning about the TOML in the context being undefined
efectn: > @thinkerou not sure why this test is failing, com planning about the TOML in the context being undefined

Check https://github.com/gin-gonic/gin/blob/master/binding/binding_nomsgpack.go
codecov[bot]: # [Codecov](https://codecov.io/gh/gin-gonic/gin/pull/3081?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) Report
> Merging [#3081](https://codecov.io/gh/gin-gonic/gin/pull/3081?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) (447e325) into [master](https://codecov.io/gh/gin-gonic/gin/commit/c4580944ae8edba96809660ffbfdadc6caaefc2d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) (c458094) will **decrease** coverage by `0.81%`.
> The diff coverage is `50.00%`.

```diff
@@            Coverage Diff             @@
##           master    #3081      +/-   ##
==========================================
- Coverage   98.77%   97.96%   -0.82%     
==========================================
  Files          41       43       +2     
  Lines        3105     3141      +36     
==========================================
+ Hits         3067     3077      +10     
- Misses         26       50      +24     
- Partials       12       14       +2     
```

| Flag | Coverage Δ | |
|---|---|---|
| go-1.14 | `?` | |
| go-1.15 | `?` | |
| go-1.16 | `?` | |
| go-1.17 | `97.96% <50.00%> (-0.56%)` | :arrow_down: |
| go-1.18 | `97.93% <47.05%> (-0.59%)` | :arrow_down: |
| macos-latest | `?` | |
| nomsgpack | `97.93% <47.05%> (-0.83%)` | :arrow_down: |
| ubuntu-latest | `97.96% <50.00%> (-0.82%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/gin-gonic/gin/pull/3081?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | Coverage Δ | |
|---|---|---|
| [render/render.go](https://codecov.io/gh/gin-gonic/gin/pull/3081/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-cmVuZGVyL3JlbmRlci5nbw==) | `100.00% <ø> (ø)` | |
| [render/toml.go](https://codecov.io/gh/gin-gonic/gin/pull/3081/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-cmVuZGVyL3RvbWwuZ28=) | `0.00% <0.00%> (ø)` | |
| [context.go](https://codecov.io/gh/gin-gonic/gin/pull/3081/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-Y29udGV4dC5nbw==) | `96.22% <22.22%> (-1.69%)` | :arrow_down: |
| [binding/binding.go](https://codecov.io/gh/gin-gonic/gin/pull/3081/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-YmluZGluZy9iaW5kaW5nLmdv) | `100.00% <100.00%> (ø)` | |
| [binding/binding\_nomsgpack.go](https://codecov.io/gh/gin-gonic/gin/pull/3081/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-YmluZGluZy9iaW5kaW5nX25vbXNncGFjay5nbw==) | `100.00% <100.00%> (ø)` | |
| [binding/toml.go](https://codecov.io/gh/gin-gonic/gin/pull/3081/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-YmluZGluZy90b21sLmdv) | `100.00% <100.00%> (ø)` | |
| [debug.go](https://codecov.io/gh/gin-gonic/gin/pull/3081/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-ZGVidWcuZ28=) | `91.66% <0.00%> (-8.34%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/gin-gonic/gin/pull/3081?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/gin-gonic/gin/pull/3081?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic). Last update [c458094...447e325](https://codecov.io/gh/gin-gonic/gin/pull/3081?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic).


Patch:
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 4cbc455..e27022d 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -37,7 +37,7 @@ jobs:
 
       # Initializes the CodeQL tools for scanning.
       - name: Initialize CodeQL
-        uses: github/codeql-action/init@v1
+        uses: github/codeql-action/init@v2
         with:
           languages: ${{ matrix.language }}
           # 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
 
       - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@v1
+        uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml
index f67641a..e8ef30c 100644
--- a/.github/workflows/gin.yml
+++ b/.github/workflows/gin.yml
@@ -13,13 +13,13 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Setup go
-        uses: actions/setup-go@v2
+        uses: actions/setup-go@v3
         with:
           go-version: '^1.16'
       - name: Checkout repository
         uses: actions/checkout@v3
       - name: Setup golangci-lint
-        uses: golangci/golangci-lint-action@v3.1.0
+        uses: golangci/golangci-lint-action@v3.2.0
         with:
           version: v1.45.0
           args: --verbose
@@ -43,7 +43,7 @@ jobs:
       GOPROXY: https://proxy.golang.org
     steps:
       - name: Set up Go ${{ matrix.go }}
-        uses: actions/setup-go@v2
+        uses: actions/setup-go@v3
         with:
           go-version: ${{ matrix.go }}
 
@@ -65,7 +65,7 @@ jobs:
         run: make test
 
       - name: Upload coverage to Codecov
-        uses: codecov/codecov-action@v2
+        uses: codecov/codecov-action@v3
         with:
           flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}
   notification-gitter:
diff --git a/README.md b/README.md
index 4aa638d..6b4cabb 100644
--- a/README.md
+++ b/README.md
@@ -86,7 +86,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
 
 To install Gin package, you need to install Go and set your Go workspace first.
 
-1. The first need [Go](https://golang.org/) installed (**version 1.14+ is required**), then you can use the below Go command to install Gin.
+1. You first need [Go](https://golang.org/) installed (**version 1.14+ is required**), then you can use the below Go command to install Gin.
 
 ```sh
 $ go get -u github.com/gin-gonic/gin
@@ -658,7 +658,7 @@ func main() {
 
 ### Model binding and validation
 
-To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz).
+To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz).
 
 Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://godoc.org/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags).
 
@@ -666,10 +666,10 @@ Note that you need to set the corresponding binding tag on all fields you want t
 
 Also, Gin provides two sets of methods for binding:
 - **Type** - Must bind
-  - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`
+  - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`, `BindTOML`
   - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method.
 - **Type** - Should bind
-  - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`
+  - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`, `ShouldBindTOML`,
   - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately.
 
 When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`.
diff --git a/auth.go b/auth.go
index 4d8a6ce..482c499 100644
--- a/auth.go
+++ b/auth.go
@@ -31,7 +31,7 @@ func (a authPairs) searchCredential(authValue string) (string, bool) {
 		return "", false
 	}
 	for _, pair := range a {
-		if subtle.ConstantTimeCompare([]byte(pair.value), []byte(authValue)) == 1 {
+		if subtle.ConstantTimeCompare(bytesconv.StringToBytes(pair.value), bytesconv.StringToBytes(authValue)) == 1 {
 			return pair.user, true
 		}
 	}
diff --git a/binding/binding.go b/binding/binding.go
index 703a1cf..5051051 100644
--- a/binding/binding.go
+++ b/binding/binding.go
@@ -22,6 +22,7 @@ const (
 	MIMEMSGPACK           = "application/x-msgpack"
 	MIMEMSGPACK2          = "application/msgpack"
 	MIMEYAML              = "application/x-yaml"
+	MIMETOML              = "application/toml"
 )
 
 // Binding describes the interface which needs to be implemented for binding the
@@ -83,6 +84,7 @@ var (
 	YAML          = yamlBinding{}
 	Uri           = uriBinding{}
 	Header        = headerBinding{}
+	TOML          = tomlBinding{}
 )
 
 // Default returns the appropriate Binding instance based on the HTTP method
@@ -103,6 +105,8 @@ func Default(method, contentType string) Binding {
 		return MsgPack
 	case MIMEYAML:
 		return YAML
+	case MIMETOML:
+		return TOML
 	case MIMEMultipartPOSTForm:
 		return FormMultipart
 	default: // case MIMEPOSTForm:
diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go
index b381854..7f6a904 100644
--- a/binding/binding_nomsgpack.go
+++ b/binding/binding_nomsgpack.go
@@ -20,6 +20,7 @@ const (
 	MIMEMultipartPOSTForm = "multipart/form-data"
 	MIMEPROTOBUF          = "application/x-protobuf"
 	MIMEYAML              = "application/x-yaml"
+	MIMETOML              = "application/toml"
 )
 
 // Binding describes the interface which needs to be implemented for binding the
@@ -79,6 +80,7 @@ var (
 	YAML          = yamlBinding{}
 	Uri           = uriBinding{}
 	Header        = headerBinding{}
+	TOML          = tomlBinding{}
 )
 
 // Default returns the appropriate Binding instance based on the HTTP method
@@ -99,6 +101,8 @@ func Default(method, contentType string) Binding {
 		return YAML
 	case MIMEMultipartPOSTForm:
 		return FormMultipart
+	case MIMETOML:
+		return TOML
 	default: // case MIMEPOSTForm:
 		return Form
 	}
diff --git a/binding/toml.go b/binding/toml.go
new file mode 100644
index 0000000..5b9ad16
--- /dev/null
+++ b/binding/toml.go
@@ -0,0 +1,31 @@
+package binding
+
+import (
+	"bytes"
+	"io"
+	"net/http"
+
+	"github.com/pelletier/go-toml/v2"
+)
+
+type tomlBinding struct{}
+
+func (tomlBinding) Name() string {
+	return "toml"
+}
+
+func decodeToml(r io.Reader, obj any) error {
+	decoder := toml.NewDecoder(r)
+	if err := decoder.Decode(obj); err != nil {
+		return err
+	}
+	return decoder.Decode(obj)
+}
+
+func (tomlBinding) Bind(req *http.Request, obj any) error {
+	return decodeToml(req.Body, obj)
+}
+
+func (tomlBinding) BindBody(body []byte, obj any) error {
+	return decodeToml(bytes.NewReader(body), obj)
+}
diff --git a/context.go b/context.go
index faa4813..ecbd3da 100644
--- a/context.go
+++ b/context.go
@@ -34,11 +34,15 @@ const (
 	MIMEPOSTForm          = binding.MIMEPOSTForm
 	MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
 	MIMEYAML              = binding.MIMEYAML
+	MIMETOML              = binding.MIMETOML
 )
 
 // BodyBytesKey indicates a default body bytes key.
 const BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
 
+// ContextKey is the key that a Context returns itself for.
+const ContextKey = "_gin-gonic/gin/contextkey"
+
 // abortIndex represents a typical value used in abort functions.
 const abortIndex int8 = math.MaxInt8 >> 1
 
@@ -98,6 +102,7 @@ func (c *Context) reset() {
 	c.Accepted = nil
 	c.queryCache = nil
 	c.formCache = nil
+	c.sameSite = 0
 	*c.params = (*c.params)[:0]
 	*c.skippedNodes = (*c.skippedNodes)[:0]
 }
@@ -632,6 +637,11 @@ func (c *Context) BindYAML(obj any) error {
 	return c.MustBindWith(obj, binding.YAML)
 }
 
+// BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML).
+func (c *Context) BindTOML(obj interface{}) error {
+	return c.MustBindWith(obj, binding.TOML)
+}
+
 // BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
 func (c *Context) BindHeader(obj any) error {
 	return c.MustBindWith(obj, binding.Header)
@@ -690,6 +700,11 @@ func (c *Context) ShouldBindYAML(obj any) error {
 	return c.ShouldBindWith(obj, binding.YAML)
 }
 
+// ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML).
+func (c *Context) ShouldBindTOML(obj interface{}) error {
+	return c.ShouldBindWith(obj, binding.TOML)
+}
+
 // ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
 func (c *Context) ShouldBindHeader(obj any) error {
 	return c.ShouldBindWith(obj, binding.Header)
@@ -961,6 +976,11 @@ func (c *Context) YAML(code int, obj any) {
 	c.Render(code, render.YAML{Data: obj})
 }
 
+// TOML serializes the given struct as TOML into the response body.
+func (c *Context) TOML(code int, obj interface{}) {
+	c.Render(code, render.TOML{Data: obj})
+}
+
 // ProtoBuf serializes the given struct as ProtoBuf into the response body.
 func (c *Context) ProtoBuf(code int, obj any) {
 	c.Render(code, render.ProtoBuf{Data: obj})
@@ -1065,6 +1085,7 @@ type Negotiate struct {
 	XMLData  any
 	YAMLData any
 	Data     any
+	TOMLData any
 }
 
 // Negotiate calls different Render according to acceptable Accept format.
@@ -1086,6 +1107,10 @@ func (c *Context) Negotiate(code int, config Negotiate) {
 		data := chooseData(config.YAMLData, config.Data)
 		c.YAML(code, data)
 
+	case binding.MIMETOML:
+		data := chooseData(config.TOMLData, config.Data)
+		c.TOML(code, data)
+
 	default:
 		c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck
 	}
@@ -1162,6 +1187,9 @@ func (c *Context) Value(key any) any {
 	if key == 0 {
 		return c.Request
 	}
+	if key == ContextKey {
+		return c
+	}
 	if keyAsString, ok := key.(string); ok {
 		if val, exists := c.Get(keyAsString); exists {
 			return val
diff --git a/gin.go b/gin.go
index c2e95f2..3a831e5 100644
--- a/gin.go
+++ b/gin.go
@@ -195,7 +195,7 @@ func New() *Engine {
 		trees:                  make(methodTrees, 0, 9),
 		delims:                 render.Delims{Left: "{{", Right: "}}"},
 		secureJSONPrefix:       "while(1);",
-		trustedProxies:         []string{"0.0.0.0/0"},
+		trustedProxies:         []string{"0.0.0.0/0", "::/0"},
 		trustedCIDRs:           defaultTrustedCIDRs,
 	}
 	engine.RouterGroup.engine = engine
diff --git a/go.mod b/go.mod
index bfb462b..e519b36 100644
--- a/go.mod
+++ b/go.mod
@@ -5,9 +5,10 @@ go 1.18
 require (
 	github.com/gin-contrib/sse v0.1.0
 	github.com/go-playground/validator/v10 v10.10.0
-	github.com/goccy/go-json v0.9.6
+	github.com/goccy/go-json v0.9.7
 	github.com/json-iterator/go v1.1.12
 	github.com/mattn/go-isatty v0.0.14
+	github.com/pelletier/go-toml/v2 v2.0.0-beta.6
 	github.com/stretchr/testify v1.7.1
 	github.com/ugorji/go/codec v1.2.7
 	golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
diff --git a/go.sum b/go.sum
index 983b50a..d7555f9 100644
--- a/go.sum
+++ b/go.sum
@@ -12,8 +12,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j
 github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
 github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
 github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
-github.com/goccy/go-json v0.9.6 h1:5/4CtRQdtsX0sal8fdVhTaiMN01Ri8BExZZ8iRmHQ6E=
-github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
+github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -36,6 +36,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/pelletier/go-toml/v2 v2.0.0-beta.6 h1:JFNqj2afbbhCqTiyN16D7Tudc/aaDzE2FBDk+VlBQnE=
+github.com/pelletier/go-toml/v2 v2.0.0-beta.6/go.mod h1:ke6xncR3W76Ba8xnVxkrZG0js6Rd2BsQEAYrfgJ6eQA=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -46,8 +48,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
 github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
 github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
 github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
diff --git a/mode.go b/mode.go
index 1fb994b..4e7d734 100644
--- a/mode.go
+++ b/mode.go
@@ -5,6 +5,7 @@
 package gin
 
 import (
+	"flag"
 	"io"
 	"os"
 
@@ -54,7 +55,11 @@ func init() {
 // SetMode sets gin mode according to input string.
 func SetMode(value string) {
 	if value == "" {
-		value = DebugMode
+		if flag.Lookup("test.v") != nil {
+			value = TestMode
+		} else {
+			value = DebugMode
+		}
 	}
 
 	switch value {
diff --git a/render/render.go b/render/render.go
index bcd568b..1fa4806 100644
--- a/render/render.go
+++ b/render/render.go
@@ -30,6 +30,7 @@ var (
 	_ Render     = Reader{}
 	_ Render     = AsciiJSON{}
 	_ Render     = ProtoBuf{}
+	_ Render     = TOML{}
 )
 
 func writeContentType(w http.ResponseWriter, value []string) {
diff --git a/render/toml.go b/render/toml.go
new file mode 100644
index 0000000..1192c78
--- /dev/null
+++ b/render/toml.go
@@ -0,0 +1,32 @@
+package render
+
+import (
+	"net/http"
+
+	"github.com/pelletier/go-toml/v2"
+)
+
+// TOML contains the given interface object.
+type TOML struct {
+	Data any
+}
+
+var TOMLContentType = []string{"application/toml; charset=utf-8"}
+
+// Render (TOML) marshals the given interface object and writes data with custom ContentType.
+func (r TOML) Render(w http.ResponseWriter) error {
+	r.WriteContentType(w)
+
+	bytes, err := toml.Marshal(r.Data)
+	if err != nil {
+		return err
+	}
+
+	_, err = w.Write(bytes)
+	return err
+}
+
+// WriteContentType (TOML) writes TOML ContentType for response.
+func (r TOML) WriteContentType(w http.ResponseWriter) {
+	writeContentType(w, TOMLContentType)
+}


Test patch:
diff --git a/binding/binding_test.go b/binding/binding_test.go
index b1edbf5..f08e173 100644
--- a/binding/binding_test.go
+++ b/binding/binding_test.go
@@ -165,6 +165,9 @@ func TestBindingDefault(t *testing.T) {
 
 	assert.Equal(t, YAML, Default("POST", MIMEYAML))
 	assert.Equal(t, YAML, Default("PUT", MIMEYAML))
+
+	assert.Equal(t, TOML, Default("POST", MIMETOML))
+	assert.Equal(t, TOML, Default("PUT", MIMETOML))
 }
 
 func TestBindingJSONNilBody(t *testing.T) {
@@ -454,6 +457,20 @@ func TestBindingXMLFail(t *testing.T) {
 		"<map><foo>bar<foo></map>", "<map><bar>foo</bar></map>")
 }
 
+func TestBindingTOML(t *testing.T) {
+	testBodyBinding(t,
+		TOML, "toml",
+		"/", "/",
+		`foo="bar"`, `bar="foo"`)
+}
+
+func TestBindingTOMLFail(t *testing.T) {
+	testBodyBindingFail(t,
+		TOML, "toml",
+		"/", "/",
+		`foo=\n"bar"`, `bar="foo"`)
+}
+
 func TestBindingYAML(t *testing.T) {
 	testBodyBinding(t,
 		YAML, "yaml",
@@ -1339,10 +1356,10 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body
 	err := b.Bind(req, &obj)
 	assert.Error(t, err)
 
-	invalid_obj := FooStruct{}
+	invalidobj := FooStruct{}
 	req.Body = ioutil.NopCloser(strings.NewReader(`{"msg":"hello"}`))
 	req.Header.Add("Content-Type", MIMEPROTOBUF)
-	err = b.Bind(req, &invalid_obj)
+	err = b.Bind(req, &invalidobj)
 	assert.Error(t, err)
 	assert.Equal(t, err.Error(), "obj is not ProtoMessage")
 
diff --git a/binding/toml_test.go b/binding/toml_test.go
new file mode 100644
index 0000000..2bc0e3a
--- /dev/null
+++ b/binding/toml_test.go
@@ -0,0 +1,22 @@
+// 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.
+
+package binding
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestTOMLBindingBindBody(t *testing.T) {
+	var s struct {
+		Foo string `toml:"foo"`
+	}
+	tomlBody := `foo="FOO"`
+	err := tomlBinding{}.BindBody([]byte(tomlBody), &s)
+	require.NoError(t, err)
+	assert.Equal(t, "FOO", s.Foo)
+}
diff --git a/context_go17_test.go b/context_go17_test.go
new file mode 100644
index 0000000..eca089c
--- /dev/null
+++ b/context_go17_test.go
@@ -0,0 +1,50 @@
+// +build go1.7
+
+package gin
+
+import (
+	"bytes"
+	"net/http"
+	"net/http/httptest"
+	"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 TestInterceptedHeader(t *testing.T) {
+	w := httptest.NewRecorder()
+	c, r := CreateTestContext(w)
+
+	r.Use(func(c *Context) {
+		i := interceptedWriter{
+			ResponseWriter: c.Writer,
+			b:              bytes.NewBuffer(nil),
+		}
+		c.Writer = i
+		c.Next()
+		c.Header("X-Test", "overridden")
+		c.Writer = i.ResponseWriter
+	})
+	r.GET("/", func(c *Context) {
+		c.Header("X-Test", "original")
+		c.Header("X-Test-2", "present")
+		c.String(http.StatusOK, "hello world")
+	})
+	c.Request = httptest.NewRequest("GET", "/", nil)
+	r.HandleContext(c)
+	// Result() has headers frozen when WriteHeaderNow() has been called
+	// Compared to this time, this is when the response headers will be flushed
+	// As response is flushed on c.String, the Header cannot be set by the first
+	// middleware. Assert this
+	assert.Equal(t, "", w.Result().Header.Get("X-Test"))
+	assert.Equal(t, "present", w.Result().Header.Get("X-Test-2"))
+}
diff --git a/context_test.go b/context_test.go
index fb46e67..7e4d0b3 100644
--- a/context_test.go
+++ b/context_test.go
@@ -1773,6 +1773,23 @@ func TestContextShouldBindWithYAML(t *testing.T) {
 	assert.Equal(t, 0, w.Body.Len())
 }
 
+func TestContextShouldBindWithTOML(t *testing.T) {
+	w := httptest.NewRecorder()
+	c, _ := CreateTestContext(w)
+
+	c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo='bar'\nbar= 'foo'"))
+	c.Request.Header.Add("Content-Type", MIMETOML) // set fake content-type
+
+	var obj struct {
+		Foo string `toml:"foo"`
+		Bar string `toml:"bar"`
+	}
+	assert.NoError(t, c.ShouldBindTOML(&obj))
+	assert.Equal(t, "foo", obj.Bar)
+	assert.Equal(t, "bar", obj.Foo)
+	assert.Equal(t, 0, w.Body.Len())
+}
+
 func TestContextBadAutoShouldBind(t *testing.T) {
 	w := httptest.NewRecorder()
 	c, _ := CreateTestContext(w)
@@ -1880,6 +1897,7 @@ func TestContextGolangContext(t *testing.T) {
 	assert.Equal(t, ti, time.Time{})
 	assert.False(t, ok)
 	assert.Equal(t, c.Value(0), c.Request)
+	assert.Equal(t, c.Value(ContextKey), c)
 	assert.Nil(t, c.Value("foo"))
 
 	c.Set("foo", "bar")
diff --git a/gin_integration_test.go b/gin_integration_test.go
index 8c22e7b..0dfa903 100644
--- a/gin_integration_test.go
+++ b/gin_integration_test.go
@@ -15,6 +15,7 @@ import (
 	"net/http/httptest"
 	"os"
 	"path/filepath"
+	"runtime"
 	"sync"
 	"testing"
 	"time"
@@ -281,7 +282,16 @@ func TestFileDescriptor(t *testing.T) {
 	listener, err := net.ListenTCP("tcp", addr)
 	assert.NoError(t, err)
 	socketFile, err := listener.File()
-	assert.NoError(t, err)
+	if isWindows() {
+		// not supported by windows, it is unimplemented now
+		assert.Error(t, err)
+	} else {
+		assert.NoError(t, err)
+	}
+
+	if socketFile == nil {
+		return
+	}
 
 	go func() {
 		router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
@@ -547,3 +557,7 @@ func TestTreeRunDynamicRouting(t *testing.T) {
 	testRequest(t, ts.URL+"/addr/dd/aa", "404 Not Found")
 	testRequest(t, ts.URL+"/something/secondthing/121", "404 Not Found")
 }
+
+func isWindows() bool {
+	return runtime.GOOS == "windows"
+}
diff --git a/gin_test.go b/gin_test.go
index 0c11134..a438062 100644
--- a/gin_test.go
+++ b/gin_test.go
@@ -202,7 +202,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
 	}
 
 	resp, _ := ioutil.ReadAll(res.Body)
-	assert.Equal(t, "Date: 2017/07/01\n", string(resp))
+	assert.Equal(t, "Date: 2017/07/01", string(resp))
 }
 
 func init() {
@@ -320,7 +320,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
 	}
 
 	resp, _ := ioutil.ReadAll(res.Body)
-	assert.Equal(t, "Date: 2017/07/01\n", string(resp))
+	assert.Equal(t, "Date: 2017/07/01", string(resp))
 }
 
 func TestAddRoute(t *testing.T) {
diff --git a/githubapi_test.go b/githubapi_test.go
index e74bddd..5fe65a4 100644
--- a/githubapi_test.go
+++ b/githubapi_test.go
@@ -296,8 +296,8 @@ func TestShouldBindUri(t *testing.T) {
 	router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
 		var person Person
 		assert.NoError(t, c.ShouldBindUri(&person))
-		assert.True(t, "" != person.Name)
-		assert.True(t, "" != person.ID)
+		assert.True(t, person.Name != "")
+		assert.True(t, person.ID != "")
 		c.String(http.StatusOK, "ShouldBindUri test OK")
 	})
 
@@ -318,8 +318,8 @@ func TestBindUri(t *testing.T) {
 	router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
 		var person Person
 		assert.NoError(t, c.BindUri(&person))
-		assert.True(t, "" != person.Name)
-		assert.True(t, "" != person.ID)
+		assert.True(t, person.Name != "")
+		assert.True(t, person.ID != "")
 		c.String(http.StatusOK, "BindUri test OK")
 	})
 
diff --git a/logger_test.go b/logger_test.go
index da1b654..b704998 100644
--- a/logger_test.go
+++ b/logger_test.go
@@ -208,6 +208,7 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
 		// set dummy ClientIP
 		c.Request.Header.Set("X-Forwarded-For", "20.20.20.20")
 		gotKeys = c.Keys
+		time.Sleep(time.Millisecond)
 	})
 	PerformRequest(router, "GET", "/example?a=100")
 
diff --git a/mode_test.go b/mode_test.go
index 1b5fb2f..6fd9a13 100644
--- a/mode_test.go
+++ b/mode_test.go
@@ -5,6 +5,7 @@
 package gin
 
 import (
+	"flag"
 	"os"
 	"testing"
 
@@ -21,9 +22,16 @@ func TestSetMode(t *testing.T) {
 	assert.Equal(t, TestMode, Mode())
 	os.Unsetenv(EnvGinMode)
 
+	SetMode("")
+	assert.Equal(t, testCode, ginMode)
+	assert.Equal(t, TestMode, Mode())
+
+	tmp := flag.CommandLine
+	flag.CommandLine = flag.NewFlagSet("", flag.ContinueOnError)
 	SetMode("")
 	assert.Equal(t, debugCode, ginMode)
 	assert.Equal(t, DebugMode, Mode())
+	flag.CommandLine = tmp
 
 	SetMode(DebugMode)
 	assert.Equal(t, debugCode, ginMode)
diff --git a/testdata/template/raw.tmpl b/testdata/template/raw.tmpl
index 8bc7570..f3f530a 100644
--- a/testdata/template/raw.tmpl
+++ b/testdata/template/raw.tmpl
@@ -1,1 +1,1 @@
-Date: {[{.now | formatAsDate}]}
+Date: {[{.now | formatAsDate}]}
\ No newline at end of file