This is an automated email from the ASF dual-hosted git repository.

robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git

commit fe5e5eebeebc71404182fbb570b53c4d33e4feb4
Author: Robert Lazarski <[email protected]>
AuthorDate: Wed Sep 2 18:41:24 2026 -1000

    Do not follow redirects on a decoupled response over HTTP/2
    
    The HTTP sender stopped following them in 75191f7752, but the HTTP/2 sender
    never had the check and its async client follows redirects by default too, 
so
    a reply endpoint answering 307 could still send the server to an address the
    scheme and range checks had refused. The predicate both senders now share
    lives next to those checks, as AddressingResponseEndpointPolicy is what a
    redirect would otherwise walk past.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 .../AddressingResponseEndpointPolicy.java          | 22 +++++++++++++
 .../AddressingResponseEndpointPolicyTest.java      | 37 ++++++++++++++++++++++
 .../h2/impl/httpclient5/H2RequestImpl.java         |  9 ++++++
 .../http/impl/httpclient5/RequestImpl.java         | 17 +++-------
 4 files changed, 73 insertions(+), 12 deletions(-)

diff --git 
a/modules/kernel/src/org/apache/axis2/addressing/AddressingResponseEndpointPolicy.java
 
b/modules/kernel/src/org/apache/axis2/addressing/AddressingResponseEndpointPolicy.java
index c9b10946d3..654dc16ff1 100644
--- 
a/modules/kernel/src/org/apache/axis2/addressing/AddressingResponseEndpointPolicy.java
+++ 
b/modules/kernel/src/org/apache/axis2/addressing/AddressingResponseEndpointPolicy.java
@@ -427,6 +427,28 @@ public final class AddressingResponseEndpointPolicy {
         }
     }
 
