diff --git a/src/main/java/it/inaf/oats/vospace/datamodel/NodeUtils.java b/src/main/java/it/inaf/oats/vospace/datamodel/NodeUtils.java
index fe8e7d5525aa652e1a7c3a5af7b7b4d5e07899e4..dc6668b3503d141faa88f21869b19c45f8aa3843 100644
--- a/src/main/java/it/inaf/oats/vospace/datamodel/NodeUtils.java
+++ b/src/main/java/it/inaf/oats/vospace/datamodel/NodeUtils.java
@@ -1,28 +1,63 @@
 package it.inaf.oats.vospace.datamodel;
 
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 import net.ivoa.xml.vospace.v2.Node;
 
 public class NodeUtils {
 
+    /**
+     * Forbidden path chars are non printable characters and some symbols that
+     * could create issues to scripts that manipulates files. Other UTF-8
+     * characters are allowed. Front end needs to pay attention to other allowed
+     * characters like & and parenthesis in any case, also to avoid XSS attacks.
+     */
+    private static final Pattern FORBIDDEN_CHARS = Pattern.compile("[\\x00\\x08\\x0B\\x0C\\x0E-\\x1F" + Pattern.quote("<>?\":\\|'*") + "]");
+
     /**
      * Slash is a special character in defining REST endpoints and trying to
      * define a PathVariable containing slashes doesn't work, so the endpoint
      * has been defined using "/nodes/**" instead of "/nodes/{path}" and the
-     * path is extracted manually parsing the request URL.
+     * path is extracted manually parsing the request URL. Proper URL encoding
+     * handling is needed, considering also that slashes mustn't be escaped.
      */
     public static String getPathFromRequestURLString(String requestUrlString) {
+        return getPathFromRequestURLString(requestUrlString, "/nodes/");
+    }
 
-        String[] split = requestUrlString.split("/nodes/");
+    public static String getPathFromRequestURLString(String requestUrlString, String prefix) {
+
+        String[] split = requestUrlString.split(prefix);
 
         String path = "/";
         if (split.length == 2) {
-            path += split[1];
+            String[] parts = split[1].split("/");
+            path += String.join("/", Arrays.stream(parts)
+                    .map(p -> {
+                        String decoded = URLDecoder.decode(p, StandardCharsets.UTF_8);
+                        if (FORBIDDEN_CHARS.matcher(decoded).find()) {
+                            throw new IllegalArgumentException("Path segment " + decoded + " contains an illegal character");
+                        }
+                        return decoded;
+                    })
+                    .collect(Collectors.toList()));
         }
         return path;
     }
 
+    public static String urlEncodePath(String path) {
+        String[] parts = path.split("/");
+        return String.join("/", Arrays.stream(parts)
+                .map(p -> URLEncoder.encode(p, StandardCharsets.UTF_8))
+                .collect(Collectors.toList()));
+    }
+
     // This method assumes that URL is in the format /node1/node2/...
     // multiple slashes as a single separator are allowed
     // But the output has only single slash separators
diff --git a/src/test/java/it/inaf/oats/vospace/datamodel/NodeUtilsTest.java b/src/test/java/it/inaf/oats/vospace/datamodel/NodeUtilsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..1b3f4f4164c04e9c4eb7d2287731cac98ea23cdd
--- /dev/null
+++ b/src/test/java/it/inaf/oats/vospace/datamodel/NodeUtilsTest.java
@@ -0,0 +1,54 @@
+package it.inaf.oats.vospace.datamodel;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.junit.jupiter.api.Test;
+
+public class NodeUtilsTest {
+
+    @Test
+    public void testGetPathFromRequestURLString() {
+
+        String requestUrl = "http://localhost/vospace/nodes/a/b/c/";
+        assertEquals("/a/b/c", NodeUtils.getPathFromRequestURLString(requestUrl));
+    }
+
+    @Test
+    public void testGetPathWithSpacesFromRequestURLString() {
+
+        String requestUrl = "http://localhost/vospace/nodes/a/b/c%20d%20%C3%A4.pdf";
+        assertEquals("/a/b/c d ä.pdf", NodeUtils.getPathFromRequestURLString(requestUrl));
+    }
+
+    @Test
+    public void testEncodePathSpecialChars() {
+
+        String specialChars = "ä è#+ /other/+-ò@";
+        assertEquals("%C3%A4+%C3%A8%23%2B+/other/%2B-%C3%B2%40", NodeUtils.urlEncodePath(specialChars));
+    }
+
+    @Test
+    public void testIllegalBrakets() {
+        testIllegalChars("<no>.pdf");
+    }
+
+    @Test
+    public void testIllegalQuestionMark() {
+        testIllegalChars("???.pdf");
+    }
+
+    @Test
+    public void testIllegalQuotes() {
+        testIllegalChars("\"'.pdf");
+    }
+
+    private void testIllegalChars(String illegalString) {
+        boolean exception = false;
+        try {
+            NodeUtils.getPathFromRequestURLString("http://localhost/vospace/nodes/path/to/" + illegalString);
+        } catch (IllegalArgumentException ex) {
+            exception = true;
+        }
+        assertTrue(exception);
+    }
+}