11/*
2- * Copyright (c) 2016, 2024 Oracle and/or its affiliates. All rights reserved.
2+ * Copyright (c) 2016, 2025 Oracle and/or its affiliates. All rights reserved.
33 *
44 * This program and the accompanying materials are made available under the
55 * terms of the Eclipse Public License v. 2.0, which is available at
1818
1919import java .io .IOException ;
2020import java .net .URI ;
21- import java .util .Iterator ;
22- import java .util .List ;
2321import java .util .Locale ;
2422import java .util .Map ;
2523import java .util .Set ;
2826import java .util .concurrent .TimeoutException ;
2927import java .util .function .Predicate ;
3028
31- import javax .ws .rs .HttpMethod ;
32- import javax .ws .rs .core .MultivaluedMap ;
3329import javax .ws .rs .core .Response ;
3430
3531import org .glassfish .jersey .client .ClientProperties ;
@@ -67,6 +63,7 @@ class JerseyClientHandler extends SimpleChannelInboundHandler<HttpObject> {
6763 private final boolean followRedirects ;
6864 private final int maxRedirects ;
6965 private final NettyConnector connector ;
66+ private final NettyHttpRedirectController redirectController ;
7067
7168 private NettyInputStream nis ;
7269 private ClientResponse jerseyResponse ;
@@ -83,6 +80,10 @@ class JerseyClientHandler extends SimpleChannelInboundHandler<HttpObject> {
8380 this .followRedirects = jerseyRequest .resolveProperty (ClientProperties .FOLLOW_REDIRECTS , true );
8481 this .maxRedirects = jerseyRequest .resolveProperty (NettyClientProperties .MAX_REDIRECTS , DEFAULT_MAX_REDIRECTS );
8582 this .connector = connector ;
83+
84+ final NettyHttpRedirectController customRedirectController = jerseyRequest
85+ .resolveProperty (NettyClientProperties .HTTP_REDIRECT_CONTROLLER , NettyHttpRedirectController .class );
86+ this .redirectController = customRedirectController == null ? new NettyHttpRedirectController () : customRedirectController ;
8687 }
8788
8889 @ Override
@@ -142,22 +143,24 @@ protected void notifyResponse() {
142143 } else {
143144 ClientRequest newReq = new ClientRequest (jerseyRequest );
144145 newReq .setUri (newUri );
145- restrictRedirectRequest (newReq , cr );
146-
147- final NettyConnector newConnector = new NettyConnector (newReq .getClient ());
148- newConnector .execute (newReq , redirectUriHistory , new CompletableFuture <ClientResponse >() {
149- @ Override
150- public boolean complete (ClientResponse value ) {
151- newConnector .close ();
152- return responseAvailable .complete (value );
153- }
154-
155- @ Override
156- public boolean completeExceptionally (Throwable ex ) {
157- newConnector .close ();
158- return responseAvailable .completeExceptionally (ex );
159- }
160- });
146+ if (redirectController .prepareRedirect (newReq , cr )) {
147+ final NettyConnector newConnector = new NettyConnector (newReq .getClient ());
148+ newConnector .execute (newReq , redirectUriHistory , new CompletableFuture <ClientResponse >() {
149+ @ Override
150+ public boolean complete (ClientResponse value ) {
151+ newConnector .close ();
152+ return responseAvailable .complete (value );
153+ }
154+
155+ @ Override
156+ public boolean completeExceptionally (Throwable ex ) {
157+ newConnector .close ();
158+ return responseAvailable .completeExceptionally (ex );
159+ }
160+ });
161+ } else {
162+ responseAvailable .complete (cr );
163+ }
161164 }
162165 } catch (IllegalArgumentException e ) {
163166 responseAvailable .completeExceptionally (
@@ -226,8 +229,6 @@ public String getReasonPhrase() {
226229 }
227230 }
228231
229-
230-
231232 @ Override
232233 public void exceptionCaught (ChannelHandlerContext ctx , final Throwable cause ) {
233234 responseDone .completeExceptionally (cause );
@@ -243,53 +244,6 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc
243244 }
244245 }
245246
246- /*
247- * RFC 9110 Section 15.4
248- * https://httpwg.org/specs/rfc9110.html#rfc.section.15.4
249- */
250- private void restrictRedirectRequest (ClientRequest newRequest , ClientResponse response ) {
251- final MultivaluedMap <String , Object > headers = newRequest .getHeaders ();
252- final Boolean keepMethod = newRequest .resolveProperty (NettyClientProperties .PRESERVE_METHOD_ON_REDIRECT , Boolean .TRUE );
253-
254- if (Boolean .FALSE .equals (keepMethod ) && newRequest .getMethod ().equals (HttpMethod .POST )) {
255- switch (response .getStatus ()) {
256- case 301 /* MOVED PERMANENTLY */ :
257- case 302 /* FOUND */ :
258- removeContentHeaders (headers );
259- newRequest .setMethod (HttpMethod .GET );
260- newRequest .setEntity (null );
261- break ;
262- }
263- }
264-
265- for (final Iterator <Map .Entry <String , List <Object >>> it = headers .entrySet ().iterator (); it .hasNext (); ) {
266- final Map .Entry <String , List <Object >> entry = it .next ();
267- if (ProxyHeaders .INSTANCE .test (entry .getKey ())) {
268- it .remove ();
269- }
270- }
271-
272- headers .remove (HttpHeaders .IF_MATCH );
273- headers .remove (HttpHeaders .IF_NONE_MATCH );
274- headers .remove (HttpHeaders .IF_MODIFIED_SINCE );
275- headers .remove (HttpHeaders .IF_UNMODIFIED_SINCE );
276- headers .remove (HttpHeaders .AUTHORIZATION );
277- headers .remove (HttpHeaders .REFERER );
278- headers .remove (HttpHeaders .COOKIE );
279- }
280-
281- private void removeContentHeaders (MultivaluedMap <String , Object > headers ) {
282- for (final Iterator <Map .Entry <String , List <Object >>> it = headers .entrySet ().iterator (); it .hasNext (); ) {
283- final Map .Entry <String , List <Object >> entry = it .next ();
284- final String lowName = entry .getKey ().toLowerCase (Locale .ROOT );
285- if (lowName .startsWith ("content-" )) {
286- it .remove ();
287- }
288- }
289- headers .remove (HttpHeaders .LAST_MODIFIED );
290- headers .remove (HttpHeaders .TRANSFER_ENCODING );
291- }
292-
293247 /* package */ static class ProxyHeaders implements Predicate <String > {
294248 static final ProxyHeaders INSTANCE = new ProxyHeaders ();
295249 private static final String HOST = HttpHeaders .HOST .toLowerCase (Locale .ROOT );
0 commit comments