This is an automated email from the ASF dual-hosted git repository. Croway pushed a commit to branch camel-4.18.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit 6653df8e65e685cb103eacf4f749d81e79a00265 Author: Andrea Cosentino <[email protected]> AuthorDate: Mon Aug 31 10:24:07 2026 +0200 CAMEL-24423: camel-tika - filter parsed document metadata before mapping it to headers (#25819) TikaProducer.convertMetadataToHeaders() copied every metadata name produced by the parse straight onto the Camel message. Those names come out of the document itself, so a document could ask for any header name at all, including names in the Camel-internal namespace - an HTML <meta name="CamelFileName" content="../../x"/> reached the message as CamelFileName and would then be picked up by a later file: producer. Filter the names the same way a consumer filters names supplied by an external sender: a DefaultHeaderFilterStrategy with lowerCase matching and inFilterStartsWith of Camel, camel and org.apache.camel. A filtered name is skipped and logged at DEBUG. Metadata outside that namespace is mapped exactly as before. Filtering rather than prefixing all parsed metadata keeps the change small enough to backport; prefixing would rename every header the component produces today. Signed-off-by: Andrea Cosentino <[email protected]> (cherry picked from commit b6f6b4708543677a8b6032304c66cbe7f687a2fb) Co-authored-by: Claude Opus 5 (1M context) <[email protected]> --- components/camel-tika/pom.xml | 5 ++ .../apache/camel/component/tika/TikaProducer.java | 25 ++++++-- .../tika/TikaMetadataHeaderFilterTest.java | 74 ++++++++++++++++++++++ 3 files changed, 100 insertions(+), 4 deletions(-) diff --git a/components/camel-tika/pom.xml b/components/camel-tika/pom.xml index 821a6e2d9a6d..0c1b82a3c625 100644 --- a/components/camel-tika/pom.xml +++ b/components/camel-tika/pom.xml @@ -86,6 +86,11 @@ <version>${hamcrest-version}</version> <scope>test</scope> </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> </dependencies> </project> diff --git a/components/camel-tika/src/main/java/org/apache/camel/component/tika/TikaProducer.java b/components/camel-tika/src/main/java/org/apache/camel/component/tika/TikaProducer.java index 9328dc47d344..6d8f2c2625c2 100644 --- a/components/camel-tika/src/main/java/org/apache/camel/component/tika/TikaProducer.java +++ b/components/camel-tika/src/main/java/org/apache/camel/component/tika/TikaProducer.java @@ -35,6 +35,8 @@ import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.apache.camel.Exchange; +import org.apache.camel.spi.HeaderFilterStrategy; +import org.apache.camel.support.DefaultHeaderFilterStrategy; import org.apache.camel.support.DefaultProducer; import org.apache.tika.config.TikaConfig; import org.apache.tika.detect.Detector; @@ -53,6 +55,8 @@ public class TikaProducer extends DefaultProducer { private static final Logger LOG = LoggerFactory.getLogger(TikaProducer.class); + private static final HeaderFilterStrategy HEADER_FILTER_STRATEGY = createHeaderFilterStrategy(); + private final TikaConfiguration tikaConfiguration; private final Parser parser; @@ -127,11 +131,15 @@ public class TikaProducer extends DefaultProducer { if (metadata != null) { for (String metaname : metadata.names()) { String[] values = metadata.getValues(metaname); - if (values.length == 1) { - exchange.getIn().setHeader(metaname, values[0]); - } else { - exchange.getIn().setHeader(metaname, values); + Object value = values.length == 1 ? values[0] : values; + // The names come out of the parsed document, so they are chosen by whoever produced it. + // Filter them the same way a consumer filters names supplied by an external sender, so a + // document cannot declare a metadata name that lands in the Camel-internal namespace. + if (HEADER_FILTER_STRATEGY.applyFilterToExternalHeaders(metaname, value, exchange)) { + LOG.debug("Skipping parsed metadata {} as the name is in the Camel-internal namespace", metaname); + continue; } + exchange.getIn().setHeader(metaname, value); } } } @@ -178,4 +186,13 @@ public class TikaProducer extends DefaultProducer { return handler; } + + private static HeaderFilterStrategy createHeaderFilterStrategy() { + DefaultHeaderFilterStrategy strategy = new DefaultHeaderFilterStrategy(); + // Match case-insensitively, and cover the fully qualified form as well as the Camel prefix + strategy.setLowerCase(true); + strategy.setInFilterStartsWith("Camel", "camel", "org.apache.camel."); + return strategy; + } + } diff --git a/components/camel-tika/src/test/java/org/apache/camel/component/tika/TikaMetadataHeaderFilterTest.java b/components/camel-tika/src/test/java/org/apache/camel/component/tika/TikaMetadataHeaderFilterTest.java new file mode 100644 index 000000000000..ba4b85e9b44b --- /dev/null +++ b/components/camel-tika/src/test/java/org/apache/camel/component/tika/TikaMetadataHeaderFilterTest.java @@ -0,0 +1,74 @@ +/* + * 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.tika; + +import java.nio.charset.StandardCharsets; + +import org.apache.camel.EndpointInject; +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The metadata names handed to {@code convertMetadataToHeaders} come out of the parsed document, so they are chosen by + * whoever produced it. An HTML {@code <meta name="..">} is the most direct way to demonstrate that: the name attribute + * reaches Tika's metadata verbatim, so a document can ask for any header name at all. + */ +class TikaMetadataHeaderFilterTest extends CamelTestSupport { + + @EndpointInject("mock:result") + protected MockEndpoint resultEndpoint; + + @Test + void documentMetadataCannotSetCamelInternalHeaders() throws Exception { + String html = "<html><head>" + + "<meta name=\"CamelFileName\" content=\"../../pwned\"/>" + + "<meta name=\"camelfilename\" content=\"../../pwned\"/>" + + "<meta name=\"CAMELHttpUri\" content=\"http://other.example/x\"/>" + + "<meta name=\"org.apache.camel.internal\" content=\"nope\"/>" + + "<meta name=\"author\" content=\"kept\"/>" + + "<title>t</title></head><body>hi</body></html>"; + + resultEndpoint.setExpectedMessageCount(1); + template.sendBody("direct:start", html.getBytes(StandardCharsets.UTF_8)); + resultEndpoint.assertIsSatisfied(); + + Exchange exchange = resultEndpoint.getExchanges().get(0); + assertThat(exchange.getIn().getHeader(Exchange.FILE_NAME)).isNull(); + assertThat(exchange.getIn().getHeader("camelfilename")).isNull(); + assertThat(exchange.getIn().getHeader("CAMELHttpUri")).isNull(); + assertThat(exchange.getIn().getHeader("org.apache.camel.internal")).isNull(); + + // metadata outside the internal namespace is still mapped, so the filter has not simply dropped everything + assertThat(exchange.getIn().getHeader("author")).isEqualTo("kept"); + assertThat(exchange.getIn().getHeader("dc:title")).isEqualTo("t"); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:start").to("tika:parse").to("mock:result"); + } + }; + } +}
