Skip to content

Commit 7ff246f

Browse files
authored
Merge pull request #4513 from gofiber/fix-empty-query-url-semantics-issue
🐛 bug: preserve proxy empty-query URL semantics
2 parents d1e48af + 7fc08e9 commit 7ff246f

4 files changed

Lines changed: 40 additions & 17 deletions

File tree

ctx_interface_gen.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

middleware/proxy/config.go

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import (
99
)
1010

1111
// Config defines the config for middleware.
12-
//
13-
//nolint:govet // struct layout favors readability/grouping over field alignment
1412
type Config struct {
1513
// Next defines a function to skip this middleware when returned true.
1614
//
@@ -35,6 +33,13 @@ type Config struct {
3533
// and DialDualStack will not be used if the client are set.
3634
Client *fasthttp.LBClient
3735

36+
// SecurityPolicy overrides the default SSRF, redirect, and
37+
// hop-by-hop header rules for this balancer. When nil, the
38+
// package-level policy set via WithSecurityPolicy is used.
39+
//
40+
// Optional. Default: nil
41+
SecurityPolicy *SecurityPolicy
42+
3843
// Servers defines a list of <scheme>://<host> HTTP servers,
3944
//
4045
// which are used in a round-robin manner.
@@ -62,6 +67,14 @@ type Config struct {
6267
// Per-connection buffer size for responses' writing.
6368
WriteBufferSize int
6469

70+
// MaxResponseBodySize bounds the size (in bytes) of upstream
71+
// responses accepted by the proxy. Responses larger than this are
72+
// rejected to protect the proxy from memory exhaustion. Zero
73+
// preserves fasthttp's default unlimited behavior.
74+
//
75+
// Optional. Default: 0
76+
MaxResponseBodySize int
77+
6578
// KeepConnectionHeader keeps the "Connection" header when set to true.
6679
//
6780
// Note: even when KeepConnectionHeader is true, other RFC 7230 §6.1
@@ -80,21 +93,6 @@ type Config struct {
8093
//
8194
// Optional. Default: false
8295
DialDualStack bool
83-
84-
// SecurityPolicy overrides the default SSRF, redirect, and
85-
// hop-by-hop header rules for this balancer. When nil, the
86-
// package-level policy set via WithSecurityPolicy is used.
87-
//
88-
// Optional. Default: nil
89-
SecurityPolicy *SecurityPolicy
90-
91-
// MaxResponseBodySize bounds the size (in bytes) of upstream
92-
// responses accepted by the proxy. Responses larger than this are
93-
// rejected to protect the proxy from memory exhaustion. Zero
94-
// preserves fasthttp's default unlimited behavior.
95-
//
96-
// Optional. Default: 0
97-
MaxResponseBodySize int
9896
}
9997

10098
const defaultMaxConnsPerHost = 1024

middleware/proxy/security.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,7 @@ func baseIsPlainOrigin(base *url.URL) bool {
605605
base.RawPath == "" &&
606606
base.User == nil &&
607607
base.RawQuery == "" &&
608+
!base.ForceQuery &&
608609
base.Fragment == "" &&
609610
base.Opaque == ""
610611
}
@@ -647,6 +648,11 @@ func canFastJoinPath(requestPath string) bool {
647648
// Repeated path/query separator — let slow path handle.
648649
return false
649650
}
651+
if i == len(requestPath)-1 {
652+
// Empty query/fragment markers are not represented in the
653+
// slow path's output, so fall back to preserve behavior.
654+
return false
655+
}
650656
inPath = false
651657
case '%':
652658
if i+2 >= len(requestPath) || !isHexDigit(requestPath[i+1]) || !isHexDigit(requestPath[i+2]) {

middleware/proxy/security_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,20 @@ func Test_Security_JoinUpstreamPath_PreservesQuery(t *testing.T) {
462462
require.Equal(t, "http://upstream.example/x?y=1&z=2", out)
463463
}
464464

465+
func Test_Security_JoinUpstreamPath_EmptyQueryMarkersUseSlowPath(t *testing.T) {
466+
t.Parallel()
467+
468+
baseWithEmptyQuery, err := parseUpstream("http://upstream.example?")
469+
require.NoError(t, err)
470+
require.True(t, baseWithEmptyQuery.ForceQuery)
471+
require.Equal(t, "http://upstream.example/foo?", joinUpstreamPath(baseWithEmptyQuery, "/foo"))
472+
473+
base, err := parseUpstream("http://upstream.example")
474+
require.NoError(t, err)
475+
require.Equal(t, "http://upstream.example/foo", joinUpstreamPath(base, "/foo?"))
476+
require.Equal(t, "http://upstream.example/foo", joinUpstreamPath(base, "/foo#"))
477+
}
478+
465479
func Test_Security_SecureTLSConfig_DefaultMinVersion(t *testing.T) {
466480
t.Parallel()
467481
out := secureTLSConfig(nil)

0 commit comments

Comments
 (0)