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

jsinovassin pushed a commit to branch UNOMI-973-review-followups
in repository https://gitbox.apache.org/repos/asf/unomi.git

commit c7bce26074cf50860f45b0fc8ce4a02715a341ad
Author: jsinovassin <[email protected]>
AuthorDate: Tue Sep 1 18:35:22 2026 +0200

    UNOMI-973: Append moveFailed as an option, not to the directory name
    
    The router appends its own moveFailed to the configured source with an
    unconditional '&'. A source that carries no query has no separator to append
    to, so the option became part of the directory the endpoint names:
    file:///base turned into file:///base&moveFailed=.error, a directory called
    "base&moveFailed=.error".
    
    That was a malformed endpoint before this validation existed; with it, the
    two layers disagree. saveConfiguration validates the source as configured
    and answers 200, then the route builder validates the source with the option
    appended, refuses it and marks the configuration INVALID_ENDPOINT -- stored,
    answered 200, never run, which is what refusing at save time exists to
    prevent.
    
    The separator now depends on whether the source already carries a query. The
    two layers agree again in every case, since .error is relative and lands
    under the endpoint's own directory.
---
 .../router/core/route/ProfileImportFromSourceRouteBuilder.java   | 4 +++-
 .../unomi/router/core/route/FileEndpointContainmentTest.java     | 9 +++++++++
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git 
a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportFromSourceRouteBuilder.java
 
b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportFromSourceRouteBuilder.java
index 4c468a659..c226b7717 100644
--- 
a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportFromSourceRouteBuilder.java
+++ 
b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportFromSourceRouteBuilder.java
@@ -94,7 +94,9 @@ public class ProfileImportFromSourceRouteBuilder extends 
RouterAbstractRouteBuil
 
                 String endpoint = (String) 
importConfiguration.getProperties().get("source");
                 if (StringUtils.isNotBlank(endpoint)) {
-                    endpoint += "&moveFailed=.error";
+                    // the separator depends on whether the source already 
carries a query: appending
+                    // '&' to a source that has none makes the option part of 
the directory name
+                    endpoint += (endpoint.indexOf('?') < 0 ? "?" : "&") + 
"moveFailed=.error";
                 }
 
                 String refusal = EndpointValidator.validate(endpoint, 
allowedEndpoints, permittedBaseDirs);
diff --git 
a/extensions/router/router-core/src/test/java/org/apache/unomi/router/core/route/FileEndpointContainmentTest.java
 
b/extensions/router/router-core/src/test/java/org/apache/unomi/router/core/route/FileEndpointContainmentTest.java
index 97ad266bc..85e22b368 100644
--- 
a/extensions/router/router-core/src/test/java/org/apache/unomi/router/core/route/FileEndpointContainmentTest.java
+++ 
b/extensions/router/router-core/src/test/java/org/apache/unomi/router/core/route/FileEndpointContainmentTest.java
@@ -165,6 +165,15 @@ public class FileEndpointContainmentTest {
                 "a parent segment applies to the target of the link that 
precedes it, not to the link's own parent");
     }
 
+    @Test
+    public void importRouteIsBuiltWhenSourceCarriesNoOption() throws Exception 
{
+        // the router appends moveFailed itself; a source with no query has no 
separator to append to,
+        // and the option would otherwise become part of the directory the 
endpoint names
+        addImportRoutes(recurrentImport("no-option", 
fileUri(permittedImportDir, "")));
+
+        assertRouteBuilt("no-option", "a source may name a directory and 
nothing else");
+    }
+
     @Test
     public void importRouteIsRefusedWhenSourceIsOutsidePermittedBaseDir() 
throws Exception {
         addImportRoutes(recurrentImport("arbitrary-dir", fileUri(arbitraryDir, 
"?fileName=profiles.csv")));

Reply via email to