From 42dddeb7d851d7022dd7596a9e74600e30d437b0 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 12 Mar 2026 13:31:38 +0100 Subject: [PATCH 1/2] [CXF-7491] Make XSLT and Transform interceptors charset-aware The XSLT and Transform interceptors were hardcoded to use UTF-8 encoding, ignoring the charset specified in the message. This change propagates the message encoding through the interceptor chain so that non-UTF-8 content (e.g. ISO-8859-1) is handled correctly. - Add getEncoding() to AbstractXSLTInterceptor - Add encoding-aware XSLTUtils.transform(Templates, InputStream, String) - Add encoding-aware TransformUtils.createNewReaderIfNeeded() and createTransformReaderIfNeeded() overloads - Update XSLTInInterceptor, XSLTOutInterceptor, and TransformInInterceptor to pass message encoding - Add tests for ISO-8859-1 encoded XML transformation Co-Authored-By: Claude Opus 4.6 --- .../transform/AbstractXSLTInterceptor.java | 17 +++++++ .../feature/transform/XSLTInInterceptor.java | 14 ++++-- .../feature/transform/XSLTOutInterceptor.java | 14 ++++-- .../cxf/feature/transform/XSLTUtils.java | 14 +++++- .../transform/TransformInInterceptor.java | 47 +++++++++++++++---- .../staxutils/transform/TransformUtils.java | 26 +++++++++- .../transform/XSLTInterceptorsTest.java | 34 ++++++++++++++ .../feature/transform/message-iso-8859-1.xml | 8 ++++ 8 files changed, 154 insertions(+), 20 deletions(-) create mode 100644 core/src/test/java/org/apache/cxf/feature/transform/message-iso-8859-1.xml diff --git a/core/src/main/java/org/apache/cxf/feature/transform/AbstractXSLTInterceptor.java b/core/src/main/java/org/apache/cxf/feature/transform/AbstractXSLTInterceptor.java index 1dc91c8d76c..d50af90fb1c 100644 --- a/core/src/main/java/org/apache/cxf/feature/transform/AbstractXSLTInterceptor.java +++ b/core/src/main/java/org/apache/cxf/feature/transform/AbstractXSLTInterceptor.java @@ -21,6 +21,7 @@ import java.io.InputStream; +import java.nio.charset.StandardCharsets; import javax.xml.stream.XMLStreamException; import javax.xml.transform.Templates; @@ -31,6 +32,7 @@ import org.w3c.dom.Document; import org.apache.cxf.common.classloader.ClassLoaderUtils; +import org.apache.cxf.message.Exchange; import org.apache.cxf.message.Message; import org.apache.cxf.message.MessageUtils; import org.apache.cxf.phase.AbstractPhaseInterceptor; @@ -88,4 +90,19 @@ protected boolean checkContextProperty(Message message) { protected Templates getXSLTTemplate() { return xsltTemplate; } + + protected String getEncoding(Message message) { + String encoding = (String) message.get(Message.ENCODING); + if (encoding == null) { + Exchange ex = message.getExchange(); + if (ex != null && ex.getInMessage() != null) { + encoding = + (String) ex.getInMessage().get(Message.ENCODING); + } + } + if (encoding == null) { + encoding = StandardCharsets.UTF_8.name(); + } + return encoding; + } } diff --git a/core/src/main/java/org/apache/cxf/feature/transform/XSLTInInterceptor.java b/core/src/main/java/org/apache/cxf/feature/transform/XSLTInInterceptor.java index 5e98493ffd2..6ffd9e29ed4 100644 --- a/core/src/main/java/org/apache/cxf/feature/transform/XSLTInInterceptor.java +++ b/core/src/main/java/org/apache/cxf/feature/transform/XSLTInInterceptor.java @@ -79,9 +79,12 @@ public void handleMessage(Message message) { protected void transformXReader(Message message, XMLStreamReader xReader) { CachedOutputStream cachedOS = new CachedOutputStream(); try { + String encoding = getEncoding(message); StaxUtils.copy(xReader, cachedOS); - InputStream transformedIS = XSLTUtils.transform(getXSLTTemplate(), cachedOS.getInputStream()); - XMLStreamReader transformedReader = StaxUtils.createXMLStreamReader(transformedIS); + InputStream transformedIS = XSLTUtils.transform( + getXSLTTemplate(), cachedOS.getInputStream(), encoding); + XMLStreamReader transformedReader = + StaxUtils.createXMLStreamReader(transformedIS, encoding); message.setContent(XMLStreamReader.class, transformedReader); } catch (XMLStreamException e) { throw new Fault("STAX_COPY", LOG, e, e.getMessage()); @@ -103,9 +106,12 @@ protected void transformXReader(Message message, XMLStreamReader xReader) { protected void transformIS(Message message, InputStream is) { try (InputStream inputStream = is) { - message.setContent(InputStream.class, XSLTUtils.transform(getXSLTTemplate(), inputStream)); + String encoding = getEncoding(message); + message.setContent(InputStream.class, + XSLTUtils.transform(getXSLTTemplate(), inputStream, encoding)); } catch (IOException e) { - LOG.warning("Cannot close stream after transformation: " + e.getMessage()); + LOG.warning("Cannot close stream after transformation: " + + e.getMessage()); } } diff --git a/core/src/main/java/org/apache/cxf/feature/transform/XSLTOutInterceptor.java b/core/src/main/java/org/apache/cxf/feature/transform/XSLTOutInterceptor.java index a7f54c80f0b..c4508cda7a2 100644 --- a/core/src/main/java/org/apache/cxf/feature/transform/XSLTOutInterceptor.java +++ b/core/src/main/java/org/apache/cxf/feature/transform/XSLTOutInterceptor.java @@ -94,7 +94,10 @@ protected void transformXWriter(Message message, XMLStreamWriter xWriter) { protected void transformOS(Message message, OutputStream out) { CachedOutputStream wrapper = new CachedOutputStream(); - CachedOutputStreamCallback callback = new XSLTCachedOutputStreamCallback(getXSLTTemplate(), out); + String encoding = getEncoding(message); + CachedOutputStreamCallback callback = + new XSLTCachedOutputStreamCallback(getXSLTTemplate(), + out, encoding); wrapper.registerCallback(callback); message.setContent(OutputStream.class, wrapper); } @@ -144,10 +147,14 @@ public void close() { public static class XSLTCachedOutputStreamCallback implements CachedOutputStreamCallback { private final Templates xsltTemplate; private final OutputStream origStream; + private final String encoding; - public XSLTCachedOutputStreamCallback(Templates xsltTemplate, OutputStream origStream) { + public XSLTCachedOutputStreamCallback(Templates xsltTemplate, + OutputStream origStream, + String encoding) { this.xsltTemplate = xsltTemplate; this.origStream = origStream; + this.encoding = encoding; } @Override @@ -159,7 +166,8 @@ public void onClose(CachedOutputStream wrapper) { InputStream transformedStream; Exception exceptionOnClose = null; try { - transformedStream = XSLTUtils.transform(xsltTemplate, wrapper.getInputStream()); + transformedStream = XSLTUtils.transform( + xsltTemplate, wrapper.getInputStream(), encoding); IOUtils.copyAndCloseInput(transformedStream, origStream); } catch (IOException e) { throw new Fault("STREAM_COPY", LOG, e, e.getMessage()); diff --git a/core/src/main/java/org/apache/cxf/feature/transform/XSLTUtils.java b/core/src/main/java/org/apache/cxf/feature/transform/XSLTUtils.java index 4b35b2ffb95..1b70ab3cf2b 100644 --- a/core/src/main/java/org/apache/cxf/feature/transform/XSLTUtils.java +++ b/core/src/main/java/org/apache/cxf/feature/transform/XSLTUtils.java @@ -50,8 +50,18 @@ private XSLTUtils() { } public static InputStream transform(Templates xsltTemplate, InputStream in) { - try (InputStream inputStream = in; CachedOutputStream out = new CachedOutputStream()) { - Source beforeSource = new StaxSource(StaxUtils.createXMLStreamReader(inputStream)); + return transform(xsltTemplate, in, null); + } + + public static InputStream transform(Templates xsltTemplate, + InputStream in, + String encoding) { + try (InputStream inputStream = in; + CachedOutputStream out = new CachedOutputStream()) { + Source beforeSource = new StaxSource( + encoding != null + ? StaxUtils.createXMLStreamReader(inputStream, encoding) + : StaxUtils.createXMLStreamReader(inputStream)); Transformer trans = xsltTemplate.newTransformer(); trans.transform(beforeSource, new StreamResult(out)); diff --git a/core/src/main/java/org/apache/cxf/interceptor/transform/TransformInInterceptor.java b/core/src/main/java/org/apache/cxf/interceptor/transform/TransformInInterceptor.java index 1c0eb847b57..cb69d1bebc1 100644 --- a/core/src/main/java/org/apache/cxf/interceptor/transform/TransformInInterceptor.java +++ b/core/src/main/java/org/apache/cxf/interceptor/transform/TransformInInterceptor.java @@ -21,12 +21,14 @@ import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import javax.xml.stream.XMLStreamReader; import org.apache.cxf.interceptor.StaxInInterceptor; +import org.apache.cxf.message.Exchange; import org.apache.cxf.message.Message; import org.apache.cxf.message.MessageUtils; import org.apache.cxf.phase.AbstractPhaseInterceptor; @@ -71,26 +73,51 @@ public TransformInInterceptor(String phase, List before, List af public void handleMessage(Message message) { if (contextPropertyName != null - && !MessageUtils.getContextualBoolean(message, contextPropertyName, false)) { + && !MessageUtils.getContextualBoolean(message, + contextPropertyName, false)) { return; } - XMLStreamReader reader = message.getContent(XMLStreamReader.class); + XMLStreamReader reader = + message.getContent(XMLStreamReader.class); InputStream is = message.getContent(InputStream.class); - XMLStreamReader transformReader = createTransformReaderIfNeeded(reader, is); + String encoding = getEncoding(message); + XMLStreamReader transformReader = + createTransformReaderIfNeeded(reader, is, encoding); if (transformReader != null) { message.setContent(XMLStreamReader.class, transformReader); } } - protected XMLStreamReader createTransformReaderIfNeeded(XMLStreamReader reader, InputStream is) { - return TransformUtils.createTransformReaderIfNeeded(reader, is, - inDropElements, - inElementsMap, - inAppendMap, - inAttributesMap, - blockOriginalReader); + protected XMLStreamReader createTransformReaderIfNeeded( + XMLStreamReader reader, + InputStream is, + String encoding) { + return TransformUtils.createTransformReaderIfNeeded( + reader, is, + inDropElements, + inElementsMap, + inAppendMap, + inAttributesMap, + blockOriginalReader, + encoding); + } + + private String getEncoding(Message message) { + String encoding = + (String) message.get(Message.ENCODING); + if (encoding == null) { + Exchange ex = message.getExchange(); + if (ex != null && ex.getInMessage() != null) { + encoding = (String) ex.getInMessage() + .get(Message.ENCODING); + } + } + if (encoding == null) { + encoding = StandardCharsets.UTF_8.name(); + } + return encoding; } public void setInAppendElements(Map inElements) { diff --git a/core/src/main/java/org/apache/cxf/staxutils/transform/TransformUtils.java b/core/src/main/java/org/apache/cxf/staxutils/transform/TransformUtils.java index dfe6051b1d0..cbd98fbb44f 100644 --- a/core/src/main/java/org/apache/cxf/staxutils/transform/TransformUtils.java +++ b/core/src/main/java/org/apache/cxf/staxutils/transform/TransformUtils.java @@ -42,6 +42,17 @@ public static XMLStreamReader createNewReaderIfNeeded(XMLStreamReader reader, In return reader == null ? StaxUtils.createXMLStreamReader(is) : reader; } + public static XMLStreamReader createNewReaderIfNeeded( + XMLStreamReader reader, InputStream is, String encoding) { + if (reader != null) { + return reader; + } + if (encoding != null) { + return StaxUtils.createXMLStreamReader(is, encoding); + } + return StaxUtils.createXMLStreamReader(is); + } + public static XMLStreamWriter createNewWriterIfNeeded(XMLStreamWriter writer, OutputStream os) { return createNewWriterIfNeeded(writer, os, null); } @@ -106,9 +117,22 @@ public static XMLStreamReader createTransformReaderIfNeeded(XMLStreamReader read Map inAppendMap, Map inAttributesMap, boolean blockOriginalReader) { + return createTransformReaderIfNeeded(reader, is, + inDropElements, inElementsMap, inAppendMap, + inAttributesMap, blockOriginalReader, null); + } + + public static XMLStreamReader createTransformReaderIfNeeded(XMLStreamReader reader, + InputStream is, + List inDropElements, + Map inElementsMap, + Map inAppendMap, + Map inAttributesMap, + boolean blockOriginalReader, + String encoding) { if (inElementsMap != null || inAppendMap != null || inDropElements != null || inAttributesMap != null) { - reader = new InTransformReader(createNewReaderIfNeeded(reader, is), + reader = new InTransformReader(createNewReaderIfNeeded(reader, is, encoding), inElementsMap, inAppendMap, inDropElements, inAttributesMap, blockOriginalReader); } diff --git a/core/src/test/java/org/apache/cxf/feature/transform/XSLTInterceptorsTest.java b/core/src/test/java/org/apache/cxf/feature/transform/XSLTInterceptorsTest.java index 72f6b2b7863..8f40071fc9b 100644 --- a/core/src/test/java/org/apache/cxf/feature/transform/XSLTInterceptorsTest.java +++ b/core/src/test/java/org/apache/cxf/feature/transform/XSLTInterceptorsTest.java @@ -144,6 +144,40 @@ public void outWriterStreamTest() throws Exception { Assert.assertTrue("Message was not transformed", checkTransformedXML(doc)); } + @Test + public void inStreamWithEncodingTest() throws Exception { + InputStream iso88591IS = ClassLoaderUtils + .getResourceAsStream("message-iso-8859-1.xml", + this.getClass()); + message.setContent(InputStream.class, iso88591IS); + message.put(Message.ENCODING, "ISO-8859-1"); + inInterceptor.handleMessage(message); + InputStream transformedIS = + message.getContent(InputStream.class); + Document doc = StaxUtils.read(transformedIS); + Assert.assertTrue("Message was not transformed", + checkTransformedXML(doc)); + } + + @Test + public void outStreamWithEncodingTest() throws Exception { + CachedOutputStream cos = new CachedOutputStream(); + cos.holdTempFile(); + message.setContent(OutputStream.class, cos); + message.put(Message.ENCODING, "ISO-8859-1"); + outInterceptor.handleMessage(message); + InputStream iso88591IS = ClassLoaderUtils + .getResourceAsStream("message-iso-8859-1.xml", + this.getClass()); + OutputStream os = message.getContent(OutputStream.class); + IOUtils.copy(iso88591IS, os); + os.close(); + cos.releaseTempFileHold(); + Document doc = StaxUtils.read(cos.getInputStream()); + Assert.assertTrue("Message was not transformed", + checkTransformedXML(doc)); + } + private boolean checkTransformedXML(Document doc) { NodeList list = doc.getDocumentElement() .getElementsByTagNameNS("http://customerservice.example.com/", "getCustomersByName1"); diff --git a/core/src/test/java/org/apache/cxf/feature/transform/message-iso-8859-1.xml b/core/src/test/java/org/apache/cxf/feature/transform/message-iso-8859-1.xml new file mode 100644 index 00000000000..e13197d89e8 --- /dev/null +++ b/core/src/test/java/org/apache/cxf/feature/transform/message-iso-8859-1.xml @@ -0,0 +1,8 @@ + + + + + Müller + + + From 1d9a6cf4ed32787216c244679e44ebb9cad372f1 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 12 Mar 2026 13:55:11 +0100 Subject: [PATCH 2/2] Suppress ParameterNumber checkstyle for createTransformReaderIfNeeded Co-Authored-By: Claude Opus 4.6 --- .../java/org/apache/cxf/staxutils/transform/TransformUtils.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/org/apache/cxf/staxutils/transform/TransformUtils.java b/core/src/main/java/org/apache/cxf/staxutils/transform/TransformUtils.java index cbd98fbb44f..0bbe966e96b 100644 --- a/core/src/main/java/org/apache/cxf/staxutils/transform/TransformUtils.java +++ b/core/src/main/java/org/apache/cxf/staxutils/transform/TransformUtils.java @@ -122,6 +122,7 @@ public static XMLStreamReader createTransformReaderIfNeeded(XMLStreamReader read inAttributesMap, blockOriginalReader, null); } + //CHECKSTYLE:OFF ParameterNumber public static XMLStreamReader createTransformReaderIfNeeded(XMLStreamReader reader, InputStream is, List inDropElements, @@ -139,6 +140,7 @@ public static XMLStreamReader createTransformReaderIfNeeded(XMLStreamReader read return reader; } + //CHECKSTYLE:ON ParameterNumber protected static void convertToQNamesMap(Map map, QNamesMap elementsMap,