@@ -153,6 +153,9 @@ func (l *lbClientTransport) DoDeadline(req *fasthttp.Request, resp *fasthttp.Res
153153 return l .client .DoDeadline (req , resp , deadline )
154154}
155155
156+ // DoRedirects proxies redirect handling through doRedirectsWithClient so
157+ // lbClientTransport matches fasthttp.Client semantics even though
158+ // fasthttp.LBClient does not publish a dedicated DoRedirects helper.
156159func (l * lbClientTransport ) DoRedirects (req * fasthttp.Request , resp * fasthttp.Response , maxRedirects int ) error {
157160 return doRedirectsWithClient (req , resp , maxRedirects , l .client )
158161}
@@ -191,12 +194,16 @@ func (l *lbClientTransport) Client() any {
191194 return l .client
192195}
193196
197+ // forEachHostClient applies fn to every host client reachable from the provided
198+ // load balancer, recursing into nested balancing clients.
194199func forEachHostClient (lb * fasthttp.LBClient , fn func (* fasthttp.HostClient )) {
195200 for _ , c := range lb .Clients {
196201 walkBalancingClient (c , fn )
197202 }
198203}
199204
205+ // walkBalancingClient traverses nested balancing clients and invokes fn for
206+ // each host client encountered.
200207func walkBalancingClient (client any , fn func (* fasthttp.HostClient )) {
201208 switch c := client .(type ) {
202209 case * fasthttp.HostClient :
@@ -212,6 +219,9 @@ func walkBalancingClient(client any, fn func(*fasthttp.HostClient)) {
212219 }
213220}
214221
222+ // extractTLSConfig returns the first TLS configuration discovered while walking
223+ // the provided balancing clients, enabling cached reuse across nested load
224+ // balancers.
215225func extractTLSConfig (clients []fasthttp.BalancingClient ) * tls.Config {
216226 var cfg * tls.Config
217227 for _ , c := range clients {
@@ -228,6 +238,9 @@ func extractTLSConfig(clients []fasthttp.BalancingClient) *tls.Config {
228238 return cfg
229239}
230240
241+ // extractDial returns the first dial function discovered while walking the
242+ // provided balancing clients, ensuring overrides propagate through nested
243+ // transports.
231244func extractDial (clients []fasthttp.BalancingClient ) fasthttp.DialFunc {
232245 var dial fasthttp.DialFunc
233246 for _ , c := range clients {
@@ -244,6 +257,9 @@ func extractDial(clients []fasthttp.BalancingClient) fasthttp.DialFunc {
244257 return dial
245258}
246259
260+ // walkBalancingClientWithBreak traverses balancing clients and invokes fn for
261+ // each host client encountered until fn returns true, allowing callers to stop
262+ // the traversal early when a match is found.
247263func walkBalancingClientWithBreak (client any , fn func (* fasthttp.HostClient ) bool ) bool {
248264 switch c := client .(type ) {
249265 case * fasthttp.HostClient :
@@ -264,10 +280,16 @@ func walkBalancingClientWithBreak(client any, fn func(*fasthttp.HostClient) bool
264280 return false
265281}
266282
283+ // redirectClient describes the minimal surface needed by doRedirectsWithClient to
284+ // issue HTTP requests while preserving fasthttp redirect semantics.
267285type redirectClient interface {
268286 Do (req * fasthttp.Request , resp * fasthttp.Response ) error
269287}
270288
289+ // doRedirectsWithClient mirrors fasthttp's redirect loop for transports that do
290+ // not expose DoRedirects (e.g. fasthttp.LBClient). The helper always issues the
291+ // initial request, respects zero redirect limits, falls back to the default cap
292+ // for negative values, and validates redirect targets before following them.
271293func doRedirectsWithClient (req * fasthttp.Request , resp * fasthttp.Response , maxRedirects int , client redirectClient ) error {
272294 currentURL := req .URI ().String ()
273295 redirects := 0
@@ -318,7 +340,15 @@ func doRedirectsWithClient(req *fasthttp.Request, resp *fasthttp.Response, maxRe
318340 }
319341}
320342
343+ // composeRedirectURL resolves a redirect target relative to the current request
344+ // URL while rejecting suspicious payloads (e.g. control characters) and
345+ // restricting schemes to HTTP/S so caller-provided Location headers cannot
346+ // trigger arbitrary transports.
321347func composeRedirectURL (base string , location []byte , disablePathNormalizing bool ) (string , error ) {
348+ if bytes .ContainsAny (location , "\r \n " ) {
349+ return "" , fasthttp .ErrorInvalidURI
350+ }
351+
322352 uri := fasthttp .AcquireURI ()
323353 defer fasthttp .ReleaseURI (uri )
324354
@@ -330,5 +360,9 @@ func composeRedirectURL(base string, location []byte, disablePathNormalizing boo
330360 return "" , fasthttp .ErrorInvalidURI
331361 }
332362
363+ if len (uri .Scheme ()) > 0 && len (uri .Host ()) == 0 {
364+ return "" , fasthttp .ErrorInvalidURI
365+ }
366+
333367 return uri .String (), nil
334368}
0 commit comments