diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index b36e1010..3fe007f1 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -15,24 +15,28 @@ jobs: lint: runs-on: ubuntu-latest steps: - - name: Setup go + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "^1.18" - - name: Checkout repository - uses: actions/checkout@v4 + go-version-file: "go.mod" + check-latest: true - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v3.7.0 + uses: golangci/golangci-lint-action@v4 with: - version: v1.55.2 + version: v1.56.2 args: --verbose test: needs: lint strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ["1.18", "1.19", "1.20", "1.21"] - test-tags: ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json"] + go: ["1.18", "1.19", "1.20", "1.21", "1.22"] + test-tags: + ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"] include: - os: ubuntu-latest go-build: ~/.cache/go-build @@ -69,10 +73,10 @@ jobs: run: make test - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} - name: Format - if: matrix.go-version == '1.21.x' + if: matrix.go-version == '1.22.x' run: diff -u <(echo -n) <(gofmt -d .) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index cbd5d418..8ae11823 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -16,13 +16,11 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - - name: Set up Go + - name: Set up Go uses: actions/setup-go@v5 with: go-version: "^1" - - - name: Run GoReleaser + - name: Run GoReleaser uses: goreleaser/goreleaser-action@v5 with: # either 'goreleaser' (default) or 'goreleaser-pro' diff --git a/binding/default_validator.go b/binding/default_validator.go index e216b854..ac43d7cc 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -54,7 +54,10 @@ func (v *defaultValidator) ValidateStruct(obj any) error { value := reflect.ValueOf(obj) switch value.Kind() { case reflect.Ptr: - return v.ValidateStruct(value.Elem().Interface()) + if value.Elem().Kind() != reflect.Struct { + return v.ValidateStruct(value.Elem().Interface()) + } + return v.validateStruct(obj) case reflect.Struct: return v.validateStruct(obj) case reflect.Slice, reflect.Array: diff --git a/binding/validate_test.go b/binding/validate_test.go index 801bd9b7..1fc15ff0 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -192,6 +192,30 @@ func TestValidatePrimitives(t *testing.T) { 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 // custom validation can be registered on it. // The `notone` binding directive is for custom validation and registered later. diff --git a/context_test.go b/context_test.go index 70d47583..88165c07 100644 --- a/context_test.go +++ b/context_test.go @@ -1569,6 +1569,12 @@ func TestContextClientIP(t *testing.T) { c.Request.Header.Del("CF-Connecting-IP") 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 = "" // no port @@ -1581,6 +1587,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-Appengine-Remote-Addr", "50.50.50.50") 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.engine.TrustedPlatform = "" c.engine.trustedCIDRs = defaultTrustedCIDRs diff --git a/docs/doc.md b/docs/doc.md index 520d105c..df006e87 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -2214,10 +2214,16 @@ import ( func main() { router := gin.Default() // Use predefined header gin.PlatformXXX + // Google App Engine router.TrustedPlatform = gin.PlatformGoogleAppEngine - // Or set your own trusted request header for another trusted proxy service - // Don't set it to any suspect request header, it's unsafe - router.TrustedPlatform = "X-CDN-IP" + // Cloudflare + router.TrustedPlatform = gin.PlatformCloudflare + // 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) { // If you set TrustedPlatform, ClientIP() will resolve the diff --git a/gin.go b/gin.go index 5a605cf1..24a9864a 100644 --- a/gin.go +++ b/gin.go @@ -77,6 +77,8 @@ const ( // PlatformCloudflare when using Cloudflare's CDN. Trust CF-Connecting-IP for determining // the client's 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. @@ -633,17 +635,25 @@ func (engine *Engine) handleHTTPRequest(c *Context) { } 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 { if tree.method == httpMethod { continue } if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil { - c.handlers = engine.allNoMethod - serveError(c, http.StatusMethodNotAllowed, default405Body) - return + allowed = append(allowed, tree.method) } } + if len(allowed) > 0 { + c.handlers = engine.allNoMethod + c.writermem.Header().Set("Allow", strings.Join(allowed, ", ")) + serveError(c, http.StatusMethodNotAllowed, default405Body) + return + } } + c.handlers = engine.allNoRoute serveError(c, http.StatusNotFound, default404Body) } diff --git a/go.mod b/go.mod index 0b60c5d7..fbbce7c0 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,16 @@ module github.com/gin-gonic/gin go 1.20 require ( - github.com/bytedance/sonic v1.10.2 + github.com/bytedance/sonic v1.11.0 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.17.0 + github.com/go-playground/validator/v10 v10.18.0 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/pelletier/go-toml/v2 v2.1.1 github.com/stretchr/testify v1.8.4 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.20.0 + golang.org/x/net v0.21.0 google.golang.org/protobuf v1.32.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -25,13 +25,13 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect - github.com/leodido/go-urn v1.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.7.0 // indirect - golang.org/x/crypto v0.18.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index e360d9d2..ce6c7fe7 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= -github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +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-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -21,8 +21,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= -github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U= +github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= @@ -33,8 +33,8 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/leodido/go-urn v1.3.0 h1:jX8FDLfW4ThVXctBNZ+3cIWnCSnrACDV73r76dy0aQQ= -github.com/leodido/go-urn v1.3.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -63,14 +63,14 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= diff --git a/routes_test.go b/routes_test.go index 7a51f817..a0ff695f 100644 --- a/routes_test.go +++ b/routes_test.go @@ -514,6 +514,18 @@ func TestRouteNotAllowedEnabled2(t *testing.T) { 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) { router := New() router.HandleMethodNotAllowed = false