This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new a439c1ab3912 CAMEL-25787: camel-smooks - handle Smooks Source bodies 
like other XML message bodies (#28095)
a439c1ab3912 is described below

commit a439c1ab39125a7fa19a0e54b8c646b46bf02db7
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Oct 19 16:29:19 2026 +0200

    CAMEL-25787: camel-smooks - handle Smooks Source bodies like other XML 
message bodies (#28095)
    
    Co-authored-by: Claude Opus 5.5 <[email protected]>
---
 .../camel/component/smooks/SmooksProcessor.java    |  11 +-
 .../component/smooks/SmooksSecuritySupport.java    |  80 +++++++++-
 .../component/smooks/SmooksReaderConfigTest.java   |  91 ++++++++++++
 .../component/smooks/SmooksSourceBodyTest.java     | 165 +++++++++++++++++++++
 .../reader-without-class-smooks-config.xml         |  42 ++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  22 +++
 6 files changed, 401 insertions(+), 10 deletions(-)

diff --git 
a/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksProcessor.java
 
b/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksProcessor.java
index 1f5124526b01..eefdf46f0391 100644
--- 
a/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksProcessor.java
+++ 
b/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksProcessor.java
@@ -153,11 +153,11 @@ public class SmooksProcessor extends ServiceSupport 
implements Processor, CamelC
             final Exports exports = 
smooks.getApplicationContext().getRegistry().lookup(new ExportsLookup());
             if (exports.hasExports()) {
                 final Sink[] sinks = exports.createSinks();
-                smooks.filterSource(executionContext, getSource(exchange), 
sinks);
+                smooks.filterSource(executionContext, getSource(exchange, 
executionContext.getContentEncoding()), sinks);
                 setResultOnBody(exports, sinks, exchange);
             } else {
                 final OutputStreamBuilder outputStreamBuilder = 
OutputStreamBuilder.withExchange(exchange);
-                smooks.filterSource(executionContext, getSource(exchange),
+                smooks.filterSource(executionContext, getSource(exchange, 
executionContext.getContentEncoding()),
                         new StreamSink<>(outputStreamBuilder));
                 exchange.getMessage().setBody(outputStreamBuilder.build());
             }
@@ -191,10 +191,10 @@ public class SmooksProcessor extends ServiceSupport 
implements Processor, CamelC
         }
     }
 
-    private Source getSource(final Exchange exchange) throws 
InvalidPayloadException {
+    private Source getSource(final Exchange exchange, final String 
contentEncoding) throws InvalidPayloadException {
         final Object payload = exchange.getIn().getBody();
 
-        if (payload instanceof Source source) {
+        if (payload instanceof Source source && !hardenXmlInput) {
             return source;
         }
 
@@ -205,6 +205,9 @@ public class SmooksProcessor extends ServiceSupport 
implements Processor, CamelC
         if (hardenXmlInput) {
             // Untrusted XML: parse with a reader that does not resolve 
external entities before Smooks sees it
             try {
+                if (payload instanceof Source source) {
+                    return SmooksSecuritySupport.secureXmlSource(source, 
contentEncoding);
+                }
                 return 
SmooksSecuritySupport.secureXmlSource(exchange.getIn().getMandatoryBody(InputStream.class));
             } catch (IOException | SAXException e) {
                 throw new SmooksException(e);
diff --git 
a/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksSecuritySupport.java
 
b/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksSecuritySupport.java
index a8d7174b1410..0ec1e4c57d45 100644
--- 
a/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksSecuritySupport.java
+++ 
b/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksSecuritySupport.java
@@ -18,6 +18,12 @@ package org.apache.camel.component.smooks;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PushbackReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 
 import javax.xml.XMLConstants;
@@ -26,17 +32,23 @@ import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 
 import org.w3c.dom.Document;
+import org.w3c.dom.DocumentType;
 
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
 
 import org.smooks.Smooks;
 import org.smooks.api.Registry;
+import org.smooks.api.SmooksException;
 import org.smooks.api.io.Source;
 import org.smooks.api.resource.config.ResourceConfig;
 import org.smooks.engine.lookup.ResourceConfigsLookup;
 import org.smooks.engine.resource.config.DefaultConfigSearch;
 import org.smooks.io.source.DOMSource;
+import org.smooks.io.source.JavaSource;
+import org.smooks.io.source.ReaderSource;
+import org.smooks.io.source.StreamSource;
+import org.smooks.io.source.URLSource;
 
 /**
  * Secures the XML input handed to Smooks against XML external entity 
resolution.
@@ -48,8 +60,8 @@ import org.smooks.io.source.DOMSource;
  * applied by the other Camel XML components.
  * <p/>
  * Hardening is only relevant when the Smooks configuration parses XML with 
its default reader. Configurations that
- * declare their own reader (for example EDI, CSV, JSON, or a custom reader) 
do not use the XML reader and are left
- * untouched.
+ * declare their own reader class (for example EDI, CSV, JSON, or a custom 
reader) do not use the XML reader and are
+ * left untouched.
  */
 public final class SmooksSecuritySupport {
 
@@ -58,6 +70,7 @@ public final class SmooksSecuritySupport {
     private static final String EXTERNAL_GENERAL_ENTITIES = 
"http://xml.org/sax/features/external-general-entities";;
     private static final String EXTERNAL_PARAMETER_ENTITIES = 
"http://xml.org/sax/features/external-parameter-entities";;
     private static final String LOAD_EXTERNAL_DTD = 
"http://apache.org/xml/features/nonvalidating/load-external-dtd";;
+    private static final char BYTE_ORDER_MARK = '\uFEFF';
 
     private static final DocumentBuilderFactory 
SECURE_DOCUMENT_BUILDER_FACTORY = createSecureDocumentBuilderFactory();
 
@@ -66,14 +79,15 @@ public final class SmooksSecuritySupport {
 
     /**
      * Returns {@code true} when the given Smooks instance parses input with 
its default XML reader, i.e. the
-     * configuration does not declare its own reader (such as EDI, CSV, JSON, 
or a custom reader). Only the default XML
-     * reader resolves XML external entities, so only that case needs 
hardening.
+     * configuration does not declare a reader class (such as EDI, CSV, JSON, 
or a custom reader). A reader declared
+     * without a class only sets handlers, features or parameters of the 
default XML reader. Only the default XML reader
+     * resolves XML external entities, so only that case needs hardening.
      */
     public static boolean usesDefaultXmlReader(Smooks smooks) {
         Registry registry = smooks.getApplicationContext().getRegistry();
         List<ResourceConfig> readerConfigs
                 = registry.lookup(new ResourceConfigsLookup(registry, new 
DefaultConfigSearch().selector(ORG_XML_SAX_DRIVER)));
-        return readerConfigs == null || readerConfigs.isEmpty();
+        return readerConfigs == null || 
readerConfigs.stream().allMatch(readerConfig -> readerConfig.getResource() == 
null);
     }
 
     /**
@@ -81,6 +95,49 @@ public final class SmooksSecuritySupport {
      * {@link Source} backed by the resulting DOM.
      */
     public static Source secureXmlSource(InputStream inputStream) throws 
IOException, SAXException {
+        return secureXmlSource(new InputSource(inputStream));
+    }
+
+    /**
+     * Parses the XML of the given Smooks {@link Source} like {@link 
#secureXmlSource(InputStream)}. The bytes of a
+     * stream or URL source are decoded with the given content encoding, as 
Smooks decodes them. A {@link DOMSource} or
+     * a {@link JavaSource} holds no XML text and is returned as it is; any 
other type of source is rejected.
+     */
+    static Source secureXmlSource(Source source, String contentEncoding) 
throws IOException, SAXException {
+        if (source instanceof DOMSource || source instanceof JavaSource) {
+            return source;
+        }
+        if (source instanceof ReaderSource<?> readerSource) {
+            return secureXmlSource(readerSource.getReader(), null);
+        }
+        if (source instanceof StreamSource<?> streamSource) {
+            return secureXmlSource(toReader(streamSource.getInputStream(), 
contentEncoding), null);
+        }
+        if (source instanceof URLSource urlSource) {
+            URL url = urlSource.getURL();
+            try (InputStream inputStream = url.openStream()) {
+                return secureXmlSource(toReader(inputStream, contentEncoding), 
url.toExternalForm());
+            }
+        }
+        throw new SmooksException(
+                "A message body of type " + source.getClass().getName()
+                                  + " is not supported with 
allowExternalEntities=false. Use a String, byte[] or InputStream"
+                                  + " body, or a StreamSource, ReaderSource, 
URLSource, DOMSource or JavaSource body.");
+    }
+
+    private static Source secureXmlSource(Reader reader, String systemId) 
throws IOException, SAXException {
+        // Skip a leading byte order mark as the default reader of Smooks 
does: the JDK parser rejects it in characters
+        PushbackReader pushbackReader = new PushbackReader(reader);
+        int first = pushbackReader.read();
+        if (first != -1 && first != BYTE_ORDER_MARK) {
+            pushbackReader.unread(first);
+        }
+        InputSource inputSource = new InputSource(pushbackReader);
+        inputSource.setSystemId(systemId);
+        return secureXmlSource(inputSource);
+    }
+
+    private static Source secureXmlSource(InputSource inputSource) throws 
IOException, SAXException {
         DocumentBuilder documentBuilder;
         synchronized (SECURE_DOCUMENT_BUILDER_FACTORY) {
             try {
@@ -89,10 +146,21 @@ public final class SmooksSecuritySupport {
                 throw new SAXException(e);
             }
         }
-        Document document = documentBuilder.parse(new 
InputSource(inputStream));
+        Document document = documentBuilder.parse(inputSource);
+        // The DOM reader of Smooks rejects a document type node, and the 
parser has already applied what it declares
+        DocumentType documentType = document.getDoctype();
+        if (documentType != null) {
+            document.removeChild(documentType);
+        }
         return new DOMSource(document);
     }
 
+    private static Reader toReader(InputStream inputStream, String 
contentEncoding) throws UnsupportedEncodingException {
+        return contentEncoding != null
+                ? new InputStreamReader(inputStream, contentEncoding)
+                : new InputStreamReader(inputStream, StandardCharsets.UTF_8);
+    }
+
     private static DocumentBuilderFactory createSecureDocumentBuilderFactory() 
{
         DocumentBuilderFactory documentBuilderFactory = 
DocumentBuilderFactory.newInstance();
         documentBuilderFactory.setNamespaceAware(true);
diff --git 
a/components/camel-smooks/src/test/java/org/apache/camel/component/smooks/SmooksReaderConfigTest.java
 
b/components/camel-smooks/src/test/java/org/apache/camel/component/smooks/SmooksReaderConfigTest.java
new file mode 100644
index 000000000000..8126f7d99b4d
--- /dev/null
+++ 
b/components/camel-smooks/src/test/java/org/apache/camel/component/smooks/SmooksReaderConfigTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.smooks;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.dataformat.smooks.Customer;
+import org.apache.camel.dataformat.smooks.SmooksDataFormat;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * A {@code <reader>} declared without a class sets handlers, features or 
parameters of the default XML reader of
+ * Smooks, so the {@code allowExternalEntities} option applies as it does to a 
configuration that declares no reader.
+ */
+class SmooksReaderConfigTest extends CamelTestSupport {
+
+    private static final String CONFIG = 
"reader-without-class-smooks-config.xml";
+    private static final String ENTITY_TEXT = "Text of the external entity";
+
+    @TempDir
+    Path tempDir;
+
+    private String customerXml;
+
+    @BeforeEach
+    void writeEntityFile() throws Exception {
+        Path entityFile = Files.writeString(tempDir.resolve("first-name.txt"), 
ENTITY_TEXT);
+        customerXml = "<!DOCTYPE customer [<!ENTITY name SYSTEM \"" + 
entityFile.toUri() + "\">]>"
+                      + 
"<customer><firstName>&name;</firstName><lastName>Smith</lastName></customer>";
+    }
+
+    @Test
+    void readerConfigWithoutClassFollowsAllowExternalEntities() {
+        assertFollowsAllowExternalEntities("direct:customer", 
"direct:customerWithExternalEntities");
+    }
+
+    @Test
+    void dataFormatReaderConfigWithoutClassFollowsAllowExternalEntities() {
+        assertFollowsAllowExternalEntities("direct:unmarshal", 
"direct:unmarshalWithExternalEntities");
+    }
+
+    private void assertFollowsAllowExternalEntities(String endpoint, String 
endpointWithExternalEntities) {
+        Customer customer = template.requestBody(endpoint, customerXml, 
Customer.class);
+        assertEquals("Smith", customer.getLastName());
+        assertEquals("", customer.getFirstName(), "The external entity must 
not be read by default");
+
+        customer = template.requestBody(endpointWithExternalEntities, 
customerXml, Customer.class);
+        assertEquals(ENTITY_TEXT, customer.getFirstName());
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:customer").to("smooks://" + CONFIG);
+                from("direct:customerWithExternalEntities").to("smooks://" + 
CONFIG + "?allowExternalEntities=true");
+                from("direct:unmarshal").unmarshal(smooksDataFormat(false));
+                
from("direct:unmarshalWithExternalEntities").unmarshal(smooksDataFormat(true));
+            }
+        };
+    }
+
+    private static SmooksDataFormat smooksDataFormat(boolean 
allowExternalEntities) {
+        SmooksDataFormat dataFormat = new SmooksDataFormat();
+        dataFormat.setSmooksConfig("/" + CONFIG);
+        dataFormat.setAllowExternalEntities(allowExternalEntities);
+        return dataFormat;
+    }
+}
diff --git 
a/components/camel-smooks/src/test/java/org/apache/camel/component/smooks/SmooksSourceBodyTest.java
 
b/components/camel-smooks/src/test/java/org/apache/camel/component/smooks/SmooksSourceBodyTest.java
new file mode 100644
index 000000000000..505a12589bbd
--- /dev/null
+++ 
b/components/camel-smooks/src/test/java/org/apache/camel/component/smooks/SmooksSourceBodyTest.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.smooks;
+
+import java.io.ByteArrayInputStream;
+import java.io.StringReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.function.Supplier;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.dataformat.smooks.Customer;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.smooks.Smooks;
+import org.smooks.api.SmooksException;
+import org.smooks.api.io.Source;
+import org.smooks.io.payload.Exports;
+import org.smooks.io.sink.StringSink;
+import org.smooks.io.source.ByteSource;
+import org.smooks.io.source.DOMSource;
+import org.smooks.io.source.JavaSource;
+import org.smooks.io.source.ReaderSource;
+import org.smooks.io.source.StreamSource;
+import org.smooks.io.source.StringSource;
+import org.smooks.io.source.URLSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A message body that is already a Smooks {@link Source} follows the {@code 
allowExternalEntities} option like the
+ * other XML message bodies.
+ */
+class SmooksSourceBodyTest extends CamelTestSupport {
+
+    private static final String ENTITY_TEXT = "Text of the external entity";
+
+    @TempDir
+    Path tempDir;
+
+    private String customerXml;
+
+    @BeforeEach
+    void writeEntityFile() throws Exception {
+        Path entityFile = Files.writeString(tempDir.resolve("first-name.txt"), 
ENTITY_TEXT);
+        customerXml = "<!DOCTYPE customer [<!ENTITY name SYSTEM \"" + 
entityFile.toUri() + "\">]>"
+                      + 
"<customer><firstName>&name;</firstName><lastName>Smith</lastName></customer>";
+    }
+
+    @Test
+    void stringSourceBodyFollowsAllowExternalEntities() {
+        assertFollowsAllowExternalEntities(() -> new 
StringSource(customerXml));
+    }
+
+    @Test
+    void readerSourceBodyFollowsAllowExternalEntities() {
+        assertFollowsAllowExternalEntities(() -> new ReaderSource<>(new 
StringReader(customerXml)));
+    }
+
+    @Test
+    void streamSourceBodyFollowsAllowExternalEntities() {
+        assertFollowsAllowExternalEntities(
+                () -> new StreamSource<>(new 
ByteArrayInputStream(customerXml.getBytes(StandardCharsets.UTF_8))));
+    }
+
+    @Test
+    void byteSourceBodyFollowsAllowExternalEntities() {
+        assertFollowsAllowExternalEntities(() -> new 
ByteSource(customerXml.getBytes(StandardCharsets.UTF_8)));
+    }
+
+    @Test
+    void urlSourceBodyFollowsAllowExternalEntities() throws Exception {
+        URL url = Files.writeString(tempDir.resolve("customer.xml"), 
customerXml).toUri().toURL();
+        assertFollowsAllowExternalEntities(() -> new URLSource(url));
+    }
+
+    @Test
+    void internalEntitiesAreExpandedInStringAndSourceBodies() {
+        String xml = "<!DOCTYPE customer [<!ENTITY name \"Ann\">]>"
+                     + 
"<customer><firstName>&name;</firstName><lastName>Smith</lastName></customer>";
+        assertEquals("Ann", template.requestBody("direct:customer", xml, 
Customer.class).getFirstName());
+        assertEquals("Ann", template.requestBody("direct:customer", new 
StringSource(xml), Customer.class).getFirstName());
+    }
+
+    @Test
+    void streamSourceBodyWithByteOrderMarkIsParsed() {
+        byte[] xml = 
"\uFEFF<customer><lastName>Smith</lastName></customer>".getBytes(StandardCharsets.UTF_8);
+        Customer customer = template.requestBody("direct:customer", new 
ByteSource(xml), Customer.class);
+        assertEquals("Smith", customer.getLastName());
+    }
+
+    @Test
+    void domSourceAndJavaSourceBodiesAreProcessedAsGiven() throws Exception {
+        Document order = 
DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+        Element id = order.createElementNS(null, "id");
+        id.setTextContent("1");
+        order.appendChild(order.createElementNS(null, 
"order")).appendChild(id);
+        assertEquals("<order><id>1</id></order>", 
template.requestBody("direct:toXml", new DOMSource(order), String.class));
+
+        String coordinate = template.requestBody("direct:toXml", new 
JavaSource(new Coordinate(111, 222)), String.class);
+        assertTrue(coordinate.contains("<x>111</x>") && 
coordinate.contains("<y>222</y>"), coordinate);
+    }
+
+    @Test
+    void unsupportedSourceBodyIsRejected() {
+        Source body = new Source() {
+        };
+        CamelExecutionException e
+                = assertThrows(CamelExecutionException.class, () -> 
template.requestBody("direct:customer", body));
+        SmooksException cause = assertInstanceOf(SmooksException.class, 
e.getCause());
+        assertTrue(cause.getMessage().startsWith("A message body of type " + 
body.getClass().getName() + " is not supported"),
+                cause.getMessage());
+    }
+
+    private void assertFollowsAllowExternalEntities(Supplier<Source> body) {
+        Customer customer = template.requestBody("direct:customer", 
body.get(), Customer.class);
+        assertEquals("Smith", customer.getLastName());
+        assertEquals("", customer.getFirstName(), "The external entity must 
not be read by default");
+
+        customer = template.requestBody("direct:customerWithExternalEntities", 
body.get(), Customer.class);
+        assertEquals(ENTITY_TEXT, customer.getFirstName());
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            @SuppressWarnings("resource")
+            // NOTE: the context will take care to clean resources
+            public void configure() {
+                from("direct:customer").to("smooks://smooks-config.xml");
+                
from("direct:customerWithExternalEntities").to("smooks://smooks-config.xml?allowExternalEntities=true");
+                from("direct:toXml")
+                        .process(new SmooksProcessor(new 
Smooks().setExports(new Exports(StringSink.class)), context));
+            }
+        };
+    }
+}
diff --git 
a/components/camel-smooks/src/test/resources/reader-without-class-smooks-config.xml
 
b/components/camel-smooks/src/test/resources/reader-without-class-smooks-config.xml
new file mode 100644
index 000000000000..d477060a87f2
--- /dev/null
+++ 
b/components/camel-smooks/src/test/resources/reader-without-class-smooks-config.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+
+<smooks-resource-list
+       xmlns:core="https://www.smooks.org/xsd/smooks/smooks-core-1.6.xsd";
+       xmlns="https://www.smooks.org/xsd/smooks-2.0.xsd";
+       xmlns:jb="https://www.smooks.org/xsd/smooks/javabean-1.6.xsd";>
+
+    <!-- A reader without a class sets features of the default XML reader -->
+    <reader>
+        <features>
+            <setOn feature="http://xml.org/sax/features/namespaces"/>
+        </features>
+    </reader>
+
+    <jb:bean beanId="cust" class="org.apache.camel.dataformat.smooks.Customer" 
createOnElement="customer">
+        <jb:value property="firstName" data="firstName"/>
+        <jb:value property="lastName" data="lastName"/>
+    </jb:bean>
+
+    <core:exports>
+        <core:result type="org.smooks.io.sink.JavaSink" extract="cust"/>
+    </core:exports>
+
+</smooks-resource-list>
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 32969ae2039e..8cd5c733d623 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -849,6 +849,28 @@ It is now written with all its decimals, so a record that 
is unmarshalled and ma
 `Double` that is the result of a calculation is written with all its digits 
too (`0.1 + 0.2` as `0.30000000000000004`,
 before `0.3`). Set `precision` to get a fixed number of decimals, as before.
 
+=== camel-smooks - allowExternalEntities applies to Smooks Source bodies and 
to a reader declared without a class
+
+The Smooks component and `SmooksProcessor` now parse a message body that is 
already a Smooks `Source`
+(`org.smooks.io.source.StringSource`, `ReaderSource`, `StreamSource`, 
`ByteSource` or `URLSource`) with the same XML
+reader settings as the other message bodies, so external entities in it are 
only resolved when `allowExternalEntities`
+is `true`. Before, such a body was handed to Smooks as it was. The bytes of a 
`StreamSource`, `ByteSource` or
+`URLSource` are decoded with the exchange charset, or UTF-8, as before. These 
bodies are now parsed into a DOM before
+Smooks processes them, which uses more memory for very large inputs. 
`DOMSource` and `JavaSource` bodies are unchanged.
+
+A Smooks configuration whose `<reader>` declares no `class` only sets 
handlers, features or parameters of the default
+XML reader. The Smooks component, `SmooksProcessor` and the Smooks data format 
now apply `allowExternalEntities` to
+such a configuration as they do to a configuration without a `<reader>`, so 
the handlers and features of that reader no
+longer take part in parsing the XML unless `allowExternalEntities` is `true`. 
Configurations that declare a reader
+class, such as EDI, CSV, JSON, DFDL or a custom reader, are unchanged.
+
+A document with a document type declaration (`<!DOCTYPE ...>`) is now 
processed with these settings for every body
+type, `String`, `byte[]` and `InputStream` bodies included, which failed 
before: the entities of its internal subset are
+expanded, and the declaration itself is not passed on to Smooks.
+
+Set `allowExternalEntities=true` on the endpoint or on the data format for 
documents that rely on external entities or
+an external DTD.
+
 === camel-ical - each unmarshal parses with its own calendar builder
 
 The `ical` data format used one ical4j `CalendarBuilder` for all exchanges. 
Two exchanges that were unmarshalled at

Reply via email to