diff --git a/src/main/java/it/inaf/oats/vospace/datamodel/NodeProperties.java b/src/main/java/it/inaf/oats/vospace/datamodel/NodeProperties.java
index 100ba0c9f342c60fcd43eddbcd54ecf6160370d4..fac730e73dce3e82cb1b2fab306f61685f377ed5 100644
--- a/src/main/java/it/inaf/oats/vospace/datamodel/NodeProperties.java
+++ b/src/main/java/it/inaf/oats/vospace/datamodel/NodeProperties.java
@@ -1,8 +1,3 @@
-/*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
- */
 package it.inaf.oats.vospace.datamodel;
 
 import java.util.List;
@@ -10,17 +5,11 @@ import java.util.stream.Collectors;
 import net.ivoa.xml.vospace.v2.Node;
 import net.ivoa.xml.vospace.v2.Property;
 
-/**
- *
- * @author bertocco
- */
-public class NodeProperties {
+public abstract class NodeProperties {
     
     
     private NodeProperties() { }
     
-    
-    private static final String PROPERTY_BASE_URI = "ivo://ivoa.net/vospace/core#";
     public static final String AVAILABLE_SPACE_URI = "ivo://ivoa.net/vospace/core#availableSpace";      // the amount of space available within a container      
     public static final String INITIAL_CREATION_TIME_URI  = "ivo://ivoa.net/vospace/core#btime";       // the initial creation time
     public static final String CONTRIBUTOR_URI  = "ivo://ivoa.net/vospace/core#contributor"; // an entity responsible for making contributions to this resource
@@ -45,14 +34,21 @@ public class NodeProperties {
     public static final String SUBJECT_URI  = "ivo://ivoa.net/vospace/core#subject";     // the topic of the resource     
     public static final String TITLE_URI  = "ivo://ivoa.net/vospace/core#title";       // a name given to the resource  
     public static final String TYPE_URI  = "ivo://ivoa.net/vospace/core#type";        // the nature or genre of the resource
-
+    //
+    // Non-standard properties
+    public static final String ASYNC_TRANS_URN = "urn:async_trans";
+    public static final String STICKY_URN = "urn:sticky";
 
     
+    public static String getStandardNodePropertyByName(Node node, String propertyName) {
+        return getNodePropertyByURI(node, "ivo://ivoa.net/vospace/core#".concat(propertyName));
+    }
 
-    public static String getPropertiesStringByName(Node node, String propertyName) {
+
+    public static String getProperty(Node node, String propertyName) {
 
         for (Property property : node.getProperties()) {
-            if (property.getUri().equals(PROPERTY_BASE_URI.concat(propertyName))) {
+            if (property.getUri().equals("ivo://ivoa.net/vospace/core#".concat(propertyName))) {
                 return property.getValue();
             }
         }
@@ -60,7 +56,7 @@ public class NodeProperties {
     }
     
 
-    public static String getPropertiesStringByURI(Node node, String uri) {
+    public static String getNodePropertyByURI(Node node, String uri) {
         
         for (Property property : node.getProperties()) {
             if (uri.equals(property.getUri())) {
@@ -72,7 +68,7 @@ public class NodeProperties {
     }
     // Returns all properties stored inside the node under the requested
     // property URI.    
-    public static List<String> getNodePropertiesListByURI(Node node, String propertyURI) {
+    public static List<String> getNodePropertyAsListByURI(Node node, String propertyURI) {
 
         List<String> propertyList = node.getProperties().stream()
                 .filter((i) -> i.getUri()
@@ -96,10 +92,5 @@ public class NodeProperties {
         return List.of(trimmedProperty.split(separator));
 
     }
-   
-
-    public static String getPropertyURI(String propertyName) {
-        return PROPERTY_BASE_URI.concat(propertyName);
-    }
     
 }
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 e878de1cb3dd3dad4b5dfc8d292a1203327996e5..77c14b13ae98e74839639db7121b2aa8c89d097d 100644
--- a/src/main/java/it/inaf/oats/vospace/datamodel/NodeUtils.java
+++ b/src/main/java/it/inaf/oats/vospace/datamodel/NodeUtils.java
@@ -1,8 +1,3 @@
-/*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
- */
 package it.inaf.oats.vospace.datamodel;
 
 import java.security.Principal;
@@ -11,29 +6,64 @@ import java.util.List;
 import net.ivoa.xml.vospace.v2.ContainerNode;
 import net.ivoa.xml.vospace.v2.DataNode;
 import net.ivoa.xml.vospace.v2.Node;
+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.StructuredDataNode;
 
 
 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 ) {
-        
-        String[] split = requestUrlString.split("/nodes/");
+    public static String getPathFromRequestURLString(String requestUrlString) {
+        return getPathFromRequestURLString(requestUrlString, "/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).replace("+", "%20"))
+                .collect(Collectors.toList()));
+    }
     
     // This method assumes that URL is in the format /node1/node2/...
     // multiple slashes as a single separator are allowed
@@ -88,6 +118,8 @@ public class NodeUtils {
         
     }
     
+
+    
     public static boolean checkIfWritable(Node myNode, String userName, List<String> userGroups) {
         
         return checkAccessPropery(myNode, userName, userGroups, NodeProperties.GROUP_WRITE_URI);
@@ -144,60 +176,6 @@ public class NodeUtils {
         }
 
         return true;
-    }    
-        
-
-    public static String getDbNodeType(Node node) {
-        if (node instanceof ContainerNode) {
-            return "container";
-        } else if (node instanceof DataNode) {
-            return "data";
-        }
-        throw new UnsupportedOperationException("Unable to retrieve database node type for class " + node.getClass().getCanonicalName());
-    }
-     
-
-    public static String getNodeName(String path) {
-        String[] parsedPath = path.split("/");
-
-        return parsedPath[parsedPath.length - 1];
-    }
-
-    
-    public static String getNodeName(Node myNode) {
-        String uri = myNode.getUri();
-        return getNodeName(uri);
-    }
-        
-
-    public static boolean getIsBusy(Node myNode) {
-
-        if (myNode instanceof DataNode) {
-
-            DataNode dataNode = (DataNode) myNode;
-            return dataNode.isBusy();
-        }
-
-        return false;
-    }
-    
-    
-    public static Node getTypedNode(String type) {
-        Node node;
-        switch (type) {
-            case "container":
-                node = new ContainerNode();
-                break;
-            case "data":
-                node = new DataNode();
-                break;
-            case "structured":
-                node = new StructuredDataNode();
-                break;
-            default:
-                throw new UnsupportedOperationException("Node type " + type + " not supported yet");
-        }
-        return node;
-    }
+    }  
     
 }
diff --git a/src/main/java/net/ivoa/xml/uws/v1/JobSummary.java b/src/main/java/net/ivoa/xml/uws/v1/JobSummary.java
index b755062d96cd82b22177bb8dbeb968175fac1901..1d7d382e0eb751f5f5cc4b5c5ceac210e4c85737 100644
--- a/src/main/java/net/ivoa/xml/uws/v1/JobSummary.java
+++ b/src/main/java/net/ivoa/xml/uws/v1/JobSummary.java
@@ -8,6 +8,7 @@
 
 package net.ivoa.xml.uws.v1;
 
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import it.inaf.oats.vospace.datamodel.JobInfoDeserializer;
@@ -20,7 +21,6 @@ import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlAnyElement;
 import javax.xml.bind.annotation.XmlAttribute;
 import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlElementRef;
 import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlSchemaType;
@@ -98,6 +98,7 @@ import org.w3c.dom.Element;
 // <edit>
 @XmlSeeAlso({Transfer.class}) // Necessary for setting a Transfer inside the jobInfo property.
 @XmlRootElement(name = "job")
+@JsonIgnoreProperties(ignoreUnknown = true)
 // </edit>
 public class JobSummary {
 
diff --git a/src/test/java/it/inaf/oats/vospace/datamodel/NodeUtilsTest.java b/src/test/java/it/inaf/oats/vospace/datamodel/NodeUtilsTest.java
index 54c78d3e8c2bf4a238d7635f485581729f4225c7..eb52987bc75d772973804cca85ef6550299d8078 100644
--- a/src/test/java/it/inaf/oats/vospace/datamodel/NodeUtilsTest.java
+++ b/src/test/java/it/inaf/oats/vospace/datamodel/NodeUtilsTest.java
@@ -1,21 +1,62 @@
-/*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
- */
 package it.inaf.oats.vospace.datamodel;
 
-
-import java.util.ArrayList;
-import java.util.List;
-import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 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+%2B.pdf";
+        assertEquals("/a/b/c d ä +.pdf", NodeUtils.getPathFromRequestURLString(requestUrl));
+    }
+
+    @Test
+    public void testEncodePathSpecialChars() {
+
+        String specialChars = "ä è#+ /other/+-ò@";
+        assertEquals("%C3%A4%20%C3%A8%23%2B%20/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");
+    }
     
-    
+    @Test
+    public void testIllegalSlashEncoded() {
+        testIllegalChars("%2F.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);
+    }
+
     //@Test
     public void getPathFromRequestURLStringTest() {
         
@@ -89,5 +130,4 @@ public class NodeUtilsTest {
         assertArrayEquals(expected.toArray(), result.toArray());
         
     }
-    
 }
diff --git a/src/test/java/net/ivoa/xml/uws/v1/JobSummaryTest.java b/src/test/java/net/ivoa/xml/uws/v1/JobSummaryTest.java
index 713181de6dd54383f2945e8214d922dad4f5e4ff..6e31e1d37379a1f4262af0d5d67655e9a66f866e 100644
--- a/src/test/java/net/ivoa/xml/uws/v1/JobSummaryTest.java
+++ b/src/test/java/net/ivoa/xml/uws/v1/JobSummaryTest.java
@@ -51,6 +51,15 @@ public class JobSummaryTest {
 
         verifyJobsAreEquals(deserialized);
     }
+    
+    /**
+     * Uses JSON extracted from real job executed by transfer service. Contains extra field jobType.
+     */
+    @Test
+    public void testDeserializeTransferServiceResponse() throws Exception {
+        String response = "{\"jobId\": \"917c784f814c4a1a91a9d5d1af07dbe9\", \"ownerId\": \"2386\", \"jobType\": \"pullToVoSpace\", \"phase\": \"PENDING\", \"startTime\": null, \"endTime\": null, \"creationTime\": \"2021-02-03T15:05:57.233602\", \"jobInfo\": {\"transfer\": {\"view\": null, \"target\": \"vos://example.com!vospace/szorba/aaa\", \"version\": null, \"direction\": \"pullToVoSpace\", \"keepBytes\": null, \"protocols\": [{\"uri\": \"ia2:async-recall\", \"param\": [{\"uri\": \"ia2:node-type\", \"value\": \"single\"}], \"endpoint\": null}]}}, \"results\": null}";
+        MAPPER.readValue(response, JobSummary.class);
+    }
 
     private JobSummary getJobSummary() {