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

coheigea pushed a commit to branch 4.1.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 725d4639c164311cc8da78f8da0fccc864782e94
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Thu Nov 12 13:15:03 2026 +0000

    Reject unexpected SOAP Envelope children before the Body (#3710)
    
    (cherry picked from commit ea98a784aa075512fc477f949c8df61426d019a9)
---
 .../binding/soap/interceptor/Messages.properties   |  1 +
 .../soap/interceptor/ReadHeadersInterceptor.java   | 17 ++++++
 .../interceptor/ReadHeadersInterceptorTest.java    | 62 +++++++++++++++++++++-
 3 files changed, 79 insertions(+), 1 deletion(-)

diff --git 
a/rt/bindings/soap/src/main/java/org/apache/cxf/binding/soap/interceptor/Messages.properties
 
b/rt/bindings/soap/src/main/java/org/apache/cxf/binding/soap/interceptor/Messages.properties
index 8d5e174d3e0..e8a64fd4da3 100644
--- 
a/rt/bindings/soap/src/main/java/org/apache/cxf/binding/soap/interceptor/Messages.properties
+++ 
b/rt/bindings/soap/src/main/java/org/apache/cxf/binding/soap/interceptor/Messages.properties
@@ -26,6 +26,7 @@ MUST_UNDERSTAND=MustUnderstand headers: {0} are not 
understood.
 PARSER_EXC=Could not create DOM DocumentBuilder.
 NO_OPERATION=No such operation: {0}
 NO_SOAP_BODY=No soap body
+UNEXPECTED_ENVELOPE_CHILD=Unexpected element {0} before the SOAP Body
 ATTACHMENT_IO=Attachment IO Exception: {0}
 INVALID_VERSION="{0}", the namespace on the "{1}" element, is not a valid SOAP 
version.
 INVALID_11_VERSION=A SOAP 1.2 message is not valid when sent to a SOAP 1.1 
only endpoint.
diff --git 
a/rt/bindings/soap/src/main/java/org/apache/cxf/binding/soap/interceptor/ReadHeadersInterceptor.java
 
b/rt/bindings/soap/src/main/java/org/apache/cxf/binding/soap/interceptor/ReadHeadersInterceptor.java
index 7376c059e69..938214e5768 100644
--- 
a/rt/bindings/soap/src/main/java/org/apache/cxf/binding/soap/interceptor/ReadHeadersInterceptor.java
+++ 
b/rt/bindings/soap/src/main/java/org/apache/cxf/binding/soap/interceptor/ReadHeadersInterceptor.java
@@ -358,6 +358,7 @@ public class ReadHeadersInterceptor extends 
AbstractSoapInterceptor {
         private final String header;
         private final String body;
         private final String envelope;
+        private final SoapVersion version;
         private final List<XMLEvent> events = new ArrayList<>(8);
         private List<XMLEvent> envEvents;
         private List<XMLEvent> bodyEvents;
@@ -378,6 +379,7 @@ public class ReadHeadersInterceptor extends 
AbstractSoapInterceptor {
         }
 
         HeadersProcessor(SoapVersion version) {
+            this.version = version;
             this.header = version.getHeader().getLocalPart();
             this.ns = version.getEnvelope().getNamespaceURI();
             this.envelope = version.getEnvelope().getLocalPart();
@@ -392,6 +394,9 @@ public class ReadHeadersInterceptor extends 
AbstractSoapInterceptor {
                 switch (event) {
                 case XMLStreamConstants.START_ELEMENT:
                     read++;
+                    if (doc == null && read == 2) {
+                        checkEnvelopeChild(reader);
+                    }
                     addEvent(eventFactory.createStartElement(new 
QName(reader.getNamespaceURI(), reader
                                                             .getLocalName(), 
reader.getPrefix()), null, null));
                     for (int i = 0; i < reader.getNamespaceCount(); i++) {
@@ -442,6 +447,18 @@ public class ReadHeadersInterceptor extends 
AbstractSoapInterceptor {
             return doc;
         }
 
+        /**
+         * Before the SOAP Body, the only child the Envelope may have is the 
SOAP Header. Reject anything
+         * else here, as the events read before the Header are buffered in 
memory.
+         */
+        private void checkEnvelopeChild(XMLStreamReader reader) {
+            if (!ns.equals(reader.getNamespaceURI())
+                || !header.equals(reader.getLocalName()) && 
!body.equals(reader.getLocalName())) {
+                throw new SoapFault(new Message("UNEXPECTED_ENVELOPE_CHILD", 
LOG, reader.getName()),
+                                    version.getSender());
+            }
+        }
+
         private void addEvent(XMLEvent event) {
             if (event.isStartElement()) {
                 lastStartElementQName = event.asStartElement().getName();
diff --git 
a/rt/bindings/soap/src/test/java/org/apache/cxf/binding/soap/interceptor/ReadHeadersInterceptorTest.java
 
b/rt/bindings/soap/src/test/java/org/apache/cxf/binding/soap/interceptor/ReadHeadersInterceptorTest.java
index 906d42a28b3..04b53c7ebfe 100644
--- 
a/rt/bindings/soap/src/test/java/org/apache/cxf/binding/soap/interceptor/ReadHeadersInterceptorTest.java
+++ 
b/rt/bindings/soap/src/test/java/org/apache/cxf/binding/soap/interceptor/ReadHeadersInterceptorTest.java
@@ -31,6 +31,8 @@ import org.w3c.dom.Element;
 import org.w3c.dom.NamedNodeMap;
 
 import org.apache.cxf.binding.soap.Soap11;
+import org.apache.cxf.binding.soap.Soap12;
+import org.apache.cxf.binding.soap.SoapFault;
 import org.apache.cxf.binding.soap.SoapMessage;
 import org.apache.cxf.headers.Header;
 import org.apache.cxf.helpers.CastUtils;
@@ -44,6 +46,8 @@ import org.junit.runners.Parameterized;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 
 /**
  *
@@ -130,9 +134,65 @@ public class ReadHeadersInterceptorTest {
         assertEquals(expectedTransferredAttributeCount, 
attributes.getLength());
     }
 
+    @Test
+    public void testWhitespaceAndCommentsBeforeHeader() throws Exception {
+        SoapMessage message = setUpMessage(("<soap:Envelope 
xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>"
+            + "\n  <!-- comment -->\n  "
+            + "<soap:Header><x:AuthToken 
xmlns:x='http://example.com/schema'>Token</x:AuthToken></soap:Header>"
+            + "<soap:Body><ns2:payload xmlns:ns2='urn:tmp:foo'/></soap:Body>"
+            + "</soap:Envelope>").getBytes());
+        interceptor.handleMessage(message);
+        assertEquals(1, message.getHeaders().size());
+    }
+
+    @Test
+    public void testNoHeader() throws Exception {
+        SoapMessage message = setUpMessage(("<soap:Envelope 
xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>"
+            + "<soap:Body><ns2:payload xmlns:ns2='urn:tmp:foo'/></soap:Body>"
+            + "</soap:Envelope>").getBytes());
+        interceptor.handleMessage(message);
+        assertTrue(message.getHeaders().isEmpty());
+    }
+
+    @Test
+    public void testUnexpectedElementBeforeBody() throws Exception {
+        SoapMessage message = setUpMessage(("<soap:Envelope 
xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>"
+            + "<foo><bar/></foo>"
+            + "<soap:Body><ns2:payload xmlns:ns2='urn:tmp:foo'/></soap:Body>"
+            + "</soap:Envelope>").getBytes());
+        SoapFault fault = assertThrows(SoapFault.class, () -> 
interceptor.handleMessage(message));
+        assertEquals(Soap11.getInstance().getSender(), fault.getFaultCode());
+    }
+
+    @Test
+    public void testUnexpectedElementBeforeHeader() throws Exception {
+        SoapMessage message = setUpMessage(("<soap:Envelope 
xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>"
+            + "<soap:Fault/>"
+            + "<soap:Header><x:AuthToken 
xmlns:x='http://example.com/schema'>Token</x:AuthToken></soap:Header>"
+            + "<soap:Body><ns2:payload xmlns:ns2='urn:tmp:foo'/></soap:Body>"
+            + "</soap:Envelope>").getBytes());
+        assertThrows(SoapFault.class, () -> 
interceptor.handleMessage(message));
+    }
+
+    @Test
+    public void testUnexpectedElementBeforeBodySoap12() throws Exception {
+        SoapMessage message = new SoapMessage(Soap12.getInstance());
+        message.setContent(XMLStreamReader.class, 
StaxUtils.createXMLStreamReader(new ByteArrayInputStream(
+            ("<soap:Envelope 
xmlns:soap='http://www.w3.org/2003/05/soap-envelope'>"
+            + "<foo/>"
+            + "<soap:Body><ns2:payload xmlns:ns2='urn:tmp:foo'/></soap:Body>"
+            + "</soap:Envelope>").getBytes())));
+        SoapFault fault = assertThrows(SoapFault.class, () -> 
interceptor.handleMessage(message));
+        assertEquals(Soap12.getInstance().getSender(), fault.getFaultCode());
+    }
+
     private SoapMessage setUpMessage() throws Exception {
+        return setUpMessage(TEST_SOAP);
+    }
+
+    private SoapMessage setUpMessage(byte[] soap) throws Exception {
         SoapMessage message = new SoapMessage(Soap11.getInstance());
-        message.setContent(XMLStreamReader.class, 
StaxUtils.createXMLStreamReader(new ByteArrayInputStream(TEST_SOAP)));
+        message.setContent(XMLStreamReader.class, 
StaxUtils.createXMLStreamReader(new ByteArrayInputStream(soap)));
         return message;
     }
 

Reply via email to