From 46b8b2c3f083ef91350efdf78bc6f8bc59e2ed41 Mon Sep 17 00:00:00 2001 From: Christian Kaaber Date: Thu, 19 Feb 2026 12:57:17 +0100 Subject: [PATCH 1/2] Restore workaround for redundant namespace declarations on SOAP headers Replace setMustUnderstand() and setRole() with explicit addAttribute() calls using the envelope's namespace. Axiom's high-level setters generate redundant namespace declarations on SOAP header attributes (e.g. xmlns:mustUnderstand="..." mustUnderstand:mustUnderstand="true") which can confuse other SOAP processors when performing XML Canonicalization. This fix was originally introduced by Sander Fieten in commit ada0200f (Nov 2020) but was inadvertently lost during the project restructuring in the 6.0 development cycle when Messaging.java was copied from a version without the fix. Uses Axiom's SOAPConstants and SOAPVersion.getRoleAttributeQName() to remain SOAP-version-independent rather than hardcoding attribute names. Co-Authored-By: Claude Opus 4.6 --- .../holodeckb2b/as4/multihop/ConfigureMultihop.java | 7 +++++-- .../org/holodeckb2b/ebms3/packaging/Messaging.java | 13 +++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/modules/holodeckb2b-ebms3as4/src/main/java/org/holodeckb2b/as4/multihop/ConfigureMultihop.java b/modules/holodeckb2b-ebms3as4/src/main/java/org/holodeckb2b/as4/multihop/ConfigureMultihop.java index 9b1fd2204..9810cbe62 100644 --- a/modules/holodeckb2b-ebms3as4/src/main/java/org/holodeckb2b/as4/multihop/ConfigureMultihop.java +++ b/modules/holodeckb2b-ebms3as4/src/main/java/org/holodeckb2b/as4/multihop/ConfigureMultihop.java @@ -18,6 +18,7 @@ import java.util.Collection; +import org.apache.axiom.soap.SOAPEnvelope; import org.apache.axiom.soap.SOAPHeaderBlock; import org.apache.axis2.addressing.AddressingConstants; import org.apache.axis2.addressing.EndpointReference; @@ -83,8 +84,10 @@ protected InvocationResponse doProcessing(final IMessageProcessingContext procCt else { // This is a multi-hop message, set the multi-hop target on the eb:Messaging element log.debug("Primary message is a multi-hop UserMessage -> set multi-hop target"); - final SOAPHeaderBlock ebHeader = Messaging.getElement(procCtx.getParentContext().getEnvelope()); - ebHeader.setRole(MultiHopConstants.NEXT_MSH_TARGET); + final SOAPEnvelope envelope = procCtx.getParentContext().getEnvelope(); + final SOAPHeaderBlock ebHeader = Messaging.getElement(envelope); + final String roleAttr = envelope.getVersion().getRoleAttributeQName().getLocalPart(); + ebHeader.addAttribute(roleAttr, MultiHopConstants.NEXT_MSH_TARGET, envelope.getNamespace()); } } else if (primMU instanceof IPullRequest) { // If the primary message unit is a PullRequest the message is not sent using multi-hop as this is not diff --git a/modules/holodeckb2b-ebms3as4/src/main/java/org/holodeckb2b/ebms3/packaging/Messaging.java b/modules/holodeckb2b-ebms3as4/src/main/java/org/holodeckb2b/ebms3/packaging/Messaging.java index e024966b0..3a65f8917 100644 --- a/modules/holodeckb2b-ebms3as4/src/main/java/org/holodeckb2b/ebms3/packaging/Messaging.java +++ b/modules/holodeckb2b-ebms3as4/src/main/java/org/holodeckb2b/ebms3/packaging/Messaging.java @@ -18,6 +18,7 @@ import javax.xml.namespace.QName; +import org.apache.axiom.soap.SOAPConstants; import org.apache.axiom.soap.SOAPEnvelope; import org.apache.axiom.soap.SOAPHeaderBlock; import org.holodeckb2b.interfaces.general.EbMSConstants; @@ -52,8 +53,16 @@ public static SOAPHeaderBlock createElement(final org.apache.axiom.soap.SOAPEnve // No existing messaging element, so create a new one messaging = env.getHeader().addHeaderBlock(Q_ELEMENT_NAME.getLocalPart(), SOAPEnv.getEbms3Namespace(env)); - // The messaging header must be understood by the MSH (see 5.2.1 core spec) - messaging.setMustUnderstand(true); + /* + * The mustUnderstand attribute is explicitly added here because setting + * the mustUnderstand flag on the object results in a redundant namespace + * declaration and prefix, e.g. xmlns:mustUnderstand="...." + * mustUnderstand:mustUnderstand="true" + * Although this isn't incorrect, this additional declaration can confuse + * other SOAP processors when performing c14n. + */ + messaging.addAttribute(SOAPConstants.ATTR_MUSTUNDERSTAND, + SOAPConstants.ATTR_MUSTUNDERSTAND_TRUE, env.getNamespace()); } return messaging; From ce2702d47303e6572e3aa714bd550d97a8d9c50e Mon Sep 17 00:00:00 2001 From: Christian Kaaber Date: Thu, 19 Feb 2026 15:20:41 +0100 Subject: [PATCH 2/2] Add regression tests for redundant namespace declarations on SOAP headers The workaround in Messaging.createElement (restored in 46b8b2c3) can easily be lost during refactoring. These tests serialize the envelope and verify that mustUnderstand and role attributes do not produce redundant namespace declarations that break c14n interoperability. Co-Authored-By: Claude Opus 4.6 --- .../ebms3/packaging/MessagingTest.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/modules/holodeckb2b-ebms3as4/src/test/java/org/holodeckb2b/ebms3/packaging/MessagingTest.java b/modules/holodeckb2b-ebms3as4/src/test/java/org/holodeckb2b/ebms3/packaging/MessagingTest.java index 125ba8f24..b96b74600 100644 --- a/modules/holodeckb2b-ebms3as4/src/test/java/org/holodeckb2b/ebms3/packaging/MessagingTest.java +++ b/modules/holodeckb2b-ebms3as4/src/test/java/org/holodeckb2b/ebms3/packaging/MessagingTest.java @@ -17,13 +17,17 @@ package org.holodeckb2b.ebms3.packaging; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import java.io.ByteArrayOutputStream; import java.util.ArrayList; +import org.apache.axiom.soap.SOAPConstants; import org.apache.axiom.soap.SOAPEnvelope; import org.apache.axiom.soap.SOAPHeader; import org.apache.axiom.soap.SOAPHeaderBlock; +import org.holodeckb2b.as4.multihop.MultiHopConstants; import org.holodeckb2b.interfaces.general.EbMSConstants; import org.junit.Test; @@ -65,4 +69,39 @@ public void testGetElement() throws Exception { assertEquals(soapHeaderBlock.getVersion(), newSoapHeaderBlock.getVersion()); } + + @Test + public void testMustUnderstandSerializesCleanly() throws Exception { + SOAPEnvelope env = SOAPEnv.createEnvelope(SOAPEnv.SOAPVersion.SOAP_12); + Messaging.createElement(env); + + assertNoRedundantNamespace(env, SOAPConstants.ATTR_MUSTUNDERSTAND); + } + + @Test + public void testRoleAttributeSerializesCleanly() throws Exception { + SOAPEnvelope env = SOAPEnv.createEnvelope(SOAPEnv.SOAPVersion.SOAP_12); + SOAPHeaderBlock messaging = Messaging.createElement(env); + + String roleAttr = env.getVersion().getRoleAttributeQName().getLocalPart(); + messaging.addAttribute(roleAttr, MultiHopConstants.NEXT_MSH_TARGET, env.getNamespace()); + + assertNoRedundantNamespace(env, roleAttr); + } + + private static String serializeToString(SOAPEnvelope env) throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + env.serialize(baos); + return baos.toString("UTF-8"); + } + + private static void assertNoRedundantNamespace(SOAPEnvelope env, String attrName) throws Exception { + String xml = serializeToString(env); + assertFalse("Redundant namespace declaration 'xmlns:" + attrName + "=' breaks c14n" + + " interoperability.\nActual XML:\n" + xml, + xml.contains("xmlns:" + attrName + "=")); + assertFalse("Wrongly-prefixed attribute '" + attrName + ":" + attrName + "=' breaks c14n" + + " interoperability.\nActual XML:\n" + xml, + xml.contains(attrName + ":" + attrName + "=")); + } }