diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java
index 36a4324a474ee8e8744726b5794e871ce7cc88aa..a67829da95982884b95059e10dfe27d8f7566642 100644
--- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java
+++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java
@@ -5,6 +5,7 @@ import it.inaf.ia2.aa.data.User;
 import it.inaf.ia2.vospace.ui.VOSpaceUiApplication;
 import it.inaf.ia2.vospace.ui.data.Job;
 import it.inaf.ia2.vospace.ui.exception.VOSpaceException;
+import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.StringWriter;
@@ -72,7 +73,7 @@ public class VOSpaceClient {
 
     public Node getNode(String path) {
 
-        HttpRequest request = getRequest("/nodes" + path)
+        HttpRequest request = getRequest("/nodes" + urlEncodePath(path))
                 .header("Accept", useJson ? "application/json" : "text/xml")
                 .build();
 
@@ -105,7 +106,7 @@ public class VOSpaceClient {
 
         String path = node.getUri().substring(("vos://" + authority).length());
 
-        HttpRequest request = getRequest("/nodes" + path)
+        HttpRequest request = getRequest("/nodes" + urlEncodePath(path))
                 .header("Accept", useJson ? "application/json" : "text/xml")
                 .header("Content-Type", useJson ? "application/json" : "text/xml")
                 .PUT(HttpRequest.BodyPublishers.ofString(marshal(node)))
@@ -113,10 +114,10 @@ public class VOSpaceClient {
 
         return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, Node.class));
     }
-    
+
     public void deleteNode(String path) {
 
-        HttpRequest request = getRequest("/nodes" + path)
+        HttpRequest request = getRequest("/nodes" + urlEncodePath(path))
                 .header("Accept", useJson ? "application/json" : "text/xml")
                 .header("Content-Type", useJson ? "application/json" : "text/xml")
                 .DELETE()
diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java
index 4e12e12c2b6c5a03285241fb7a6de52fead5650f..a4fb6569ac65b849cf4d876bec4c997481970ebd 100644
--- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java
+++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java
@@ -4,12 +4,15 @@ import it.inaf.ia2.aa.data.User;
 import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
 import it.inaf.ia2.vospace.ui.data.ListNodeData;
 import it.inaf.ia2.vospace.ui.service.NodesService;
+import it.inaf.oats.vospace.datamodel.NodeUtils;
 import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
 import net.ivoa.xml.vospace.v2.ContainerNode;
 import net.ivoa.xml.vospace.v2.Property;
 import net.ivoa.xml.vospace.v2.Protocol;
 import net.ivoa.xml.vospace.v2.Transfer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpHeaders;
@@ -25,6 +28,8 @@ import org.springframework.web.bind.annotation.RestController;
 @RestController
 public class NodesController extends BaseController {
 
+    private static final Logger LOG = LoggerFactory.getLogger(NodesController.class);
+    
     @Value("${vospace-authority}")
     private String authority;
 
@@ -41,6 +46,7 @@ public class NodesController extends BaseController {
     public ResponseEntity<ListNodeData> listNodes(User principal) throws Exception {
 
         String path = getPath("/nodes/");
+        LOG.debug("listNodes called for path {}", path);
 
         return ResponseEntity.ok(nodesService.generateNodesHtml(path, principal));
     }
@@ -48,13 +54,15 @@ public class NodesController extends BaseController {
     @DeleteMapping(value = {"/nodes", "/nodes/**"})
     public void deleteNode() {
         String path = getPath("/nodes/");
+        LOG.debug("deleteNode called for path {}", path);
         client.deleteNode(path);
     }
-    
+
     @GetMapping(value = "/download/**")
     public ResponseEntity<?> directDownload() {
 
         String path = getPath("/download/");
+        LOG.debug("directDownload called for path {}", path);
 
         Transfer transfer = new Transfer();
         transfer.setDirection("pullFromVoSpace");
@@ -78,10 +86,12 @@ public class NodesController extends BaseController {
             parentPath = "/" + parentPath;
         }
         String name = getRequiredParam(params, "name");
+        
+        LOG.debug("newFolder called for path {}/{}", parentPath, name);
 
         ContainerNode node = new ContainerNode();
         node.setUri("vos://" + authority + parentPath + "/" + name);
-        
+
         Property creator = new Property();
         creator.setUri("ivo://ivoa.net/vospace/core#creator");
         creator.setValue(getUser().getName());
@@ -90,20 +100,8 @@ public class NodesController extends BaseController {
         client.createNode(node);
     }
 
-    /**
-     * 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.
-     */
     protected String getPath(String prefix) {
         String requestURL = servletRequest.getRequestURL().toString();
-        String[] split = requestURL.split(prefix);
-
-        String path = "/";
-        if (split.length == 2) {
-            path += split[1];
-        }
-        return path;
+        return NodeUtils.getPathFromRequestURLString(requestURL, prefix);
     }
 }
diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesService.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesService.java
index d8a5b4e0785f5eae5f12aa6a95a73ecf7c724c3e..f341a75883e3a15f6821e1a5313e00bb54be7d3e 100644
--- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesService.java
+++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesService.java
@@ -4,6 +4,7 @@ import it.inaf.ia2.aa.data.User;
 import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
 import it.inaf.ia2.vospace.ui.data.ListNodeData;
 import it.inaf.oats.vospace.datamodel.NodeUtils;
+import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.UncheckedIOException;
@@ -65,7 +66,7 @@ public class NodesService {
         }
 
         String html = "<tr>";
-        html += "<td><input type=\"checkbox\" data-node=\"" + nodeInfo.getPath() + "\" ";
+        html += "<td><input type=\"checkbox\" data-node=\"" + nodeInfo.getPath().replace("\"", "\\\"") + "\" ";
         if (nodeInfo.isAsyncTrans()) {
             html += "class=\"async\"";
         }
@@ -108,9 +109,9 @@ public class NodesService {
     private String getLink(NodeInfo nodeInfo, User user) {
         if (isDownloadable(nodeInfo, user)) {
             if (nodeInfo.isFolder()) {
-                return "<a href=\"#/nodes" + nodeInfo.getPath() + "\">" + nodeInfo.getName() + "</a>";
+                return "<a href=\"#/nodes" + urlEncodePath(nodeInfo.getPath()) + "\">" + nodeInfo.getName() + "</a>";
             } else {
-                return "<a href=\"download" + nodeInfo.getPath() + "\" target=\"blank_\">" + nodeInfo.getName() + "</a>";
+                return "<a href=\"download" + urlEncodePath(nodeInfo.getPath()) + "\" target=\"blank_\">" + nodeInfo.getName() + "</a>";
             }
         }
         return nodeInfo.getName();
diff --git a/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/client/VOSpaceClientTest.java b/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/client/VOSpaceClientTest.java
index 5cd931c097606454513fc6f4962ee319299a051a..e8a1eabc6463733408e972734c2c2e19ab8f4e14 100644
--- a/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/client/VOSpaceClientTest.java
+++ b/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/client/VOSpaceClientTest.java
@@ -76,6 +76,20 @@ public class VOSpaceClientTest {
         assertEquals(newNode.getUri(), responseNode.getUri());
     }
 
+    @Test
+    public void testCreateNodeBadName() {
+
+        ContainerNode newNode = new ContainerNode();
+        newNode.setUri("vos://ia2.inaf.it!vospace/mynode/File with spaces.and.dots.pdf");
+
+        ReflectionTestUtils.setField(voSpaceClient, "useJson", false);
+
+        CompletableFuture response = getMockedStreamResponseFuture(200, getResourceFileContent("node-response.xml"));
+        when(mockedHttpClient.sendAsync(any(), any())).thenReturn(response);
+
+        voSpaceClient.createNode(newNode);
+    }
+
     protected static String getResourceFileContent(String fileName) {
         try ( InputStream in = VOSpaceClientTest.class.getClassLoader().getResourceAsStream(fileName)) {
             return new String(in.readAllBytes(), StandardCharsets.UTF_8);
diff --git a/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/controller/NodesControllerTest.java b/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/controller/NodesControllerTest.java
index 085b3f22f704aabb419365db1b8fbbfc89578261..79b0fada9c875c4b8b21bd05a3021fc594191c80 100644
--- a/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/controller/NodesControllerTest.java
+++ b/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/controller/NodesControllerTest.java
@@ -22,7 +22,7 @@ public class NodesControllerTest {
 
     @Autowired
     private MockMvc mockMvc;
-    
+
     @Test
     public void testListNodesEmpty() throws Exception {
 
diff --git a/vospace-ui-frontend/src/api/server/index.js b/vospace-ui-frontend/src/api/server/index.js
index d7931218a42398532d792b5b379ed3947049328f..0cad25df45dfecb676b3c7638f9c41a9a545b6a4 100644
--- a/vospace-ui-frontend/src/api/server/index.js
+++ b/vospace-ui-frontend/src/api/server/index.js
@@ -41,9 +41,13 @@ function getErrorMessage(error) {
   }
 }
 
+function escapePath(path) {
+  return path.split('/').map(p => encodeURIComponent(p)).join('/');
+}
+
 export default {
   getNode(path) {
-    let url = BASE_API_URL + 'nodes/' + path;
+    let url = BASE_API_URL + 'nodes/' + escapePath(path);
     return apiRequest({
       method: 'GET',
       url: url,
@@ -127,7 +131,7 @@ export default {
     })
   },
   deleteNode(path) {
-    let url = BASE_API_URL + 'nodes' + path;
+    let url = BASE_API_URL + 'nodes' + escapePath(path);
     return apiRequest({
       method: 'DELETE',
       url: url,
diff --git a/vospace-ui-frontend/src/components/Main.vue b/vospace-ui-frontend/src/components/Main.vue
index e9147d76018db4fb8247e89cb71cd3fd70033389..3038c24d201c005f6a020af920a68018a5e32276 100644
--- a/vospace-ui-frontend/src/components/Main.vue
+++ b/vospace-ui-frontend/src/components/Main.vue
@@ -60,7 +60,7 @@ export default {
         for (let i = 0; i < pathSplit.length; i++) {
           items.push({
             text: pathSplit[i],
-            href: '#/nodes/' + pathSplit.slice(0, i + 1).join('/')
+            href: '#/nodes/' + pathSplit.slice(0, i + 1).map(p => encodeURIComponent(p)).join('/')
           });
         }
       }
diff --git a/vospace-ui-frontend/src/components/modal/CreateFolderModal.vue b/vospace-ui-frontend/src/components/modal/CreateFolderModal.vue
index 41ef2dc7b24567ace7b84dec2ab7abb7420b41df..d671009b8210fe5ee68e78aa6e3ea967a3e08cc7 100644
--- a/vospace-ui-frontend/src/components/modal/CreateFolderModal.vue
+++ b/vospace-ui-frontend/src/components/modal/CreateFolderModal.vue
@@ -43,6 +43,8 @@ export default {
 
       if (!this.newFolderName) {
         this.newFolderNameError = "Folder name is required";
+      } else if (/[<>?":\\/`|'*]/.test(this.newFolderName)) {
+        this.newFolderNameError = "Folder name contains an illegal character. Following characters are not allowed: < > ? \" : \\ / | ' * `";
       } else {
         this.$store.dispatch('createFolder', this.newFolderName)
           .then(() => {
diff --git a/vospace-ui-frontend/src/components/modal/UploadFilesModal.vue b/vospace-ui-frontend/src/components/modal/UploadFilesModal.vue
index 90216f504c621ceced7b7b140e24b92dee52ca46..849c38b33367e93c68eb73080fd1fb7e41ea696e 100644
--- a/vospace-ui-frontend/src/components/modal/UploadFilesModal.vue
+++ b/vospace-ui-frontend/src/components/modal/UploadFilesModal.vue
@@ -1,6 +1,6 @@
 <template>
 <b-modal id="upload-files-modal" title="Upload file" okTitle="Upload" @show="reset" @ok="uploadFiles">
-  <b-form-file v-model="files" :multiple="true" :state="fileState" placeholder="Choose your files or drop them here..." drop-placeholder="Drop files here..."></b-form-file>
+  <b-form-file v-model="files" :multiple="true" :state="fileState" placeholder="Choose your files or drop them here..." drop-placeholder="Drop files here..." @change="resetError"></b-form-file>
   <b-form-invalid-feedback id="upload-file-input-feedback" class="text-right">{{uploadFileError}}</b-form-invalid-feedback>
   <div class="mt-3">Selected files: {{ selectedFiles }}</div>
 </b-modal>
@@ -46,6 +46,14 @@ export default {
       if (this.files.length === 0) {
         this.uploadFileError = "Select at least one file";
       } else {
+        // Check special characters in file names
+        for (let file of this.files) {
+          if (/[<>?":\\/`|'*]/.test(file.name)) {
+            this.uploadFileError = "File " + file.name + " contains an illegal character. Following characters are not allowed: < > ? \" : \\ / | ' * `";
+            return;
+          }
+        }
+
         this.$store.dispatch('uploadFiles', this.files)
           .then(() => {
             this.$bvModal.hide('upload-files-modal');