Minimize dependency of user scripts.

And made some minor lint fixes and code refactoring on the way, based on CR comments of previous attempt.

BUG=none
TEST=Make sure that the extension resources can still be properly localized and that they also load correctly when they are not localized.

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

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


CrOS-Libchrome-Original-Commit: 9194b3fc91953bb1472d8671d2322ac5616ee0b4
diff --git a/base/file_path.cc b/base/file_path.cc
index 1d90674..2152187 100644
--- a/base/file_path.cc
+++ b/base/file_path.cc
@@ -158,6 +158,75 @@
 #endif  // defined(FILE_PATH_USES_DRIVE_LETTERS)
 }
 
+bool FilePath::AppendAndResolveRelative(const FilePath& relative_path,
+                                        FilePath* path) const {
+  DCHECK(path);
+  if (!path || relative_path.IsAbsolute())
+    return false;
+
+  FilePath full_path = Append(relative_path);
+  // Is it worth looking for parent references?
+  if (!full_path.ReferencesParent()) {
+    *path = full_path;
+    return true;
+  }
+
+  // If the parent has a drive letter, then we must not remove the first
+  // component, which is the drive letter.
+  bool drive_letter = (FindDriveLetter(full_path.path_) !=
+                       FilePath::StringType::npos);
+
+  std::vector<FilePath::StringType> components;
+  full_path.GetComponents(&components);
+  std::vector<FilePath::StringType>::iterator it = components.begin();
+  // Start by removing any kCurrentDirectory component, since they may
+  // fool us into not going back to the appropriate parent level.
+  for (; it != components.end(); ++it) {
+    if (*it == kCurrentDirectory) {
+      // erase returns an iterator to the next component.
+      it = components.erase(it);
+      // So now, go back to previous iterator,
+      // so that we can appropriately process the next one as we loop.
+      --it;
+    }
+  }
+
+  // Now parse the component looking for kParentDirectory and remove them as
+  // well as the previous component.
+  it = components.begin();
+  for (; it != components.end(); ++it) {
+    if (*it == kParentDirectory) {
+      // Did we reach the beginning?
+      if (it == components.begin() ||
+          (drive_letter && (it - 1) == components.begin())) {
+        return false;
+      }
+      // Remove the previous component, as well as the current one.
+      std::vector<FilePath::StringType>::iterator previous = it - 1;
+      // Unless the previous is at the beginning.
+      if (previous == components.begin() ||
+          (drive_letter && (previous - 1) == components.begin())) {
+        return false;
+      }
+      // vector::erase doesn't erase _Last, it erases [_First, _Last[,
+      // so we must increment current which we want erased.
+      it = components.erase(previous, it + 1);
+      // And go back to previous so that we can process the next one as we loop.
+      --it;
+    }
+  }
+
+  // Now reconstruct the path with the components that were left in.
+  it = components.begin();
+  // We start with the first component, in case it is absolute
+  // and absolute paths can't be appended.
+  *path = FilePath(*it);
+  for (++it; it != components.end(); ++it)
+    *path = path->Append(*it);
+
+  return true;
+}
+
 bool FilePath::IsParent(const FilePath& child) const {
   return AppendRelativePath(child, NULL);
 }
@@ -388,11 +457,9 @@
   // directory) or if the path component is empty (indicating nothing to
   // append).
   if (component.length() > 0 && new_path.path_.length() > 0) {
-
     // Don't append a separator if the path still ends with a trailing
     // separator after stripping (indicating the root directory).
     if (!IsSeparator(new_path.path_[new_path.path_.length() - 1])) {
-
       // Don't append a separator if the path is just a drive letter.
       if (FindDriveLetter(new_path.path_) + 1 != new_path.path_.length()) {
         new_path.path_.append(1, kSeparators[0]);
diff --git a/base/file_path.h b/base/file_path.h
index 7106c90..5c8f183 100644
--- a/base/file_path.h
+++ b/base/file_path.h
@@ -178,6 +178,18 @@
   // and BaseName().value() on each child component.
   void GetComponents(std::vector<FilePath::StringType>* components) const;
 
+  // Returns true, and sets *path to resulting full path, if relative_path can
+  // be applied to current path by resolving any '..' it may contain. Returns
+  // false otherwise, e.g., if relative path is absolute, or if it climbs back
+  // up the hierarchy too far (i.e., beyond the root of current path).
+  //
+  // Note that if the current path ends with a file name, we won't try to
+  // figure it out (so this method doesn't go to the disk) and we will blindly
+  // append relative_path at the end of the current path, including the file
+  // name in the current path (if any).
+  bool AppendAndResolveRelative(const FilePath& relative_path,
+                                FilePath* path) 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
diff --git a/base/file_path_unittest.cc b/base/file_path_unittest.cc
index b80d9fc..9d45170 100644
--- a/base/file_path_unittest.cc
+++ b/base/file_path_unittest.cc
@@ -497,6 +497,98 @@
   }
 }
 
+TEST_F(FilePathTest, AppendAndResolveRelativeTest) {
+  const struct BinaryTestData cases[] = {
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+    { { FPL("c:/"),              FPL("foo") },        FPL("c:/foo") },
+    { { FPL("f:/foo/bar"),       FPL("..") },         FPL("f:/foo") },
+    { { FPL("f:/foo.bar"),       FPL("..") },         FPL("f:/") },
+    { { FPL("F:/foo/.."),        FPL("./bar/.") },    FPL("F:/bar") },
+    { { FPL("E:/Foo/bar"),       FPL("../..") },      FPL("E:/") },
+    { { FPL("E:/Foo/bar/."),     FPL("../..") },      FPL("E:/") },
+    { { FPL("e:/foo/.."),        FPL("bar/..") },     FPL("e:/") },
+    { { FPL("c:/foo/./bar/.."),  FPL("../baz") },     FPL("c:/baz") },
+    { { FPL("E:/./foo/bar/.."),  FPL("../baz/..") },  FPL("E:/") },
+    { { FPL("x:/foo/../bar/.."), FPL("baz/../boo") }, FPL("x:/boo") },
+    { { FPL("E:/foo.bar/.."),    FPL("../baz/..") },  FPL("") },
+    { { FPL("Z:/foo"),           FPL("../..") },      FPL("") },
+    { { FPL("y:/"),              FPL("..") },         FPL("") },
+    { { FPL("B:/.."),            FPL("bar/.") },      FPL("") },
+    { { FPL("a:/foo/.."),        FPL("..") },         FPL("") },
+    { { FPL("r:/.."),            FPL("..") },         FPL("") },
+    { { FPL("F:/foo/.."),        FPL("../..") },      FPL("") },
+    { { FPL("O:/foo/bar/.."),    FPL("../..") },      FPL("") },
+#endif  // FILE_PATH_USES_DRIVE_LETTERS
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+    { { FPL("\\\\"),                FPL("foo") },         FPL("\\\\foo") },
+    { { FPL("\\\\foo"),             FPL("bar") },         FPL("\\\\foo\\bar") },
+    { { FPL("\\\\foo\\bar"),        FPL("..") },          FPL("\\\\foo") },
+    { { FPL("\\\\foo.bar"),         FPL("..") },          FPL("\\\\") },
+    { { FPL("\\\\Foo\\bar"),        FPL("..\\..") },      FPL("\\\\") },
+    { { FPL("\\\\Foo\\bar\\."),     FPL("..\\..") },      FPL("\\\\") },
+    { { FPL("\\\\foo\\bar"), FPL("foo\\..\\baz") }, FPL("\\\\foo\\bar\\baz") },
+    { { FPL("\\\\foo\\.\\bar"),     FPL("..\\baz\\.") },  FPL("\\\\foo\\baz") },
+    { { FPL("\\\\.\\foo\\.."),      FPL("bar") },         FPL("\\\\bar") },
+    { { FPL("\\\\foo\\.."),         FPL(".\\bar\\..") },  FPL("\\\\") },
+    { { FPL("\\\\foo\\bar\\.."),    FPL("..\\baz") },     FPL("\\\\baz") },
+    { { FPL("\\\\foo\\bar\\.."),    FPL("..\\baz\\..") }, FPL("\\\\") },
+    { { FPL("\\\\foo\\..\\bar\\.."), FPL("baz\\..\\boo") }, FPL("\\\\boo"), },
+    { { FPL("\\\\foo.bar\\.."),     FPL("..\\baz\\..") }, FPL("") },
+    { { FPL("\\\\foo"),             FPL("..\\..") },      FPL("") },
+    { { FPL("\\\\"),                FPL("..") },          FPL("") },
+    { { FPL("\\\\.."),              FPL("bar\\.") },      FPL("") },
+    { { FPL("\\\\foo\\.."),         FPL("..") },          FPL("") },
+    { { FPL("\\\\.."),              FPL("..") },          FPL("") },
+    { { FPL("\\\\foo\\.."),         FPL("..\\..") },      FPL("") },
+    { { FPL("\\\\foo\\bar\\.."),    FPL("..\\..") },      FPL("") },
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+    { { FPL("E:/foo"),           FPL("bar") },        FPL("E:/foo\\bar") },
+    { { FPL("C:/foo/bar"),       FPL("foo/../baz") }, FPL("C:/foo\\bar\\baz") },
+    { { FPL("e:/foo/bar"),       FPL("../baz") },     FPL("e:/foo\\baz") },
+#endif
+#else  // FILE_PATH_USES_WIN_SEPARAORS
+    { { FPL("/"),              FPL("foo") },        FPL("/foo") },
+    { { FPL("/foo"),           FPL("bar") },        FPL("/foo/bar") },
+    { { FPL("/foo/bar/"),      FPL("..") },         FPL("/foo") },
+    { { FPL("/foo.bar"),       FPL("..") },         FPL("/") },
+    { { FPL("//foo"),          FPL("..") },         FPL("//") },
+    { { FPL("/foo/./bar"),     FPL("../..") },      FPL("/") },
+    { { FPL("/foo/bar/."),     FPL("foo/../baz") }, FPL("/foo/bar/baz") },
+    { { FPL("/./foo/bar"),     FPL("../baz/.") },   FPL("/foo/baz") },
+    { { FPL("/foo/.."),        FPL("./bar") },      FPL("/bar") },
+    { { FPL("/foo/.."),        FPL("bar/..") },     FPL("/") },
+    { { FPL("//foo/bar/.."),   FPL("../baz") },     FPL("//baz") },
+    { { FPL("/foo/bar/.."),    FPL("../baz/..") },  FPL("/") },
+    { { FPL("/foo/../bar/.."), FPL("baz/../boo") }, FPL("/boo") },
+    { { FPL("//foo.bar/.."),   FPL("../baz") },     FPL("") },
+    { { FPL("/foo"),           FPL("../..") },      FPL("") },
+    { { FPL("//"),             FPL("..") },         FPL("") },
+    { { FPL("/.."),            FPL("./bar") },      FPL("") },
+    { { FPL("/foo/.."),        FPL("..") },         FPL("") },
+    { { FPL("/.."),            FPL("..") },         FPL("") },
+    { { FPL("/foo/.."),        FPL("../..") },      FPL("") },
+    { { FPL("/foo/bar/.."),    FPL("../..") },      FPL("") },
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+    { { FPL("E:/foo"),           FPL("bar") },        FPL("E:/foo/bar") },
+    { { FPL("C:/foo/bar"),       FPL("foo/../baz") }, FPL("C:/foo/bar/baz") },
+    { { FPL("e:/foo/bar"),       FPL("../baz") },     FPL("e:/foo/baz") },
+#endif
+#endif  // FILE_PATH_USES_WIN_SEPARAORS
+  };
+
+  for (size_t i = 0; i < arraysize(cases); ++i) {
+    FilePath parent(cases[i].inputs[0]);
+    FilePath child(cases[i].inputs[1]);
+
+    FilePath result;
+    EXPECT_EQ(cases[i].expected[0] != '\0',
+              parent.AppendAndResolveRelative(child, &result)) <<
+        "i: " << i << ", parent: " << parent.value() << ", child: " <<
+        child.value();
+    EXPECT_STREQ(cases[i].expected, result.value().c_str());
+  }
+}
+
 TEST_F(FilePathTest, IsParentTest) {
   const struct BinaryBooleanTestData cases[] = {
     { { FPL("/"),             FPL("/foo/bar/baz") },      true},
@@ -544,8 +636,8 @@
     FilePath child(cases[i].inputs[1]);
 
     EXPECT_EQ(parent.IsParent(child), cases[i].expected) <<
-      "i: " << i << ", parent: " << parent.value() << ", child: " <<
-      child.value();
+        "i: " << i << ", parent: " << parent.value() << ", child: " <<
+        child.value();
   }
 }