@@ -14,9 +14,11 @@ import (
1414 "encoding/xml"
1515 "errors"
1616 "fmt"
17+ "io"
1718 "net"
1819 "net/http"
1920 "net/http/httputil"
21+ "os"
2022 "reflect"
2123 "strconv"
2224 "strings"
@@ -142,7 +144,7 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa
142144 // Default: false
143145 StrictRouting bool `json:"strict_routing"`
144146
145- // When set to true, enables case sensitive routing.
147+ // When set to true, enables case- sensitive routing.
146148 // E.g. "/FoO" and "/foo" are treated as different routes.
147149 // By default this is disabled and both "/FoO" and "/foo" will execute the same handler.
148150 //
@@ -330,29 +332,31 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa
330332 // For example, the Host HTTP header is usually used to return the requested host.
331333 // But when you’re behind a proxy, the actual host may be stored in an X-Forwarded-Host header.
332334 //
333- // If you are behind a proxy, you should enable TrustedProxyCheck to prevent header spoofing.
334- // If you enable EnableTrustedProxyCheck and leave TrustedProxies empty Fiber will skip
335+ // If you are behind a proxy, you should enable TrustProxy to prevent header spoofing.
336+ // If you enable TrustProxy and do not provide a TrustProxyConfig, Fiber will skip
335337 // all headers that could be spoofed.
336- // If request ip in TrustedProxies whitelist then:
338+ // If the request IP is in the TrustProxyConfig.Proxies allowlist, then:
337339 // 1. c.Scheme() get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header
338340 // 2. c.IP() get value from ProxyHeader header.
339341 // 3. c.Host() and c.Hostname() get value from X-Forwarded-Host header
340- // But if request ip NOT in Trusted Proxies whitelist then:
341- // 1. c.Scheme() WON't get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header,
342- // will return https in case when tls connection is handled by the app, of http otherwise
342+ // But if the request IP is NOT in the TrustProxyConfig. Proxies allowlist, then:
343+ // 1. c.Scheme() WON'T get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header,
344+ // will return https when a TLS connection is handled by the app, or http otherwise.
343345 // 2. c.IP() WON'T get value from ProxyHeader header, will return RemoteIP() from fasthttp context
344346 // 3. c.Host() and c.Hostname() WON'T get value from X-Forwarded-Host header, fasthttp.Request.URI().Host()
345347 // will be used to get the hostname.
346348 //
349+ // To automatically trust all loopback, link-local, or private IP addresses,
350+ // without manually adding them to the TrustProxyConfig.Proxies allowlist,
351+ // you can set TrustProxyConfig.Loopback, TrustProxyConfig.LinkLocal, or TrustProxyConfig.Private to true.
352+ //
347353 // Default: false
348- EnableTrustedProxyCheck bool `json:"enable_trusted_proxy_check "`
354+ TrustProxy bool `json:"trust_proxy "`
349355
350- // Read EnableTrustedProxyCheck doc.
356+ // Read TrustProxy doc.
351357 //
352- // Default: []string
353- TrustedProxies []string `json:"trusted_proxies"`
354- trustedProxiesMap map [string ]struct {}
355- trustedProxyRanges []* net.IPNet
358+ // Default: DefaultTrustProxyConfig
359+ TrustProxyConfig TrustProxyConfig `json:"trust_proxy_config"`
356360
357361 // If set to true, c.IP() and c.IPs() will validate IP addresses before returning them.
358362 // Also, c.IP() will return only the first valid IP rather than just the raw header
@@ -372,7 +376,7 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa
372376 // Default: nil
373377 StructValidator StructValidator
374378
375- // RequestMethods provides customizibility for HTTP methods. You can add/remove methods as you wish.
379+ // RequestMethods provides customizability for HTTP methods. You can add/remove methods as you wish.
376380 //
377381 // Optional. Default: DefaultMethods
378382 RequestMethods []string
@@ -385,6 +389,36 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa
385389 EnableSplittingOnParsers bool `json:"enable_splitting_on_parsers"`
386390}
387391
392+ // Default TrustProxyConfig
393+ var DefaultTrustProxyConfig = TrustProxyConfig {}
394+
395+ // TrustProxyConfig is a struct for configuring trusted proxies if Config.TrustProxy is true.
396+ type TrustProxyConfig struct {
397+ ips map [string ]struct {}
398+
399+ // Proxies is a list of trusted proxy IP addresses or CIDR ranges.
400+ //
401+ // Default: []string
402+ Proxies []string `json:"proxies"`
403+
404+ ranges []* net.IPNet
405+
406+ // LinkLocal enables trusting all link-local IP ranges (e.g., 169.254.0.0/16, fe80::/10).
407+ //
408+ // Default: false
409+ LinkLocal bool `json:"link_local"`
410+
411+ // Loopback enables trusting all loopback IP ranges (e.g., 127.0.0.0/8, ::1/128).
412+ //
413+ // Default: false
414+ Loopback bool `json:"loopback"`
415+
416+ // Private enables trusting all private IP ranges (e.g., 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7).
417+ //
418+ // Default: false
419+ Private bool `json:"private"`
420+ }
421+
388422// RouteMessage is some message need to be print when server starts
389423type RouteMessage struct {
390424 name string
@@ -510,8 +544,8 @@ func New(config ...Config) *App {
510544 app .config .RequestMethods = DefaultMethods
511545 }
512546
513- app .config .trustedProxiesMap = make (map [string ]struct {}, len (app .config .TrustedProxies ))
514- for _ , ipAddress := range app .config .TrustedProxies {
547+ app .config .TrustProxyConfig . ips = make (map [string ]struct {}, len (app .config .TrustProxyConfig . Proxies ))
548+ for _ , ipAddress := range app .config .TrustProxyConfig . Proxies {
515549 app .handleTrustedProxy (ipAddress )
516550 }
517551
@@ -529,17 +563,22 @@ func New(config ...Config) *App {
529563 return app
530564}
531565
532- // Adds an ip address to trustedProxyRanges or trustedProxiesMap based on whether it is an IP range or not
566+ // Adds an ip address to TrustProxyConfig.ranges or TrustProxyConfig.ips based on whether it is an IP range or not
533567func (app * App ) handleTrustedProxy (ipAddress string ) {
534568 if strings .Contains (ipAddress , "/" ) {
535569 _ , ipNet , err := net .ParseCIDR (ipAddress )
536570 if err != nil {
537571 log .Warnf ("IP range %q could not be parsed: %v" , ipAddress , err )
538572 } else {
539- app .config .trustedProxyRanges = append (app .config .trustedProxyRanges , ipNet )
573+ app .config .TrustProxyConfig . ranges = append (app .config .TrustProxyConfig . ranges , ipNet )
540574 }
541575 } else {
542- app .config .trustedProxiesMap [ipAddress ] = struct {}{}
576+ ip := net .ParseIP (ipAddress )
577+ if ip == nil {
578+ log .Warnf ("IP address %q could not be parsed" , ipAddress )
579+ } else {
580+ app .config .TrustProxyConfig .ips [ipAddress ] = struct {}{}
581+ }
543582 }
544583}
545584
@@ -864,13 +903,33 @@ func (app *App) Hooks() *Hooks {
864903 return app .hooks
865904}
866905
906+ // TestConfig is a struct holding Test settings
907+ type TestConfig struct {
908+ // Timeout defines the maximum duration a
909+ // test can run before timing out.
910+ // Default: time.Second
911+ Timeout time.Duration
912+
913+ // FailOnTimeout specifies whether the test
914+ // should return a timeout error if the HTTP response
915+ // exceeds the Timeout duration.
916+ // Default: true
917+ FailOnTimeout bool
918+ }
919+
867920// Test is used for internal debugging by passing a *http.Request.
868- // Timeout is optional and defaults to 1s, -1 will disable it completely.
869- func (app * App ) Test (req * http.Request , timeout ... time.Duration ) (* http.Response , error ) {
870- // Set timeout
871- to := 1 * time .Second
872- if len (timeout ) > 0 {
873- to = timeout [0 ]
921+ // Config is optional and defaults to a 1s error on timeout,
922+ // 0 timeout will disable it completely.
923+ func (app * App ) Test (req * http.Request , config ... TestConfig ) (* http.Response , error ) {
924+ // Default config
925+ cfg := TestConfig {
926+ Timeout : time .Second ,
927+ FailOnTimeout : true ,
928+ }
929+
930+ // Override config if provided
931+ if len (config ) > 0 {
932+ cfg = config [0 ]
874933 }
875934
876935 // Add Content-Length if not provided with body
@@ -909,12 +968,15 @@ func (app *App) Test(req *http.Request, timeout ...time.Duration) (*http.Respons
909968 }()
910969
911970 // Wait for callback
912- if to >= 0 {
971+ if cfg . Timeout > 0 {
913972 // With timeout
914973 select {
915974 case err = <- channel :
916- case <- time .After (to ):
917- return nil , fmt .Errorf ("test: timeout error after %s" , to )
975+ case <- time .After (cfg .Timeout ):
976+ conn .Close () //nolint:errcheck, revive // It is fine to ignore the error here
977+ if cfg .FailOnTimeout {
978+ return nil , os .ErrDeadlineExceeded
979+ }
918980 }
919981 } else {
920982 // Without timeout
@@ -932,6 +994,9 @@ func (app *App) Test(req *http.Request, timeout ...time.Duration) (*http.Respons
932994 // Convert raw http response to *http.Response
933995 res , err := http .ReadResponse (buffer , req )
934996 if err != nil {
997+ if errors .Is (err , io .ErrUnexpectedEOF ) {
998+ return nil , errors .New ("test: got empty response" )
999+ }
9351000 return nil , fmt .Errorf ("failed to read response: %w" , err )
9361001 }
9371002
0 commit comments