This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch cxf-7491-charset-aware-interceptors in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 42dddeb7d851d7022dd7596a9e74600e30d437b0 Author: Guillaume Nodet <[email protected]> AuthorDate: Thu Mar 12 13:31:38 2026 +0100 [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 <[email protected]> --- .../feature/transform/AbstractXSLTInterceptor.java | 17 ++++++++ .../cxf/feature/transform/XSLTInInterceptor.java | 14 +++++-- .../cxf/feature/transform/XSLTOutInterceptor.java | 14 +++++-- .../apache/cxf/feature/transform/XSLTUtils.java | 14 ++++++- .../transform/TransformInInterceptor.java | 47 +++++++++++++++++----- .../cxf/staxutils/transform/TransformUtils.java | 26 +++++++++++- .../feature/transform/XSLTInterceptorsTest.java | 34 ++++++++++++++++ .../cxf/feature/transform/message-iso-8859-1.xml | 8 ++++ 8 files changed, 154 insertions(+), 20 deletions(-) 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 1dc91c8d76..d50af90fb1 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 @@ package org.apache.cxf.feature.transform; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import javax.xml.stream.XMLStreamException; import javax.xml.transform.Templates; @@ -31,6 +32,7 @@ import javax.xml.transform.dom.DOMSource; 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 @@ public abstract class AbstractXSLTInterceptor extends AbstractPhaseInterceptor<M 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 5e98493ffd..6ffd9e29ed 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 class XSLTInInterceptor extends AbstractXSLTInterceptor { 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 @@ public class XSLTInInterceptor extends AbstractXSLTInterceptor { 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 a7f54c80f0..c4508cda7a 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 @@ public class XSLTOutInterceptor extends AbstractXSLTInterceptor { 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 class XSLTOutInterceptor extends AbstractXSLTInterceptor { 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 class XSLTOutInterceptor extends AbstractXSLTInterceptor { 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 4b35b2ffb9..1b70ab3cf2 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 @@ public final class 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 1c0eb847b5..cb69d1bebc 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 @@ package org.apache.cxf.interceptor.transform; 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 class TransformInInterceptor extends AbstractPhaseInterceptor<Message> { 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<String, String> 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 dfe6051b1d..cbd98fbb44 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 final class TransformUtils { 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 final class TransformUtils { Map<String, String> inAppendMap, Map<String, String> inAttributesMap, boolean blockOriginalReader) { + return createTransformReaderIfNeeded(reader, is, + inDropElements, inElementsMap, inAppendMap, + inAttributesMap, blockOriginalReader, null); + } + + public static XMLStreamReader createTransformReaderIfNeeded(XMLStreamReader reader, + InputStream is, + List<String> inDropElements, + Map<String, String> inElementsMap, + Map<String, String> inAppendMap, + Map<String, String> 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 72f6b2b786..8f40071fc9 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 class XSLTInterceptorsTest { 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 0000000000..e13197d89e --- /dev/null +++ b/core/src/test/java/org/apache/cxf/feature/transform/message-iso-8859-1.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> + <soap:Body> + <cus:getCustomersByName xmlns:cus="http://customerservice.example.com/"> + <name xmlns:ns2="http://customerservice.example.com/">Müller</name> + </cus:getCustomersByName> + </soap:Body> +</soap:Envelope>
