Browse Source

HADOOP-423. Normalize paths containing directories named '.' or '..'. Contributed by Wendy.

git-svn-id: https://svn.apache.org/repos/asf/lucene/hadoop/trunk@451421 13f79535-47bb-0310-9956-ffa450edef68
Doug Cutting 19 years ago
parent
commit
8a009d5f1e

+ 5 - 0
CHANGES.txt

@@ -90,6 +90,11 @@ Trunk (unreleased changes)
     elsewhere.  The web interface is also updated to display killed
     elsewhere.  The web interface is also updated to display killed
     tasks.  (omalley via cutting)
     tasks.  (omalley via cutting)
 
 
+23. HADOOP-423.  Normalize Paths containing directories named "." and
+    "..", using the standard, unix interpretation.  Also add checks in
+    DFS, prohibiting the use of "." or ".." as directory or file
+    names.  (Wendy Chien via cutting)
+
 
 
 Release 0.6.2 (unreleased)
 Release 0.6.2 (unreleased)
 
 

+ 37 - 0
src/java/org/apache/hadoop/dfs/FSNamesystem.java

@@ -21,6 +21,7 @@ import org.apache.hadoop.io.*;
 import org.apache.hadoop.conf.*;
 import org.apache.hadoop.conf.*;
 import org.apache.hadoop.util.*;
 import org.apache.hadoop.util.*;
 import org.apache.hadoop.mapred.StatusHttpServer;
 import org.apache.hadoop.mapred.StatusHttpServer;
+import org.apache.hadoop.fs.Path;
 
 
 import java.io.*;
 import java.io.*;
 import java.net.InetSocketAddress;
 import java.net.InetSocketAddress;
@@ -398,6 +399,9 @@ class FSNamesystem implements FSConstants {
             +src+" for "+holder+" at "+clientMachine);
             +src+" for "+holder+" at "+clientMachine);
       if( isInSafeMode() )
       if( isInSafeMode() )
         throw new SafeModeException( "Cannot create file" + src, safeMode );
         throw new SafeModeException( "Cannot create file" + src, safeMode );
+      if (!isValidName(src.toString())) {
+        throw new IOException("Invalid file name: " + src);      	  
+      }
       try {
       try {
         if (pendingCreates.get(src) != null) {
         if (pendingCreates.get(src) != null) {
            throw new AlreadyBeingCreatedException(
            throw new AlreadyBeingCreatedException(
@@ -730,6 +734,9 @@ class FSNamesystem implements FSConstants {
         NameNode.stateChangeLog.debug("DIR* NameSystem.renameTo: " + src + " to " + dst );
         NameNode.stateChangeLog.debug("DIR* NameSystem.renameTo: " + src + " to " + dst );
         if( isInSafeMode() )
         if( isInSafeMode() )
           throw new SafeModeException( "Cannot rename " + src, safeMode );
           throw new SafeModeException( "Cannot rename " + src, safeMode );
+        if (!isValidName(dst.toString())) {
+          throw new IOException("Invalid name: " + dst);
+        }
         return dir.renameTo(src, dst);
         return dir.renameTo(src, dst);
     }
     }
 
 
@@ -784,6 +791,33 @@ class FSNamesystem implements FSConstants {
         return dir.isDir(src);
         return dir.isDir(src);
     }
     }
 
 
+    /**
+     * Whether the pathname is valid.  Currently prohibits relative paths, 
+     * and names which contain a ":" or "/" 
+     */
+    private boolean isValidName(String src) {
+      
+      // Path must be absolute.
+      if (!src.startsWith(Path.SEPARATOR)) {
+        return false;
+      }
+      
+      // Check for ".." "." ":" "/"
+      Enumeration tokens = new StringTokenizer(src, Path.SEPARATOR);    
+      ArrayList list = Collections.list(tokens);      
+      for (int i = 0; i < list.size(); i++) {
+        String element = (String)list.get(i);
+        if (element.equals("..") || 
+            element.equals(".")  ||
+            (element.indexOf(":") >= 0)  ||
+            (element.indexOf("/") >= 0)) {
+          return false;
+        }
+      }
+      
+      return true;
+    }
+    
     /**
     /**
      * Create all the necessary directories
      * Create all the necessary directories
      */
      */
@@ -791,6 +825,9 @@ class FSNamesystem implements FSConstants {
         NameNode.stateChangeLog.debug("DIR* NameSystem.mkdirs: " + src );
         NameNode.stateChangeLog.debug("DIR* NameSystem.mkdirs: " + src );
         if( isInSafeMode() )
         if( isInSafeMode() )
           throw new SafeModeException( "Cannot create directory " + src, safeMode );
           throw new SafeModeException( "Cannot create directory " + src, safeMode );
+        if (!isValidName(src)) {
+          throw new IOException("Invalid directory name: " + src);
+        }
         return dir.mkdirs(src);
         return dir.mkdirs(src);
     }
     }
 
 

+ 58 - 5
src/java/org/apache/hadoop/fs/Path.java

@@ -57,13 +57,15 @@ public class Path implements Comparable {
       this.elements = child.elements;
       this.elements = child.elements;
     } else {
     } else {
       this.isAbsolute = parent.isAbsolute;
       this.isAbsolute = parent.isAbsolute;
-      this.elements = new String[parent.elements.length+child.elements.length];
+      ArrayList list = new ArrayList(parent.elements.length+child.elements.length);
       for (int i = 0; i < parent.elements.length; i++) {
       for (int i = 0; i < parent.elements.length; i++) {
-        elements[i] = parent.elements[i];
+        list.add(parent.elements[i]);
       }
       }
       for (int i = 0; i < child.elements.length; i++) {
       for (int i = 0; i < child.elements.length; i++) {
-        elements[i+parent.elements.length] = child.elements[i];
+        list.add(child.elements[i]);
       }
       }
+      normalize(list);
+      this.elements = (String[])list.toArray(new String[list.size()]);
     }
     }
     this.drive = child.drive == null ? parent.drive : child.drive;
     this.drive = child.drive == null ? parent.drive : child.drive;
   }
   }
