Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add methods c.BindCookie(obj) from cookie and c.BindRequest(obj) from http request #2812

Closed
wants to merge 2 commits into from

Conversation

tangx
Copy link

@tangx tangx commented Aug 4, 2021

  • 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.

  1. split Bind method into 2 parts
    1. extract binding code as BindOnly() / BindBodyOnly() method, in this way, users can combine their own binding logic.
    1. Bind() / BindBody() method handle error from BindOnly / BindBodyOnly, and do validation stuff.
type Binding interface {
	Name() string
	Bind(*http.Request, interface{}) error
	BindOnly(*http.Request, interface{}) error
}

type BindingBody interface {
	Binding
	BindBody([]byte, interface{}) error
	BindBodyOnly([]byte, interface{}) error
}
  1. add `c.BindCookie(obj) method, which can bind values from request cookie into object.

  2. add c.BindRequest(obj) method, which combines uri, form , cookie, header, body handlers. With this method, now can bind all wanted values at once.

// ...
type Params struct {
	Name          string `uri:"name"`
	Age           int    `form:"age,default=18"`
	Money         int32  `form:"money" binding:"required"`
	Authorization string `cookie:"Authorization"`
	UserAgent     string `header:"User-Agent"`
	Data          struct {
		Replicas *int32 `json:"replicas" yaml:"replicas" xml:"replicas" form:"replicas"`
	} `body:"body"`
}


// ### POST test by RestClient of vscode extenstion
// POST http://127.0.0.1:9881/hello/zhangsan?money=1000
// Content-Type: application/json
// Accept-Language: en-GB,en-US;q=0.8,en;q=0.6,zh-CN;q=0.4
// Cookie: Authorization=auth123123;
//
// {
//     "replicas":5
// }


@quentinvernot
Copy link

Hello,

Not a gin maintainer, just a random passer-by here.

First off, I love this API, I implemented a similar thing as a wrapper but it makes sense for gin to expose this directly. However, there is a small but fairly dangerous issue here: gin (and encoding/json) will try to guess the name of query fields on untagged struct fields, meaning if you have a struct like this:

type MyRequest struct {
  MyField string `query:"my_field"`
}

... all the binders will try to fill this field in the order they are called. Not just BindQuery, BindHeaders and BindJSON will try it too.

If you send this request:

curl -X POST https://myserver/my-handler?my_field=query_value -d '{"my_field": "json_value"}'

... and bind with BindRequest, the MyField field of the MyRequest object will contain "json_value".

This also applies to fields that are not tagged at all, which can be dangerous security-wise. If you have, say, a UserID field that you would expect a middleware to fill, it could be filled by BindRequest and possible overwrite a value, easy way in for an attacker.

There are ways around this:

  • A lot of reflect acrobatics to create temporary sub structs for each tag, you can then pass the sub-struct to the relevant BindX function and then merge everything back into the expected struct to aggregate the results.
  • Add an option in gin to stop it from guessing field names, but beware that this wouldn't apply to json or xml because that's how the go standard library works.
  • Document the issue

Echo has a similar binding function and does 2 and 3.

Just my 2 cents.

Cheers!

@tangx
Copy link
Author

tangx commented Aug 5, 2021

gin (and encoding/json) will try to guess the name of query fields on untagged struct fields, meaning if you have a struct like this

this is cause by using a same function mapping to bind values, in mapping has a recursive mapping behavior in struct field.


@quentinvernot

another problem, gin has no query tag. c.BindQuery api is using tag form, so it's hard to divided them in body struct.
to handler this situation, I add my own binding query method in requestBinding object to support query tag , and it can only be used in c.ShouldBindRequest api.

by the way, it will be panic if body struct contains any of query, uri, cookie, header

try this

go mod edit -replace github.com/gin-gonic/gin=github.com/tangx/gin@v1.7.2-dev.requestbinding-alpha.1

example

package main

import (
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	r.POST("/hello/:name", handler)
	r.Run(":8881")
}

type MyRequest struct {
	MyField string `query:"my_field"`
	Name    string `uri:"name"`
	Data    struct {
		Name string `json:"name"`
	} `body:""`
}

func handler(c *gin.Context) {
	params := &MyRequest{}
	err := c.ShouldBindRequest(params)

	if err != nil {
		c.JSON(200, err)
		return
	}

	c.JSON(200, params)
}

…o request body struct. (#1)

* fix: bug uri, query, header, cookie can bind value into param body struct
@long2ice
Copy link

That's what I want

@long2ice
Copy link

Why there is Data struct?

@long2ice
Copy link

I update your branch like this and work fine, any other points?
image

@tangx
Copy link
Author

tangx commented Aug 18, 2021

Why there is Data struct?

more info in https://github.com/tangx/gin#bind-request

  1. Data sturct is used for binding datas from resp.Body only.
  2. Data struct can be any name , but must be with the body:"" tag.
  3. there is only one resp.Body in request, so it can have only one body tag in your Params' instance
// ### POST test by RestClient of vscode extenstion
// POST http://127.0.0.1:9881/hello/zhangsan?money=1000
// Content-Type: application/json
// Accept-Language: en-GB,en-US;q=0.8,en;q=0.6,zh-CN;q=0.4
// Cookie: Authorization=auth123123;
//
// {
//     "replicas":5
// }

@tangx
Copy link
Author

tangx commented Aug 19, 2021

try to use this to bind request

https://github.com/tangx/ginbinder

@tangx tangx closed this Aug 19, 2021
@sourcec0de sourcec0de mentioned this pull request Nov 12, 2022
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants