Move PathComponents from file_util to FilePath, add FilePath::IsParent()

r=erikkay,mark

Review URL: http://codereview.chromium.org/145026

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@19174 0039d316-1c4b-4281-b951-d872f2087c98


CrOS-Libchrome-Original-Commit: 0e4f68db63e565201a6eaa2f600441c37b150d1b
diff --git a/base/file_path.cc b/base/file_path.cc
index a00659e..e3dfbbd 100644
--- a/base/file_path.cc
+++ b/base/file_path.cc
@@ -61,6 +61,16 @@
 #endif  // FILE_PATH_USES_DRIVE_LETTERS
 }
 
+bool AreAllSeparators(FilePath::StringType input) {
+  for (FilePath::StringType::const_iterator it = input.begin();
+      it != input.end(); ++it) {
+    if (!FilePath::IsSeparator(*it))
+      return false;
+  }
+
+  return true;
+}
+
 }  // namespace
 
 bool FilePath::IsSeparator(CharType character) {
@@ -73,6 +83,68 @@
   return false;
 }
 
+void FilePath::GetComponents(std::vector<FilePath::StringType>* components)
+    const {
+  DCHECK(components);
+  if (!components)
+    return;
+  components->clear();
+  if (value().empty())
+    return;
+
+  std::vector<FilePath::StringType> ret_val;
+  FilePath current = *this;
+  FilePath base;
+
+  // Capture path components.
+  while (current != current.DirName()) {
+    base = current.BaseName();
+    if (!AreAllSeparators(base.value()))
+      ret_val.push_back(base.value());
+    current = current.DirName();
+  }
+
+  // Capture root, if any.
+  base = current.BaseName();
+  if (!base.value().empty() && base.value() != kCurrentDirectory)
+    ret_val.push_back(current.BaseName().value());
+
+  // Capture drive letter, if any.
+  FilePath dir = current.DirName();
+  StringType::size_type letter = FindDriveLetter(dir.value());
+  if (letter != FilePath::StringType::npos) {
+    ret_val.push_back(FilePath::StringType(dir.value(), 0, letter + 1));
+  }
+
+  *components = std::vector<FilePath::StringType>(ret_val.rbegin(),
+                                                  ret_val.rend());
+}
+
+bool FilePath::IsParent(const FilePath& child) const {
+  std::vector<FilePath::StringType> parent_components;
+  std::vector<FilePath::StringType> child_components;
+  GetComponents(&parent_components);
+  child.GetComponents(&child_components);
+
+  if (parent_components.size() >= child_components.size())
+    return false;
+  if (parent_components.size() == 0)
+    return false;
+
+  std::vector<FilePath::StringType>::const_iterator parent_comp =
+      parent_components.begin();
+  std::vector<FilePath::StringType>::const_iterator child_comp =
+      child_components.begin();
+  while (parent_comp != parent_components.end()) {
+    if (*parent_comp != *child_comp)
+      return false;
+    ++parent_comp;
+    ++child_comp;
+  }
+
+  return true;
+}
+
 // libgen's dirname and basename aren't guaranteed to be thread-safe and aren't
 // guaranteed to not modify their input strings, and in fact are implemented
 // differently in this regard on different platforms.  Don't use them, but
diff --git a/base/file_path.h b/base/file_path.h
index 73de4a6..ae4d36d 100644
--- a/base/file_path.h
+++ b/base/file_path.h
@@ -142,6 +142,18 @@
   // Returns true if |character| is in kSeparators.
   static bool IsSeparator(CharType character);
 
+  // Returns a vector of all of the components of the provided path. It is
+  // equivalent to calling DirName().value() on the path's root component,
+  // and BaseName().value() on each child component.
+  void GetComponents(std::vector<FilePath::StringType>* components) const;
+
+  // Returns true if this FilePath is a strict parent of the |child|. Absolute
+  // and relative paths are accepted i.e. is /foo parent to /foo/bar and
+  // is foo parent to foo/bar. Does not convert paths to absolute, follow
+  // symlinks or directory navigation (e.g. ".."). A path is *NOT* its own
+  // parent.
+  bool IsParent(const FilePath& child) const;
+
   // Returns a FilePath corresponding to the directory containing the path
   // named by this object, stripping away the file component.  If this object
   // only contains one component, returns a FilePath identifying