+    /**
+     * Whether this send is a WS-Addressing decoupled response: a server-side 
send to
+     * a destination the caller named, rather than an ordinary client-side 
request.
+     * <p>
+     * Transports use this to decide whether to follow redirects. The scheme 
allow-list
+     * and the address checks in {@link #isAllowed} run against the endpoint 
reference
+     * the caller supplied, and nothing re-examines where a redirect leads, so 
following
+     * one steps straight past them: a 307 from the caller's own endpoint to 
an address
+     * this policy had already refused would be honoured. A decoupled reply is
+     * fire-and-forget, so nothing legitimate depends on following a redirect.
+     *
+     * @param messageContext the message being sent; may be {@code null}
+     * @return whether the destination was nominated by the caller
+     */
+    public static boolean isDecoupledResponse(MessageContext messageContext) {
+        if (messageContext == null || !messageContext.isServerSide()) {
+            return false;
+        }
+        EndpointReference to = messageContext.getTo();
+        return to != null && !to.hasAnonymousAddress() && !to.hasNoneAddress();
+    }
+
     private static boolean booleanParameter(MessageContext messageContext, 
String name,
                                             boolean defaultValue) {
         if (messageContext == null) {
diff --git 
a/modules/kernel/test/org/apache/axis2/addressing/AddressingResponseEndpointPolicyTest.java
 
b/modules/kernel/test/org/apache/axis2/addressing/AddressingResponseEndpointPolicyTest.java
index 8e3773c6ce..3d2a90e2a6 100644
--- 
a/modules/kernel/test/org/apache/axis2/addressing/AddressingResponseEndpointPolicyTest.java
+++ 
b/modules/kernel/test/org/apache/axis2/addressing/AddressingResponseEndpointPolicyTest.java
@@ -356,4 +356,41 @@ public class AddressingResponseEndpointPolicyTest extends 
TestCase {
         assertFalse(AddressingResponseEndpointPolicy.isAllowed(
                 new EndpointReference("http:///no-host";), messageContext));
     }
+
+    /**
+     * The predicate transports use to decide whether to follow redirects. A 
redirect
+     * is only refused where the destination came from the caller, which is the
+     * decoupled-response case; ordinary client requests keep following them.
+     */
+    public void testDecoupledResponseIsOnlyServerSideNonAnonymous() {
+        messageContext.setServerSide(true);
+        messageContext.setTo(new 
EndpointReference("https://caller.example.com/replies";));
+        assertTrue("a server-side send to a caller-named address is a 
decoupled response",
+                
AddressingResponseEndpointPolicy.isDecoupledResponse(messageContext));
+    }
+
+    public void testClientRequestIsNotADecoupledResponse() {
+        messageContext.setServerSide(false);
+        messageContext.setTo(new 
EndpointReference("https://service.example.com/svc";));
+        assertFalse("a client-side request must keep following redirects",
+                
AddressingResponseEndpointPolicy.isDecoupledResponse(messageContext));
+    }
+
+    public void testAnonymousAndNoneRepliesAreNotDecoupled() {
+        messageContext.setServerSide(true);
+        messageContext.setTo(new 
EndpointReference(AddressingConstants.Final.WSA_ANONYMOUS_URL));
+        assertFalse("an anonymous reply goes back down the inbound connection",
+                
AddressingResponseEndpointPolicy.isDecoupledResponse(messageContext));
+
+        messageContext.setTo(new 
EndpointReference(AddressingConstants.Final.WSA_NONE_URI));
+        assertFalse("a none reply is not sent anywhere",
+                
AddressingResponseEndpointPolicy.isDecoupledResponse(messageContext));
+    }
+
+    public void testMissingDestinationIsNotDecoupled() {
+        messageContext.setServerSide(true);
+        messageContext.setTo(null);
+        
assertFalse(AddressingResponseEndpointPolicy.isDecoupledResponse(messageContext));
+        
assertFalse(AddressingResponseEndpointPolicy.isDecoupledResponse(null));
+    }
 }
diff --git 
a/modules/transport-h2/src/main/java/org/apache/axis2/transport/h2/impl/httpclient5/H2RequestImpl.java
 
b/modules/transport-h2/src/main/java/org/apache/axis2/transport/h2/impl/httpclient5/H2RequestImpl.java
index e75a34f591..2a3919511e 100644
--- 
a/modules/transport-h2/src/main/java/org/apache/axis2/transport/h2/impl/httpclient5/H2RequestImpl.java
+++ 
b/modules/transport-h2/src/main/java/org/apache/axis2/transport/h2/impl/httpclient5/H2RequestImpl.java
@@ -31,6 +31,7 @@ import java.util.concurrent.TimeUnit;
 
 import org.apache.axiom.mime.Header;
 import org.apache.axis2.AxisFault;
+import org.apache.axis2.addressing.AddressingResponseEndpointPolicy;
 import org.apache.axis2.context.MessageContext;
 import org.apache.axis2.kernel.http.HTTPConstants;
 import org.apache.axis2.transport.http.AxisRequestEntity;
@@ -278,6 +279,14 @@ public class H2RequestImpl implements Request {
             requestConfig.setCookieSpec(cookiePolicy);
         }
 
+        // Following a redirect on a decoupled response would step past the 
scheme
+        // and address checks, which only ever saw the caller-supplied 
destination;
+        // see AddressingResponseEndpointPolicy.isDecoupledResponse. The async 
client
+        // follows redirects by default just as the blocking one does.
+        if (AddressingResponseEndpointPolicy.isDecoupledResponse(msgContext)) {
+            requestConfig.setRedirectsEnabled(false);
+        }
+
         clientContext.setRequestConfig(requestConfig.build());
 
         // Remove Content-Length header to avoid ProtocolException (AXIS2-6051)
diff --git 
a/modules/transport/http/src/main/java/org/apache/axis2/transport/http/impl/httpclient5/RequestImpl.java
 
b/modules/transport/http/src/main/java/org/apache/axis2/transport/http/impl/httpclient5/RequestImpl.java
index 5901857651..a2249b388c 100644
--- 
a/modules/transport/http/src/main/java/org/apache/axis2/transport/http/impl/httpclient5/RequestImpl.java
+++ 
b/modules/transport/http/src/main/java/org/apache/axis2/transport/http/impl/httpclient5/RequestImpl.java
@@ -28,7 +28,7 @@ import java.util.Map;
 
 import org.apache.axiom.mime.Header;
 import org.apache.axis2.AxisFault;
-import org.apache.axis2.addressing.EndpointReference;
+import org.apache.axis2.addressing.AddressingResponseEndpointPolicy;
 import org.apache.axis2.context.MessageContext;
 import org.apache.axis2.kernel.http.HTTPConstants;
 import org.apache.axis2.transport.http.AxisRequestEntity;
@@ -237,17 +237,10 @@ final class RequestImpl implements Request {
             requestConfig.setCookieSpec(cookiePolicy);
         }
 
-        // A server-side send to a non-anonymous destination is a WS-Addressing
-        // decoupled response, and the destination came from the caller. The 
scheme
-        // and address checks in AddressingResponseEndpointPolicy ran against 
that
-        // destination only, so following a redirect would step straight past 
them:
-        // a 307 from the caller's own endpoint to http://169.254.169.254/ 
would be
-        // honoured even though neither the scheme nor the address would have
-        // passed. The reply is fire-and-forget, so nothing legitimate needs 
the
-        // redirect.
-        EndpointReference to = msgContext.getTo();
-        if (msgContext.isServerSide() && to != null
-                && !to.hasAnonymousAddress() && !to.hasNoneAddress()) {
+        // Following a redirect on a decoupled response would step past the 
scheme
+        // and address checks, which only ever saw the caller-supplied 
destination;
+        // see AddressingResponseEndpointPolicy.isDecoupledResponse.
+        if (AddressingResponseEndpointPolicy.isDecoupledResponse(msgContext)) {
             requestConfig.setRedirectsEnabled(false);
         }
 

Reply via email to