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"));
+    }
 }

Reply via email to