@@ -82,10 +84,10 @@ public class Path implements Comparable {
     // determine whether the path is absolute
     // determine whether the path is absolute
     this.isAbsolute = pathString.startsWith(SEPARATOR);
     this.isAbsolute = pathString.startsWith(SEPARATOR);
 
 
-
     // tokenize the path into elements
     // tokenize the path into elements
-    Enumeration tokens = new StringTokenizer(pathString, SEPARATOR);
+    Enumeration tokens = new StringTokenizer(pathString, SEPARATOR);    
     ArrayList list = Collections.list(tokens);
     ArrayList list = Collections.list(tokens);
+    normalize(list);
     this.elements = (String[])list.toArray(new String[list.size()]);
     this.elements = (String[])list.toArray(new String[list.size()]);
   }
   }
 
 
@@ -180,5 +182,56 @@ public class Path implements Comparable {
     return elements.length;
     return elements.length;
   }
   }
 
 
+  /* 
+   * Removes '.' and '..' 
+   */
+  private void normalize(ArrayList list) {
+    boolean canNormalize = this.isAbsolute;
+    boolean changed = false;    // true if we have detected any . or ..
+    int index = 0;
+    int listSize = list.size();
+    for (int i = 0; i < listSize; i++) {
+      // Invariant: (index >= 0) && (index <= i)
+      if (list.get(i).equals(".")) {
+        changed = true;
+      } else {
+        if (canNormalize) {
+          if (list.get(i).equals("..")) {
+            if ((index > 0) && !list.get(index-1).equals("..")) {
+              index--;    // effectively deletes the last element currently in list.
+              changed = true;
+            } else { // index == 0
+              // the first element is now going to be '..'
+              canNormalize = false;
+              list.set(index, "..");
+              isAbsolute = false;
+              index++; 
+            }
+          } else { // list.get(i) != ".." or "."
+            if (changed) {
+              list.set(index, list.get(i));
+            }
+            index++;
+          }
+        } else { // !canNormalize
+          if (changed) {
+            list.set(index, list.get(i));
+          }
+          index++;
+          if (!list.get(i).equals("..")) {
+           canNormalize = true;
+          }
+        }  // else !canNormalize
+      } 
+    }  // for
+    
+    // Remove the junk at the end of the list.
+    for (int j = listSize-1; j >= index; j--) {
+      list.remove(j);
+    }
+
+  }
+  
+  
 }
 }
 
 

+ 38 - 0
src/test/org/apache/hadoop/fs/TestPath.java

@@ -98,4 +98,42 @@ public class TestPath extends TestCase {
     assertFalse(new Path("/").equals(new Path("/foo")));
     assertFalse(new Path("/").equals(new Path("/foo")));
   }
   }
 
 
+  public void testDots() {
+    // Test Path(String) 
+    assertEquals(new Path("/foo/bar/baz").toString(), "/foo/bar/baz");
+    assertEquals(new Path("/foo/bar/../baz").toString(), "/foo/baz");
+    assertEquals(new Path("/foo/bar/./baz").toString(), "/foo/bar/baz");
+    assertEquals(new Path("/foo/bar/baz/../../fud").toString(), "/foo/fud");
+    assertEquals(new Path("/foo/bar/baz/.././../fud").toString(), "/foo/fud");
+    assertEquals(new Path("../../foo/bar").toString(), "../../foo/bar");
+    assertEquals(new Path(".././../foo/bar").toString(), "../../foo/bar");
+    assertEquals(new Path("./foo/bar/baz").toString(), "foo/bar/baz");
+    assertEquals(new Path("/foo/bar/../../baz/boo").toString(), "/baz/boo");
+    assertEquals(new Path("/foo/bar/../../../baz").toString(), "../baz");
+    assertEquals(new Path("foo/bar/").toString(), "foo/bar");
+    assertEquals(new Path("foo/bar/../baz").toString(), "foo/baz");
+    assertEquals(new Path("foo/bar/../../baz/boo").toString(), "baz/boo");
+    
+    
+    // Test Path(Path,Path)
+    assertEquals(new Path("/foo/bar", "baz/boo").toString(), "/foo/bar/baz/boo");
+    assertEquals(new Path("foo/bar/","baz/bud").toString(), "foo/bar/baz/bud");
+    
+    assertEquals(new Path("/foo/bar","../../boo/bud").toString(), "/boo/bud");
+    assertEquals(new Path("foo/bar","../../boo/bud").toString(), "boo/bud");
+    assertEquals(new Path("","boo/bud").toString(), "boo/bud");
+
+    assertEquals(new Path("/foo/bar/baz","../../boo/bud").toString(), "/foo/boo/bud");
+    assertEquals(new Path("foo/bar/baz","../../boo/bud").toString(), "foo/boo/bud");
+
+    assertEquals(new Path("/foo/bar/","../../../baz/boo").toString(), "../baz/boo");
+    
+    assertEquals(new Path("../../","../../boo/bud").toString(), "../../../../boo/bud");
+    assertEquals(new Path("../../foo","../../../boo/bud").toString(), "../../../../boo/bud");
+    assertEquals(new Path("../../foo/bar","../boo/bud").toString(), "../../foo/boo/bud");
+
+    assertEquals(new Path("foo/bar/baz","../../..").toString(), "");
+    assertEquals(new Path("foo/bar/baz","../../../../..").toString(), "../..");
+  }
+  
 }
 }