This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch coheigea/soapheader in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 3deaeb62f0cbedf3ee7829ccf7fa3bd818c2c1dd Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Thu Nov 12 07:32:55 2026 +0000 Reject unexpected SOAP Envelope children before the Body --- .../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; }
