From 836bd0a7d637fab714910133057403dec1dcf422 Mon Sep 17 00:00:00 2001
From: Nicola Fulvio Calabria <nicola.calabria@inaf.it>
Date: Thu, 17 Jun 2021 13:26:30 +0200
Subject: [PATCH] Implemented support to node URI percent encoding/decoding

---
 .../java/it/inaf/oats/vospace/URIUtils.java   | 96 ++++++++++++-------
 .../oats/vospace/persistence/NodeDAO.java     | 43 +++++----
 .../it/inaf/oats/vospace/URIUtilsTest.java    | 17 +++-
 3 files changed, 100 insertions(+), 56 deletions(-)

diff --git a/src/main/java/it/inaf/oats/vospace/URIUtils.java b/src/main/java/it/inaf/oats/vospace/URIUtils.java
index 0fd86c2..b30df8d 100644
--- a/src/main/java/it/inaf/oats/vospace/URIUtils.java
+++ b/src/main/java/it/inaf/oats/vospace/URIUtils.java
@@ -9,6 +9,7 @@ import it.inaf.oats.vospace.exception.InvalidURIException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.regex.Pattern;
+import net.ivoa.xml.vospace.v2.Node;
 
 public class URIUtils {
 
@@ -17,48 +18,69 @@ public class URIUtils {
     private static final Pattern FORBIDDEN_CHARS = Pattern.compile("[\\x00\\x08\\x0B\\x0C\\x0E-\\x1F" + Pattern.quote("<>?\":\\|'`*") + "]");
     private static final String SCHEME = "vos";
 
+    public static String returnURIFromVosPath(String vosPath, String authority)
+            throws URISyntaxException {
+        URI uri = new URI(
+                SCHEME,
+                authority,
+                vosPath,
+                null,
+                null
+        );
+
+        return uri.toASCIIString();
+    }   
+    
+    public static String returnVosPathFromNodeURI(Node myNode, String authority) {
+        return returnVosPathFromNodeURI(myNode.getUri(), authority);
+    }
+    
     // This method validates the URI too
     public static String returnVosPathFromNodeURI(String nodeURI, String authority) {
-                
+
         String resultPath = null;
-        
-        try{
-        URI uri = new URI(nodeURI);
-        
-        // Check scheme
-        if(!uri.isAbsolute() || 
-                uri.isOpaque() || 
-                !uri.getRawSchemeSpecificPart().startsWith("//") ||
-                !uri.getScheme().equalsIgnoreCase(SCHEME))
-            throw new InvalidURIException(nodeURI);
-        
-        // Check authority
-        if(!uri.getAuthority().replace("~", "!").equals(authority))
-            throw new InvalidURIException(nodeURI);
-        
-        // Check path
-        String rawPath = uri.getRawPath();
-        
-        // Check if raw Path is null or contains percent encoded slashes or multiple
-        // separators
-        if(rawPath == null ||                 
-                rawPath.contains("//") ||
-                rawPath.contains("%2F") || 
-                rawPath.contains("%2f"))
-            throw new InvalidURIException(nodeURI);
-        
-        resultPath = uri.getPath();
-        
-        if(resultPath.isBlank() || 
-                FORBIDDEN_CHARS.matcher(resultPath).find() ||
-                (!resultPath.equals("/") && resultPath.endsWith("/")))
-            throw new InvalidURIException(nodeURI);        
-        
-        } catch(URISyntaxException e) {
+
+        try {
+            URI uri = new URI(nodeURI);
+
+            // Check scheme
+            if (!uri.isAbsolute()
+                    || uri.isOpaque()
+                    || !uri.getRawSchemeSpecificPart().startsWith("//")
+                    || !uri.getScheme().equalsIgnoreCase(SCHEME)) {
+                throw new InvalidURIException(nodeURI);
+            }
+
+            // Check authority
+            if (!uri.getAuthority().replace("~", "!").equals(authority)) {
+                throw new InvalidURIException(nodeURI);
+            }
+
+            // Check path
+            String rawPath = uri.getRawPath();
+
+            // Check if raw Path is null or contains percent encoded slashes or multiple
+            // separators
+            if (rawPath == null
+                    || rawPath.contains("//")
+                    || rawPath.contains("%2F")
+                    || rawPath.contains("%2f")) {
+                throw new InvalidURIException(nodeURI);
+            }
+
+            resultPath = uri.getPath();
+
+            if (resultPath.isBlank()
+                    || FORBIDDEN_CHARS.matcher(resultPath).find()
+                    || (!resultPath.equals("/") && resultPath.endsWith("/"))) {
+                throw new InvalidURIException(nodeURI);
+            }
+
+        } catch (URISyntaxException e) {
             throw new InvalidURIException(nodeURI);
         }
-        
-        return resultPath;      
+
+        return resultPath;
 
     }
 
diff --git a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java
index f194e02..c6cd9eb 100644
--- a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java
+++ b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java
@@ -10,6 +10,7 @@ import it.inaf.oats.vospace.URIUtils;
 import it.inaf.oats.vospace.datamodel.NodeProperties;
 import it.inaf.oats.vospace.datamodel.NodeUtils;
 import it.inaf.oats.vospace.exception.InternalFaultException;
+import java.net.URISyntaxException;
 import java.sql.Array;
 import net.ivoa.xml.vospace.v2.Node;
 import java.sql.PreparedStatement;
@@ -58,7 +59,7 @@ public class NodeDAO {
 
     public void createNode(Node myNode, String jobId) {
 
-        String nodeVosPath = URIUtils.returnVosPathFromNodeURI(myNode.getUri(), authority);
+        String nodeVosPath = URIUtils.returnVosPathFromNodeURI(myNode, authority);
 
         List<NodePaths> paths = getNodePathsFromDB(nodeVosPath);
 
@@ -144,7 +145,7 @@ public class NodeDAO {
      */
     public Node setNode(Node newNode, boolean recursive) {
 
-        String vosPath = NodeUtils.getVosPath(newNode);
+        String vosPath = URIUtils.returnVosPathFromNodeURI(newNode, authority);
 
         if (recursive) {
             updatePermissionsRecursively(newNode, vosPath);
@@ -240,10 +241,10 @@ public class NodeDAO {
                 throw new InternalFaultException("More than 1 node id at path: " + nodeVosPath);
         }
     }
- 
+
     public Optional<ShortNodeDescriptor> getShortNodeDescriptor(String nodeVosPath,
             String userId, List<String> userGroups) {
-                        
+
         String sql = "SELECT path,\n"
                 + "NOT (n.async_trans OR n.sticky OR COALESCE(location_type = 'async', FALSE)) AS is_writable,\n"
                 + "((SELECT COUNT(*) FROM (SELECT UNNEST(?) INTERSECT SELECT UNNEST(n.group_write)) AS allowed_groups ) = 0 AND\n"
@@ -257,15 +258,15 @@ public class NodeDAO {
 
         Optional<ShortNodeDescriptor> sndOpt = jdbcTemplate.query(conn -> {
             PreparedStatement ps = conn.prepareStatement(sql);
-            
+
             String[] groups;
-            if(userGroups == null){
-                groups = new String[0];                                
+            if (userGroups == null) {
+                groups = new String[0];
             } else {
                 groups = userGroups.toArray(String[]::new);
             }
             ps.setArray(1, ps.getConnection().createArrayOf("varchar", groups));
-            
+
             ps.setString(2, userId);
             ps.setString(3, nodeVosPath);
             return ps;
@@ -342,15 +343,15 @@ public class NodeDAO {
 
         return jdbcTemplate.query(sql, ps -> {
             ps.setLong(1, parentNodeId);
-            
+
             String[] groups;
-            if(userGroups == null){
-                groups = new String[0];                                
+            if (userGroups == null) {
+                groups = new String[0];
             } else {
                 groups = userGroups.toArray(String[]::new);
-            }            
+            }
             ps.setArray(2, ps.getConnection().createArrayOf("varchar", groups));
-            
+
             ps.setString(3, userId);
         }, row -> {
             if (!row.next()) {
@@ -454,7 +455,17 @@ public class NodeDAO {
     }
 
     private String getUri(String path) {
-        return "vos://" + authority + path;
+        // Percent encode path
+        String result = null;
+
+        try {
+            result = URIUtils.returnURIFromVosPath(path, authority);
+        } catch (URISyntaxException e) {
+            throw new InternalFaultException("unable to percent encode URI from authority and path: "
+            + authority + " , " + path);
+        }
+
+        return result;
     }
 
     private NodePaths getPathsFromResultSet(ResultSet rs) throws SQLException {
@@ -583,7 +594,7 @@ public class NodeDAO {
     }
 
     private List<NodePaths> getNodePathsFromDB(String path) {
-        
+
         String parentPath = NodeUtils.getParentPath(path);
 
         String sql = "SELECT path, relative_path "
@@ -633,7 +644,7 @@ public class NodeDAO {
         public boolean isBusy() {
             return busy;
         }
-        
+
         public boolean isPermissionDenied() {
             return permissionDenied;
         }
diff --git a/src/test/java/it/inaf/oats/vospace/URIUtilsTest.java b/src/test/java/it/inaf/oats/vospace/URIUtilsTest.java
index b3e3733..d630f85 100644
--- a/src/test/java/it/inaf/oats/vospace/URIUtilsTest.java
+++ b/src/test/java/it/inaf/oats/vospace/URIUtilsTest.java
@@ -6,6 +6,7 @@
 package it.inaf.oats.vospace;
 
 import it.inaf.oats.vospace.exception.InvalidURIException;
+import java.net.URISyntaxException;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import org.junit.jupiter.api.Test;
@@ -57,7 +58,7 @@ public class URIUtilsTest {
         
         assertThrows(InvalidURIException.class, () -> {
             URIUtils.returnVosPathFromNodeURI(test7, authority);
-        });
+        });                
         
         String test8 = "vos://example.com!vospace/n1/n2/n3/n4";
         
@@ -67,11 +68,21 @@ public class URIUtilsTest {
         String test9 = "vos://example.com!vospace/";
         
         assertEquals("/", 
-                URIUtils.returnVosPathFromNodeURI(test9, authority));
-        
+                URIUtils.returnVosPathFromNodeURI(test9, authority));        
         
+    }
+    
+    @Test
+    public void testReturnURIFromVosPath() throws URISyntaxException
+    {
+        String test1 = URIUtils.returnURIFromVosPath("/", authority);        
+        assertEquals("vos://"+authority+"/", test1);
         
+        String test2 = URIUtils.returnURIFromVosPath("/test1/test2", authority);        
+        assertEquals("vos://"+authority+"/test1/test2", test2);
         
+        String test3 = URIUtils.returnURIFromVosPath("/test1/te# !?st2", authority);
+        assertEquals("vos://"+authority+"/test1/te%23%20!%3Fst2", test3);       
     }
 
     
-- 
GitLab