Refactor file path validation code; reject apps with up-level references in the name

Change-Id: I4b14604608078d12df2f7b89f9f841ed19c2552c
diff --git a/apps/yang/src/main/java/org/onosproject/yang/impl/YangLiveCompilerManager.java b/apps/yang/src/main/java/org/onosproject/yang/impl/YangLiveCompilerManager.java
index 068bc98..10f821e 100644
--- a/apps/yang/src/main/java/org/onosproject/yang/impl/YangLiveCompilerManager.java
+++ b/apps/yang/src/main/java/org/onosproject/yang/impl/YangLiveCompilerManager.java
@@ -22,7 +22,7 @@
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Service;
-import org.onlab.util.ZipValidator;
+import org.onlab.util.FilePathValidator;
 import org.onosproject.yang.YangLiveCompilerService;
 import org.onosproject.yang.compiler.tool.DefaultYangCompilationParam;
 import org.onosproject.yang.compiler.tool.YangCompilerManager;
@@ -120,7 +120,7 @@
         ZipInputStream zis = new ZipInputStream(stream);
         ZipEntry entry;
         while ((entry = zis.getNextEntry()) != null) {
-            if (ZipValidator.validateZipEntry(entry, dir)) {
+            if (FilePathValidator.validateZipEntry(entry, dir)) {
                 if (!entry.isDirectory()) {
                     byte[] data = toByteArray(zis);
                     zis.closeEntry();
diff --git a/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java b/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java
index e63a78c..13cc403 100644
--- a/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java
+++ b/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java
@@ -25,7 +25,7 @@
 import org.apache.commons.configuration.XMLConfiguration;
 import org.apache.commons.lang.StringUtils;
 import org.onlab.util.Tools;
-import org.onlab.util.ZipValidator;
+import org.onlab.util.FilePathValidator;
 import org.onosproject.app.ApplicationDescription;
 import org.onosproject.app.ApplicationEvent;
 import org.onosproject.app.ApplicationException;
@@ -234,17 +234,16 @@
         return new String(bytes, 0, Math.min(bytes.length, length), StandardCharsets.UTF_8);
     }
 
-    private String filterAppNameForFilesystem(String name) {
-        return name.replace("/", "^");
-    }
-
     /**
      * Purges the application archive directory.
      *
      * @param appName application name
      */
     public synchronized void purgeApplication(String appName) {
-        File appDir = new File(appsDir, filterAppNameForFilesystem(appName));
+        File appDir = new File(appsDir, appName);
+        if (!FilePathValidator.validateFile(appDir, appsDir)) {
+            throw new ApplicationException("Application attempting to create files outside the apps directory");
+        }
         try {
             Tools.removeDirectory(appDir);
         } catch (IOException e) {
@@ -358,12 +357,15 @@
         boolean isSelfContained = false;
         ZipInputStream zis = new ZipInputStream(stream);
         ZipEntry entry;
-        File appDir = new File(appsDir, filterAppNameForFilesystem(desc.name()));
+        File appDir = new File(appsDir, desc.name());
+        if (!FilePathValidator.validateFile(appDir, appsDir)) {
+            throw new ApplicationException("Application attempting to create files outside the apps directory");
+        }
         while ((entry = zis.getNextEntry()) != null) {
             if (!entry.isDirectory()) {
                 byte[] data = ByteStreams.toByteArray(zis);
                 zis.closeEntry();
-                if (ZipValidator.validateZipEntry(entry, appDir)) {
+                if (FilePathValidator.validateZipEntry(entry, appDir)) {
                     File file = new File(appDir, entry.getName());
                     if (isTopLevel(file)) {
                         createParentDirs(file);
@@ -446,7 +448,7 @@
     private void saveApplication(InputStream stream, ApplicationDescription desc,
                                  boolean isSelfContainedJar)
             throws IOException {
-        String name = filterAppNameForFilesystem(desc.name()) + (isSelfContainedJar ? JAR : OAR);
+        String name = desc.name() + (isSelfContainedJar ? JAR : OAR);
         Files.write(toByteArray(stream), appFile(desc.name(), name));
     }
 
@@ -508,7 +510,11 @@
 
     // Returns the name of the file located under the specified app directory.
     private File appFile(String appName, String fileName) {
-        return new File(new File(appsDir, filterAppNameForFilesystem(appName)), fileName);
+        File file = new File(new File(appsDir, appName), fileName);
+        if (!FilePathValidator.validateFile(file, appsDir)) {
+            throw new ApplicationException("Application attempting to create files outside the apps directory");
+        }
+        return file;
     }
 
     // Returns the icon file located under the specified app directory.
diff --git a/utils/misc/src/main/java/org/onlab/util/ZipValidator.java b/utils/misc/src/main/java/org/onlab/util/FilePathValidator.java
similarity index 67%
rename from utils/misc/src/main/java/org/onlab/util/ZipValidator.java
rename to utils/misc/src/main/java/org/onlab/util/FilePathValidator.java
index 22c6cba..da66a14 100644
--- a/utils/misc/src/main/java/org/onlab/util/ZipValidator.java
+++ b/utils/misc/src/main/java/org/onlab/util/FilePathValidator.java
@@ -23,13 +23,30 @@
 /**
  * Utilities for validation of Zip files.
  */
-public final class ZipValidator {
+public final class FilePathValidator {
 
     /**
      * Do not allow construction.
      */
-    private ZipValidator() {
+    private FilePathValidator() {
+    }
 
+    /**
+     * Validates a File. Checks that the file being created does not
+     * lie outside the target directory.
+     *
+     * @param destinationFile file to check
+     * @param destinationDir target directory
+     * @return true if the Entry resolves to a file inside the target directory; false otherwise
+     */
+    public static boolean validateFile(File destinationFile, File destinationDir) {
+        try {
+            String canonicalDestinationDirPath = destinationDir.getCanonicalPath();
+            String canonicalDestinationFile = destinationFile.getCanonicalPath();
+            return canonicalDestinationFile.startsWith(canonicalDestinationDirPath + File.separator);
+        } catch (IOException ioe) {
+            return false;
+        }
     }
 
     /**