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&#xFC;ller</name>
+        </cus:getCustomersByName>
+    </soap:Body>
+</soap:Envelope>

Reply via email to