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