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 3989606f99b0 CAMEL-24854: the validator reports an include/exclude
regex with a doubled backslash, which matches no file
3989606f99b0 is described below
commit 3989606f99b04e9a7e729a290df02e9ad5c7f3ec
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Sep 21 09:43:53 2026 +0200
CAMEL-24854: the validator reports an include/exclude regex with a doubled
backslash, which matches no file
From the round-2 benchmark: after the validator's hint for include:
".*\.json$"
(an unknown escape in double quotes, answered with the single-quoted form),
the model wrote include: '.*\\.json$', doubling the backslash as a Java
string would. Inside single quotes YAML keeps both, the regex then matches a
file name containing a literal backslash, no file matched, and the route ran
in silence. A person used to Java strings makes the same slip.
The endpoint checks, which already parse include/exclude on the file
components, report a value with a doubled backslash and say the form to
write.
Closes #26637
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
---
.../dsl/jbang/core/commands/ai/EndpointChecks.java | 16 +++++++++++
.../dsl/jbang/core/commands/ai/YamlLines.java | 17 +++++++++++-
.../commands/ai/SourceValidatorEndpointTest.java | 31 ++++++++++++++++++++++
3 files changed, 63 insertions(+), 1 deletion(-)
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointChecks.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointChecks.java
index 2c3c0977362b..e80081f89f14 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointChecks.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointChecks.java
@@ -306,6 +306,13 @@ final class EndpointChecks {
static final Set<String> FILE_SCHEMES = Set.of("file", "ftp", "ftps",
"sftp", "file-watch", "smb");
+ /**
+ * A doubled backslash before a character that a single backslash would
escape in a regex (\\. \\d \\( ...): the
+ * user meant the escape. A doubled backslash before any other character
(\\myfile) is left alone: \myfile is not a
+ * regex escape, so a literal backslash is the only thing it can mean.
+ */
+ static final Pattern DOUBLED_BACKSLASH_ESCAPE =
Pattern.compile("\\\\\\\\[.dswDSWbB()\\[\\]{}+*?|^$]");
+
/**
* include and exclude on the file components are regular expressions:
include=*.txt fails at startup with a
* PatternSyntaxException wrapped in a binding error. Says to write
.*\\.txt or use antInclude.
@@ -326,6 +333,15 @@ final class EndpointChecks {
if (!name.equals("include") && !name.equals("exclude") ||
value.startsWith("{{")) {
continue;
}
+ if (DOUBLED_BACKSLASH_ESCAPE.matcher(value).find()) {
+ // '.*\\.json$' in single quotes: YAML keeps both backslashes,
and in a regex \\ is one literal
+ // backslash, so the pattern matches a file name with a
backslash in it: no file matches and the route
+ // runs in silence (CAMEL-24854)
+ errors.add(linePrefix(optionLineMap.getOrDefault(name,
uriLineIdx)) + fullUri.substring(0, colon) + ": "
+ + name + "=" + value + " matches a literal
backslash in the file name (in a regex \\\\ is one"
+ + " backslash and \\. is a dot): write " + name +
"='" + value.replace("\\\\", "\\") + "'");
+ continue;
+ }
try {
Pattern.compile(value);
} catch (java.util.regex.PatternSyntaxException e) {
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/YamlLines.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/YamlLines.java
index 0f245b07c3d7..9d3f43da61de 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/YamlLines.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/YamlLines.java
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.jbang.core.commands.ai;
import java.util.regex.Pattern;
import org.apache.camel.util.StringHelper;
+import org.apache.camel.util.json.Jsoner;
/**
* Line-level helpers over a YAML source shared by the checks of {@link
SourceValidator}: the enclosing EIP of a line,
@@ -157,9 +158,23 @@ final class YamlLines {
&& val.substring(1).chars().allMatch(c -> c == '-' || c == '+'
|| Character.isDigit(c));
}
+ /**
+ * The value of a quoted scalar: inside double quotes YAML reads \\ as one
backslash and \" as a quote (so
+ * ".*\\.pdf" is the regex .*\.pdf), inside single quotes a backslash is a
backslash.
+ */
static String unquote(String val) {
if (val.length() >= 2 && val.startsWith("\"") && val.endsWith("\"")) {
- return val.substring(1, val.length() - 1);
+ String inner = val.substring(1, val.length() - 1);
+ if (inner.indexOf('\\') < 0) {
+ return inner;
+ }
+ try {
+ // the JSON escapes are the YAML ones that matter here (\\ \"
\n \t and unicode)
+ return Jsoner.unescape(inner);
+ } catch (RuntimeException e) {
+ // a YAML-only escape such as \e or \x41: the text as written
+ return inner;
+ }
}
if (val.length() >= 2 && val.startsWith("'") && val.endsWith("'")) {
return val.substring(1, val.length() - 1);
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java
index 767162c3e192..88e23e0c18f6 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java
@@ -592,4 +592,35 @@ class SourceValidatorEndpointTest {
assertThat(SourceValidator.validateYamlEndpoints(fromYaml, catalog))
.anyMatch(e -> e.startsWith("Line 3: file: the directory
archived/${header.monthDir} cannot be dynamic"));
}
+
+ /** CAMEL-24854: a doubled backslash in an include regex (kept as is
inside single quotes) matches no file. */
+ @Test
+ void aDoubledBackslashInAnIncludeRegexIsReported() {
+ String yaml = """
+ - route:
+ from:
+ uri: file:orders
+ parameters:
+ include: '.*\\\\.json$'
+ steps:
+ - to:
+ uri: log:done
+ """;
+ List<String> errors = SourceValidator.validateYamlEndpoints(yaml,
catalog);
+ assertThat(errors)
+ .anyMatch(e -> e.startsWith("Line 5: file:
include=.*\\\\.json$ matches a literal backslash in the file name")
+ && e.endsWith("write include='.*\\.json$'"));
+
+ List<String> ok =
SourceValidator.validateYamlEndpoints(yaml.replace("\\\\.json", "\\.json"),
catalog);
+ assertThat(ok).noneMatch(e -> e.contains("backslash"));
+
+ // in double quotes YAML reads \\ as one backslash: ".*\\.json$" is
the regex .*\.json$, nothing to report
+ List<String> doubleQuoted
+ =
SourceValidator.validateYamlEndpoints(yaml.replace("'.*\\\\.json$'",
"\".*\\\\.json$\""), catalog);
+ assertThat(doubleQuoted).noneMatch(e -> e.contains("backslash"));
+
+ // \\myfile is a backslash on purpose: \myfile is not a regex escape,
so there is nothing else it can mean
+ List<String> literal =
SourceValidator.validateYamlEndpoints(yaml.replace(".*\\\\.json$",
".*\\\\myfile.*"), catalog);
+ assertThat(literal).noneMatch(e -> e.contains("backslash"));
+ }
}