diff --git a/base/file_path_unittest.cc b/base/file_path_unittest.cc
index 3439c6c..0df6838 100644
--- a/base/file_path_unittest.cc
+++ b/base/file_path_unittest.cc
@@ -27,6 +27,11 @@
   const FilePath::CharType* expected;
 };
 
+struct BinaryBooleanTestData {
+  const FilePath::CharType* inputs[2];
+  bool expected;
+};
+
 // file_util winds up using autoreleased objects on the Mac, so this needs
 // to be a PlatformTest
 class FilePathTest : public PlatformTest {
@@ -375,6 +380,103 @@
   }
 }
 
+TEST_F(FilePathTest, PathComponentsTest) {
+  const struct UnaryTestData cases[] = {
+    { FPL("//foo/bar/baz/"),          FPL("|//|foo|bar|baz")},
+    { FPL("///"),                     FPL("|/")},
+    { FPL("/foo//bar//baz/"),         FPL("|/|foo|bar|baz")},
+    { FPL("/foo/bar/baz/"),           FPL("|/|foo|bar|baz")},
+    { FPL("/foo/bar/baz//"),          FPL("|/|foo|bar|baz")},
+    { FPL("/foo/bar/baz///"),         FPL("|/|foo|bar|baz")},
+    { FPL("/foo/bar/baz"),            FPL("|/|foo|bar|baz")},
+    { FPL("/foo/bar.bot/baz.txt"),    FPL("|/|foo|bar.bot|baz.txt")},
+    { FPL("//foo//bar/baz"),          FPL("|//|foo|bar|baz")},
+    { FPL("/"),                       FPL("|/")},
+    { FPL("foo"),                     FPL("|foo")},
+    { FPL(""),                        FPL("")},
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+    { FPL("e:/foo"),                  FPL("|e:|/|foo")},
+    { FPL("e:/"),                     FPL("|e:|/")},
+    { FPL("e:"),                      FPL("|e:")},
+#endif  // FILE_PATH_USES_DRIVE_LETTERS
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+    { FPL("../foo"),                  FPL("|..|foo")},
+    { FPL("./foo"),                   FPL("|foo")},
+    { FPL("../foo/bar/"),             FPL("|..|foo|bar") },
+    { FPL("\\\\foo\\bar\\baz\\"),     FPL("|\\\\|foo|bar|baz")},
+    { FPL("\\\\\\"),                  FPL("|\\")},
+    { FPL("\\foo\\\\bar\\\\baz\\"),   FPL("|\\|foo|bar|baz")},
+    { FPL("\\foo\\bar\\baz\\"),       FPL("|\\|foo|bar|baz")},
+    { FPL("\\foo\\bar\\baz\\\\"),     FPL("|\\|foo|bar|baz")},
+    { FPL("\\foo\\bar\\baz\\\\\\"),   FPL("|\\|foo|bar|baz")},
+    { FPL("\\foo\\bar\\baz"),         FPL("|\\|foo|bar|baz")},
+    { FPL("\\foo\\bar/baz\\\\\\"),    FPL("|\\|foo|bar|baz")},
+    { FPL("/foo\\bar\\baz"),          FPL("|/|foo|bar|baz")},
+    { FPL("\\foo\\bar.bot\\baz.txt"), FPL("|\\|foo|bar.bot|baz.txt")},
+    { FPL("\\\\foo\\\\bar\\baz"),     FPL("|\\\\|foo|bar|baz")},
+    { FPL("\\"),                      FPL("|\\")},
+#endif  // FILE_PATH_USES_WIN_SEPARATORS
+  };
+
+  for (size_t i = 0; i < arraysize(cases); ++i) {
+    FilePath input(cases[i].input);
+    std::vector<FilePath::StringType> comps;
+    input.GetComponents(&comps);
+
+    FilePath::StringType observed;
+    for (size_t j = 0; j < comps.size(); ++j) {
+      observed.append(FILE_PATH_LITERAL("|"), 1);
+      observed.append(comps[j]);
+    }
+    EXPECT_EQ(FilePath::StringType(cases[i].expected), observed) <<
+              "i: " << i << ", input: " << input.value();
+  }
+}
+
+TEST_F(FilePathTest, IsParentTest) {
+  const struct BinaryBooleanTestData cases[] = {
+    { { FPL("/"),             FPL("/foo/bar/baz") },        true},
+    { { FPL("/foo/bar"),      FPL("/foo/bar/baz") },        true},
+    { { FPL("/foo/bar/"),     FPL("/foo/bar/baz") },        true},
+    { { FPL("//foo/bar/"),    FPL("//foo/bar/baz") },       true},
+    { { FPL("/foo/bar"),      FPL("/foo2/bar/baz") },       false},
+    { { FPL("/foo/bar.txt"),  FPL("/foo/bar/baz") },        false},
+    { { FPL("/foo/bar"),      FPL("/foo/bar2/baz") },       false},
+    { { FPL("/foo/bar"),      FPL("/foo/bar") },            false},
+    { { FPL("/foo/bar/baz"),  FPL("/foo/bar") },            false},
+    { { FPL("foo/bar"),       FPL("foo/bar/baz") },         true},
+    { { FPL("foo/bar"),       FPL("foo2/bar/baz") },        false},
+    { { FPL("foo/bar"),       FPL("foo/bar2/baz") },        false},
+    { { FPL(""),              FPL("foo") },                 false},
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+    { { FPL("c:/foo/bar"),    FPL("c:/foo/bar/baz") },      true},
+    { { FPL("c:/"),           FPL("c:/foo/bar/baz") },      true},
+    { { FPL("c:"),            FPL("c:/foo/bar/baz") },      true},
+    { { FPL("c:/foo/bar"),    FPL("d:/foo/bar/baz") },      false},
+    { { FPL("c:/foo/bar"),    FPL("c:/foo2/bar/baz") },     false},
+    { { FPL("c:/foo/bar"),    FPL("c:/foo/bar2/baz") },     false},
+#endif  // FILE_PATH_USES_DRIVE_LETTERS
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+    { { FPL("\\foo\\bar"),    FPL("\\foo\\bar\\baz") },     true},
+    { { FPL("\\foo/bar"),     FPL("\\foo\\bar\\baz") },     true},
+    { { FPL("\\foo/bar"),     FPL("\\foo/bar/baz") },       true},
+    { { FPL("\\"),            FPL("\\foo\\bar\\baz") },     true},
+    { { FPL(""),              FPL("\\foo\\bar\\baz") },     false},
+    { { FPL("\\foo\\bar"),    FPL("\\foo2\\bar\\baz") },    false},
+    { { FPL("\\foo\\bar"),    FPL("\\foo\\bar2\\baz") },    false},
+#endif  // FILE_PATH_USES_DRIVE_LETTERS
+  };
+
+  for (size_t i = 0; i < arraysize(cases); ++i) {
+    FilePath parent(cases[i].inputs[0]);
+    FilePath child(cases[i].inputs[1]);
+
+    EXPECT_EQ(parent.IsParent(child), cases[i].expected) <<
+      "i: " << i << ", parent: " << parent.value() << ", child: " <<
+      child.value();
+  }
+}
+
 TEST_F(FilePathTest, Extension) {
   FilePath base_dir(FILE_PATH_LITERAL("base_dir"));
 
diff --git a/base/file_util.cc b/base/file_util.cc
index 5f18674..c366a76 100644
--- a/base/file_util.cc
+++ b/base/file_util.cc
@@ -26,34 +26,6 @@
 
 namespace file_util {
 
-void PathComponents(const FilePath& path,
-                    std::vector<FilePath::StringType>* components) {
-  DCHECK(components);
-  if (!components)
-    return;
-
-  FilePath::StringType path_str = path.value();
-  FilePath::StringType::size_type start = 0;
-  FilePath::StringType::size_type end =
-      path_str.find_first_of(FilePath::kSeparators);
-
-  // If the path starts with a separator, add it to components.
-  if (end == start) {
-    components->push_back(FilePath::StringType(path_str, 0, 1));
-    start = end + 1;
-    end = path_str.find_first_of(FilePath::kSeparators, start);
-  }
-  while (end != FilePath::StringType::npos) {
-    FilePath::StringType component =
-        FilePath::StringType(path_str, start, end - start);
-    components->push_back(component);
-    start = end + 1;
-    end = path_str.find_first_of(FilePath::kSeparators, start);
-  }
-
-  components->push_back(FilePath::StringType(path_str, start));
-}
-
 bool EndsWithSeparator(const FilePath& path) {
   FilePath::StringType value = path.value();
   if (value.empty())
diff --git a/base/file_util.h b/base/file_util.h
index 39d8f09..30918d3 100644
--- a/base/file_util.h
+++ b/base/file_util.h
@@ -38,10 +38,6 @@
 //-----------------------------------------------------------------------------
 // Functions that operate purely on a path string w/o touching the filesystem:
 
-// Returns a vector of all of the components of the provided path.
-void PathComponents(const FilePath& path,
-                    std::vector<FilePath::StringType>* components);
-
 // Returns true if the given path ends with a path separator character.
 bool EndsWithSeparator(const FilePath& path);
 // These two versions are both deprecated. TODO(estade): remove them.
diff --git a/base/file_util_unittest.cc b/base/file_util_unittest.cc
index 738bac8..e510594 100644
--- a/base/file_util_unittest.cc
+++ b/base/file_util_unittest.cc
@@ -1072,57 +1072,6 @@
   EXPECT_EQ(FILE_PATH_LITERAL(""), cur_file.value());
 }
 
-void PathComponents(const std::wstring& path,
-                    std::vector<std::wstring>* components) {
-  DCHECK(components != NULL);
-  if (components == NULL)
-    return;
-  std::wstring::size_type start = 0;
-  std::wstring::size_type end = path.find('/', start);
-
-  // Special case the "/" or "\" directory.  On Windows with a drive letter,
-  // this code path won't hit, but the right thing should still happen.
-  // "E:\foo" will turn into "E:","foo".
-  if (end == start) {
-    components->push_back(std::wstring(path, 0, 1));
-    start = end + 1;
-    end = path.find('/', start);
-  }
-  while (end != std::wstring::npos) {
-    std::wstring component = std::wstring(path, start, end - start);
-    components->push_back(component);
-    start = end + 1;
-    end = path.find('/', start);
-  }
-  std::wstring component = std::wstring(path, start);
-  components->push_back(component);
-}
-
-static const struct PathComponentsCase {
-  const FilePath::CharType* path;
-  const FilePath::CharType* result;
-} kPathComponents[] = {
-  {FILE_PATH_LITERAL("/foo/bar/baz/"), FILE_PATH_LITERAL("/|foo|bar|baz|")},
-  {FILE_PATH_LITERAL("/foo/bar/baz"), FILE_PATH_LITERAL("/|foo|bar|baz")},
-  {FILE_PATH_LITERAL("e:/foo"), FILE_PATH_LITERAL("e:|foo")},
-};
-
-TEST_F(FileUtilTest, PathComponentsTest) {
-  for (size_t i = 0; i < arraysize(kPathComponents); ++i) {
-    FilePath path(kPathComponents[i].path);
-    std::vector<FilePath::StringType> comps;
-    file_util::PathComponents(path, &comps);
-
-    FilePath::StringType result;
-    for (size_t j = 0; j < comps.size(); ++j) {
-      result.append(comps[j]);
-      if (j < comps.size() - 1)
-        result.append(FILE_PATH_LITERAL("|"), 1);
-    }
-    EXPECT_EQ(kPathComponents[i].result, result);
-  }
-}
-
 TEST_F(FileUtilTest, Contains) {
   FilePath data_dir = test_dir_.Append(FILE_PATH_LITERAL("FilePathTest"));