Repository: gin-gonic/gin
Pull request #3839 (2024-02-07 02:09:26)

chore(IP): add TrustedPlatform constant for Fly.io.
Also add some more detail to the docs for how to use TrustedPlatform.

https://fly.io/docs/reference/runtime-environment/#fly-client-ip

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/gin-gonic/gin/pull/3839?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`3dc1cd6`)](https://app.codecov.io/gh/gin-gonic/gin/commit/3dc1cd6572b4e3a0cd170a15debe546c2c72294f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) 99.21% compared to head [(`b1ef88d`)](https://app.codecov.io/gh/gin-gonic/gin/pull/3839?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) 99.36%.
> Report is 2 commits behind head on master.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #3839      +/-   ##
==========================================
+ Coverage   99.21%   99.36%   +0.14%     
==========================================
  Files          42       43       +1     
  Lines        3182     2674     -508     
==========================================
- Hits         3157     2657     -500     
+ Misses         17        9       -8     
  Partials        8        8              
```

| [Flag](https://app.codecov.io/gh/gin-gonic/gin/pull/3839/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | Coverage Δ | |
|---|---|---|
| [](https://app.codecov.io/gh/gin-gonic/gin/pull/3839/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `?` | |
| [-tags go_json](https://app.codecov.io/gh/gin-gonic/gin/pull/3839/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `99.35% <100.00%> (?)` | |
| [-tags nomsgpack](https://app.codecov.io/gh/gin-gonic/gin/pull/3839/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `99.35% <100.00%> (?)` | |
| [go-1.18](https://app.codecov.io/gh/gin-gonic/gin/pull/3839/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `99.27% <100.00%> (+0.15%)` | :arrow_up: |
| [go-1.19](https://app.codecov.io/gh/gin-gonic/gin/pull/3839/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `99.36% <100.00%> (+0.14%)` | :arrow_up: |
| [go-1.20](https://app.codecov.io/gh/gin-gonic/gin/pull/3839/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `99.35% <100.00%> (+0.13%)` | :arrow_up: |
| [go-1.21](https://app.codecov.io/gh/gin-gonic/gin/pull/3839/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `?` | |
| [macos-latest](https://app.codecov.io/gh/gin-gonic/gin/pull/3839/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `?` | |
| [ubuntu-latest](https://app.codecov.io/gh/gin-gonic/gin/pull/3839/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `99.36% <100.00%> (+0.14%)` | :arrow_up: |

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.


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/gin-gonic/gin/pull/3839?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic).


Patch:
diff --git a/binding/default_validator.go b/binding/default_validator.go
index e216b85..ac43d7c 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/docs/doc.md b/docs/doc.md
index 520d105..df006e8 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 b6ac535..24a9864 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.


Test patch:
diff --git a/binding/validate_test.go b/binding/validate_test.go
index 801bd9b..1fc15ff 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 70d4758..88165c0 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