Unpack extensions inside chrome's profile directory.

Other users of the temp directory will be altered in a subsequent CL.

BUG=13044
TEST=SandboxedExtensionUnpackerTest.*, ScopedTempDir.UniqueTempDirUnderPath, FileUtilTest.CreateNewTempDirInDirTest, manual testing on win, linux, mac.

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@46078 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/base/file_util.h b/base/file_util.h
index 96891e6..467b4041 100644
--- a/base/file_util.h
+++ b/base/file_util.h
@@ -249,6 +249,13 @@
 bool CreateTemporaryFileInDir(const FilePath& dir,
                               FilePath* temp_file);
 
+// Create a directory within another directory.
+// Extra characters will be appended to |name_tmpl| to ensure that the
+// new directory does not have the same name as an existing directory.
+bool CreateTemporaryDirInDir(const FilePath& base_dir,
+                             const FilePath::StringType& prefix,
+                             FilePath* new_dir);
+
 // Create a new directory under TempPath. If prefix is provided, the new
 // directory name is in the format of prefixyyyy.
 // NOTE: prefix is ignored in the POSIX implementation.
diff --git a/base/file_util_posix.cc b/base/file_util_posix.cc
index 4d4e572..d9cbe09 100644
--- a/base/file_util_posix.cc
+++ b/base/file_util_posix.cc
@@ -401,20 +401,39 @@
   return ((fd >= 0) && !close(fd));
 }
 
+static bool CreateTemporaryDirInDirImpl(const FilePath& base_dir,
+                                        const FilePath::StringType& name_tmpl,
+                                        FilePath* new_dir) {
+  CHECK(name_tmpl.find("XXXXXX") != FilePath::StringType::npos)
+    << "Directory name template must contain \"XXXXXX\".";
+
+  FilePath sub_dir = base_dir.Append(name_tmpl);
+  std::string sub_dir_string = sub_dir.value();
+
+  // this should be OK since mkdtemp just replaces characters in place
+  char* buffer = const_cast<char*>(sub_dir_string.c_str());
+  char* dtemp = mkdtemp(buffer);
+  if (!dtemp)
+    return false;
+  *new_dir = FilePath(dtemp);
+  return true;
+}
+
+bool CreateTemporaryDirInDir(const FilePath& base_dir,
+                             const FilePath::StringType& prefix,
+                             FilePath* new_dir) {
+  FilePath::StringType mkdtemp_template = prefix;
+  mkdtemp_template.append(FILE_PATH_LITERAL("XXXXXX"));
+  return CreateTemporaryDirInDirImpl(base_dir, mkdtemp_template, new_dir);
+}
+
 bool CreateNewTempDirectory(const FilePath::StringType& prefix,
                             FilePath* new_temp_path) {
   FilePath tmpdir;
   if (!GetTempDir(&tmpdir))
     return false;
-  tmpdir = tmpdir.Append(kTempFileName);
-  std::string tmpdir_string = tmpdir.value();
-  // this should be OK since mkdtemp just replaces characters in place
-  char* buffer = const_cast<char*>(tmpdir_string.c_str());
-  char* dtemp = mkdtemp(buffer);
-  if (!dtemp)
-    return false;
-  *new_temp_path = FilePath(dtemp);
-  return true;
+
+  return CreateTemporaryDirInDirImpl(tmpdir, kTempFileName, new_temp_path);
 }
 
 bool CreateDirectory(const FilePath& full_path) {
diff --git a/base/file_util_unittest.cc b/base/file_util_unittest.cc
index c46c39f..25b93e1 100644
--- a/base/file_util_unittest.cc
+++ b/base/file_util_unittest.cc
@@ -1141,6 +1141,17 @@
   EXPECT_TRUE(file_util::Delete(temp_dir, false));
 }
 
+TEST_F(FileUtilTest, CreateNewTemporaryDirInDirTest) {
+  FilePath new_dir;
+  ASSERT_TRUE(file_util::CreateTemporaryDirInDir(
+                  test_dir_,
+                  FILE_PATH_LITERAL("CreateNewTemporaryDirInDirTest"),
+                  &new_dir));
+  EXPECT_TRUE(file_util::PathExists(new_dir));
+  EXPECT_TRUE(test_dir_.IsParent(new_dir));
+  EXPECT_TRUE(file_util::Delete(new_dir, false));
+}
+
 TEST_F(FileUtilTest, GetShmemTempDirTest) {
   FilePath dir;
   EXPECT_TRUE(file_util::GetShmemTempDir(&dir));
diff --git a/base/file_util_win.cc b/base/file_util_win.cc
index d1cdc6ce..445f82e 100644
--- a/base/file_util_win.cc
+++ b/base/file_util_win.cc
@@ -500,12 +500,9 @@
   return true;
 }
 
-bool CreateNewTempDirectory(const FilePath::StringType& prefix,
-                            FilePath* new_temp_path) {
-  FilePath system_temp_dir;
-  if (!GetTempDir(&system_temp_dir))
-    return false;
-
+bool CreateTemporaryDirInDir(const FilePath& base_dir,
+                             const FilePath::StringType& prefix,
+                             FilePath* new_dir) {
   FilePath path_to_create;
   srand(static_cast<uint32>(time(NULL)));
 
@@ -513,12 +510,13 @@
   while (count < 50) {
     // Try create a new temporary directory with random generated name. If
     // the one exists, keep trying another path name until we reach some limit.
-    path_to_create = system_temp_dir;
+    path_to_create = base_dir;
+
     std::wstring new_dir_name;
     new_dir_name.assign(prefix);
     new_dir_name.append(IntToWString(rand() % kint16max));
-    path_to_create = path_to_create.Append(new_dir_name);
 
+    path_to_create = path_to_create.Append(new_dir_name);
     if (::CreateDirectory(path_to_create.value().c_str(), NULL))
       break;
     count++;
@@ -528,10 +526,19 @@
     return false;
   }
 
-  *new_temp_path = path_to_create;
+  *new_dir = path_to_create;
   return true;
 }
 
+bool CreateNewTempDirectory(const FilePath::StringType& prefix,
+                            FilePath* new_temp_path) {
+  FilePath system_temp_dir;
+  if (!GetTempDir(&system_temp_dir))
+    return false;
+
+  return CreateTemporaryDirInDir(system_temp_dir, prefix, new_temp_path);
+}
+
 bool CreateDirectory(const FilePath& full_path) {
   // If the path exists, we've succeeded if it's a directory, failed otherwise.
   const wchar_t* full_path_str = full_path.value().c_str();
diff --git a/base/scoped_temp_dir.cc b/base/scoped_temp_dir.cc
index 07fcfb7..c8ed9c6 100644
--- a/base/scoped_temp_dir.cc
+++ b/base/scoped_temp_dir.cc
@@ -26,6 +26,21 @@
   return true;
 }
 
+bool ScopedTempDir::CreateUniqueTempDirUnderPath(const FilePath& base_path) {
+  // If |path| does not exist, create it.
+  if (!file_util::CreateDirectory(base_path))
+    return false;
+
+  // Create a new, uniquly named directory under |base_path|.
+  if (!file_util::CreateTemporaryDirInDir(
+          base_path,
+          FILE_PATH_LITERAL("scoped_dir_"),
+          &path_)) {
+    return false;
+  }
+  return true;
+}
+
 bool ScopedTempDir::Set(const FilePath& path) {
   DCHECK(path_.empty());
   if (!file_util::DirectoryExists(path) &&
diff --git a/base/scoped_temp_dir.h b/base/scoped_temp_dir.h
index e9d45b9..a5dca1e 100644
--- a/base/scoped_temp_dir.h
+++ b/base/scoped_temp_dir.h
@@ -25,6 +25,9 @@
   // See file_util::CreateNewTemporaryDirectory.
   bool CreateUniqueTempDir();
 
+  // Creates a unique directory under a given path, and takes ownership of it.
+  bool CreateUniqueTempDirUnderPath(const FilePath& path);
+
   // Takes ownership of directory at |path|, creating it if necessary.
   // Don't call multiple times unless Take() has been called first.
   bool Set(const FilePath& path);
diff --git a/base/scoped_temp_dir_unittest.cc b/base/scoped_temp_dir_unittest.cc
index 72f4d8ce..4be0d07 100644
--- a/base/scoped_temp_dir_unittest.cc
+++ b/base/scoped_temp_dir_unittest.cc
@@ -55,3 +55,21 @@
   }
   EXPECT_FALSE(file_util::DirectoryExists(test_path));
 }
+
+TEST(ScopedTempDir, UniqueTempDirUnderPath) {
+  // Create a path which will contain a unique temp path.
+  FilePath base_path;
+  file_util::CreateNewTempDirectory(FILE_PATH_LITERAL("base_dir"),
+                                    &base_path);
+
+  FilePath test_path;
+  {
+    ScopedTempDir dir;
+    EXPECT_TRUE(dir.CreateUniqueTempDirUnderPath(base_path));
+    test_path = dir.path();
+    EXPECT_TRUE(file_util::DirectoryExists(test_path));
+    EXPECT_TRUE(base_path.IsParent(test_path));
+    EXPECT_TRUE(test_path.value().find(base_path.value()) != std::string::npos);
+  }
+  EXPECT_FALSE(file_util::DirectoryExists(test_path));
+}
diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc
index feb57539..f49db955 100644
--- a/chrome/browser/extensions/crx_installer.cc
+++ b/chrome/browser/extensions/crx_installer.cc
@@ -7,6 +7,7 @@
 #include "app/l10n_util.h"
 #include "app/resource_bundle.h"
 #include "base/file_util.h"
+#include "base/path_service.h"
 #include "base/scoped_temp_dir.h"
 #include "base/task.h"
 #include "base/utf_string_conversions.h"
@@ -17,6 +18,7 @@
 #include "chrome/browser/profile.h"
 #include "chrome/browser/shell_integration.h"
 #include "chrome/browser/web_applications/web_app.h"
+#include "chrome/common/chrome_paths.h"
 #include "chrome/common/extensions/extension_file_util.h"
 #include "chrome/common/notification_service.h"
 #include "chrome/common/notification_type.h"
@@ -70,9 +72,13 @@
 void CrxInstaller::InstallCrx(const FilePath& source_file) {
   source_file_ = source_file;
 
+  FilePath user_data_temp_dir;
+  CHECK(PathService::Get(chrome::DIR_USER_DATA_TEMP, &user_data_temp_dir));
+
   scoped_refptr<SandboxedExtensionUnpacker> unpacker(
       new SandboxedExtensionUnpacker(
           source_file,
+          user_data_temp_dir,
           g_browser_process->resource_dispatcher_host(),
           this));
 
diff --git a/chrome/browser/extensions/sandboxed_extension_unpacker.cc b/chrome/browser/extensions/sandboxed_extension_unpacker.cc
index a976400..9df675c 100644
--- a/chrome/browser/extensions/sandboxed_extension_unpacker.cc
+++ b/chrome/browser/extensions/sandboxed_extension_unpacker.cc
@@ -28,10 +28,13 @@
 const char SandboxedExtensionUnpacker::kExtensionHeaderMagic[] = "Cr24";
 
 SandboxedExtensionUnpacker::SandboxedExtensionUnpacker(
-    const FilePath& crx_path, ResourceDispatcherHost* rdh,
+    const FilePath& crx_path,
+    const FilePath& temp_path,
+    ResourceDispatcherHost* rdh,
     SandboxedExtensionUnpackerClient* client)
-      : crx_path_(crx_path), thread_identifier_(ChromeThread::ID_COUNT),
-        rdh_(rdh), client_(client), got_response_(false) {
+    : crx_path_(crx_path), temp_path_(temp_path),
+      thread_identifier_(ChromeThread::ID_COUNT),
+      rdh_(rdh), client_(client), got_response_(false) {
 }
 
 void SandboxedExtensionUnpacker::Start() {
@@ -40,13 +43,14 @@
   CHECK(ChromeThread::GetCurrentThreadIdentifier(&thread_identifier_));
 
   // Create a temporary directory to work in.
-  if (!temp_dir_.CreateUniqueTempDir()) {
+  if (!temp_dir_.CreateUniqueTempDirUnderPath(temp_path_)) {
     ReportFailure("Could not create temporary directory.");
     return;
   }
 
   // Initialize the path that will eventually contain the unpacked extension.
-  extension_root_ = temp_dir_.path().AppendASCII("TEMP_INSTALL");
+  extension_root_ = temp_dir_.path().AppendASCII(
+      extension_filenames::kTempExtensionName);
 
   // Extract the public key and validate the package.
   if (!ValidateSignature())
diff --git a/chrome/browser/extensions/sandboxed_extension_unpacker.h b/chrome/browser/extensions/sandboxed_extension_unpacker.h
index a3b190c41..42a334061 100644
--- a/chrome/browser/extensions/sandboxed_extension_unpacker.h
+++ b/chrome/browser/extensions/sandboxed_extension_unpacker.h
@@ -93,6 +93,7 @@
   // |client| with the result. If |rdh| is provided, unpacking is done in a
   // sandboxed subprocess. Otherwise, it is done in-process.
   SandboxedExtensionUnpacker(const FilePath& crx_path,
+                             const FilePath& temp_path,
                              ResourceDispatcherHost* rdh,
                              SandboxedExtensionUnpackerClient* cilent);
 
@@ -145,6 +146,9 @@
   // The path to the CRX to unpack.
   FilePath crx_path_;
 
+  // A path to a temp dir to unpack in.
+  FilePath temp_path_;
+
   // Our client's thread. This is the thread we respond on.
   ChromeThread::ID thread_identifier_;
 
diff --git a/chrome/browser/extensions/sandboxed_extension_unpacker_unittest.cc b/chrome/browser/extensions/sandboxed_extension_unpacker_unittest.cc
index f320cc0..748cd46b 100644
--- a/chrome/browser/extensions/sandboxed_extension_unpacker_unittest.cc
+++ b/chrome/browser/extensions/sandboxed_extension_unpacker_unittest.cc
@@ -87,15 +87,20 @@
 
     unpacker_.reset(new ExtensionUnpacker(crx_path));
 
-    // It will delete itself.
+
+    // Build a temp area where the extension will be unpacked.
+    ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &temp_dir_));
+    temp_dir_ = temp_dir_.AppendASCII("sandboxed_extension_unpacker_test_Temp");
+    file_util::CreateDirectory(temp_dir_);
+
     sandboxed_unpacker_ =
-        new SandboxedExtensionUnpacker(crx_path, NULL, client_);
+      new SandboxedExtensionUnpacker(crx_path, temp_dir_, NULL, client_);
     PrepareUnpackerEnv();
   }
 
   void PrepareUnpackerEnv() {
     sandboxed_unpacker_->extension_root_ =
-      install_dir_.AppendASCII("TEMP_INSTALL");
+      install_dir_.AppendASCII(extension_filenames::kTempExtensionName);
 
     sandboxed_unpacker_->temp_dir_.Set(install_dir_);
     sandboxed_unpacker_->public_key_ =
@@ -108,11 +113,36 @@
   }
 
   FilePath GetInstallPath() {
-    return install_dir_.AppendASCII("TEMP_INSTALL");
+    return install_dir_.AppendASCII(extension_filenames::kTempExtensionName);
+  }
+
+  bool TempFilesRemoved() {
+    // Check that temporary files were cleaned up.
+    file_util::FileEnumerator::FILE_TYPE files_and_dirs =
+      static_cast<file_util::FileEnumerator::FILE_TYPE>(
+        file_util::FileEnumerator::DIRECTORIES |
+        file_util::FileEnumerator::FILES);
+
+    file_util::FileEnumerator temp_iterator(
+      temp_dir_,
+      true,  // recursive
+      files_and_dirs
+    );
+    int items_not_removed = 0;
+    FilePath item_in_temp;
+    item_in_temp = temp_iterator.Next();
+    while (!item_in_temp.value().empty()) {
+      items_not_removed++;
+      EXPECT_STREQ(FILE_PATH_LITERAL(""), item_in_temp.value().c_str())
+        << "File was not removed on success.";
+      item_in_temp = temp_iterator.Next();
+    }
+    return (items_not_removed == 0);
   }
 
  protected:
   FilePath install_dir_;
+  FilePath temp_dir_;
   MockSandboxedExtensionUnpackerClient* client_;
   scoped_ptr<ExtensionUnpacker> unpacker_;
   scoped_refptr<SandboxedExtensionUnpacker> sandboxed_unpacker_;
@@ -135,6 +165,8 @@
 
   // Check that there still is no _locales folder.
   EXPECT_FALSE(file_util::PathExists(install_path));
+
+  ASSERT_TRUE(TempFilesRemoved());
 }
 
 TEST_F(SandboxedExtensionUnpackerTest, WithCatalogsSuccess) {
@@ -164,4 +196,6 @@
   EXPECT_TRUE(file_util::GetFileInfo(messages_file, &new_info));
 
   EXPECT_TRUE(new_info.last_modified > old_info.last_modified);
+
+  ASSERT_TRUE(TempFilesRemoved());
 }
diff --git a/chrome/common/chrome_paths.cc b/chrome/common/chrome_paths.cc
index eccb65b..aefcebc 100644
--- a/chrome/common/chrome_paths.cc
+++ b/chrome/common/chrome_paths.cc
@@ -186,6 +186,12 @@
       cur = cur.Append(FILE_PATH_LITERAL("Dictionaries"));
       create_dir = true;
       break;
+    case chrome::DIR_USER_DATA_TEMP:
+      if (!PathService::Get(chrome::DIR_USER_DATA, &cur))
+        return false;
+      cur = cur.Append(FILE_PATH_LITERAL("Temp"));
+      create_dir = true;
+      break;
     case chrome::FILE_LOCAL_STATE:
       if (!PathService::Get(chrome::DIR_USER_DATA, &cur))
         return false;
diff --git a/chrome/common/chrome_paths.h b/chrome/common/chrome_paths.h
index c5e13d29..9ed8057 100644
--- a/chrome/common/chrome_paths.h
+++ b/chrome/common/chrome_paths.h
@@ -32,6 +32,10 @@
   DIR_DEFAULT_DOWNLOADS_SAFE,   // Directory for a user's
                                 // "My Documents/Downloads".
   DIR_DEFAULT_DOWNLOADS,        // Directory for a user's downloads.
+  DIR_USER_DATA_TEMP,           // A temp directory within DIR_USER_DATA.  Use
+                                // this when a temporary file or directory will
+                                // be moved into the profile, to avoid issues
+                                // moving across volumes.  See crbug.com/13044 .
   FILE_RESOURCE_MODULE,         // Full path and filename of the module that
                                 // contains embedded resources (version,
                                 // strings, images, etc.).
diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc
index 1db05cb5..174701c 100644
--- a/chrome/common/extensions/extension_constants.cc
+++ b/chrome/common/extensions/extension_constants.cc
@@ -253,3 +253,14 @@
 const char* kMiniGalleryBrowsePrefix = "https://tools.google.com/chrome/";
 const char* kMiniGalleryDownloadPrefix = "https://dl-ssl.google.com/chrome/";
 }
+
+namespace extension_filenames {
+const char* kTempExtensionName = "CRX_INSTALL";
+
+// The file to write our decoded images to, relative to the extension_path.
+const char* kDecodedImagesFilename = "DECODED_IMAGES";
+
+// The file to write our decoded message catalogs to, relative to the
+// extension_path.
+const char* kDecodedMessageCatalogsFilename = "DECODED_MESSAGE_CATALOGS";
+}
diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h
index 1b1e798..36ba762 100644
--- a/chrome/common/extensions/extension_constants.h
+++ b/chrome/common/extensions/extension_constants.h
@@ -175,4 +175,17 @@
   extern const char* kMiniGalleryDownloadPrefix;
 }  // namespace extension_urls
 
+namespace extension_filenames {
+  // The name of a temporary directory to install an extension into for
+  // validation before finalizing install.
+  extern const char* kTempExtensionName;
+
+  // The file to write our decoded images to, relative to the extension_path.
+  extern const char* kDecodedImagesFilename;
+
+  // The file to write our decoded message catalogs to, relative to the
+  // extension_path.
+  extern const char* kDecodedMessageCatalogsFilename;
+}
+
 #endif  // CHROME_COMMON_EXTENSIONS_EXTENSION_CONSTANTS_H_
diff --git a/chrome/common/extensions/extension_file_util.h b/chrome/common/extensions/extension_file_util.h
index b711df0..dc6a5bd0 100644
--- a/chrome/common/extensions/extension_file_util.h
+++ b/chrome/common/extensions/extension_file_util.h
@@ -25,7 +25,7 @@
 extern const char kCurrentVersionFileName[];
 
 // Move source_dir to dest_dir (it will actually be named dest_dir, not inside
-// dest_dir). If the parent path doesn't exixt, create it. If something else is
+// dest_dir). If the parent path doesn't exist, create it. If something else is
 // already there, remove it.
 bool MoveDirSafely(const FilePath& source_dir, const FilePath& dest_dir);
 
diff --git a/chrome/common/extensions/extension_unpacker.cc b/chrome/common/extensions/extension_unpacker.cc
index dad10f4..8c634e2 100644
--- a/chrome/common/extensions/extension_unpacker.cc
+++ b/chrome/common/extensions/extension_unpacker.cc
@@ -26,18 +26,9 @@
 
 namespace errors = extension_manifest_errors;
 namespace keys = extension_manifest_keys;
+namespace filenames = extension_filenames;
 
 namespace {
-// The name of a temporary directory to install an extension into for
-// validation before finalizing install.
-const char kTempExtensionName[] = "TEMP_INSTALL";
-
-// The file to write our decoded images to, relative to the extension_path.
-const char kDecodedImagesFilename[] = "DECODED_IMAGES";
-
-// The file to write our decoded message catalogs to, relative to the
-// extension_path.
-const char kDecodedMessageCatalogsFilename[] = "DECODED_MESSAGE_CATALOGS";
 
 // Errors
 const char* kCouldNotCreateDirectoryError =
@@ -148,7 +139,7 @@
 
   // <profile>/Extensions/INSTALL_TEMP/<version>
   temp_install_dir_ =
-      extension_path_.DirName().AppendASCII(kTempExtensionName);
+    extension_path_.DirName().AppendASCII(filenames::kTempExtensionName);
   if (!file_util::CreateDirectory(temp_install_dir_)) {
 #if defined(OS_WIN)
     std::string dir_string = WideToUTF8(temp_install_dir_.value());
@@ -209,7 +200,8 @@
   IPC::Message pickle;  // We use a Message so we can use WriteParam.
   IPC::WriteParam(&pickle, decoded_images_);
 
-  FilePath path = extension_path_.DirName().AppendASCII(kDecodedImagesFilename);
+  FilePath path = extension_path_.DirName().AppendASCII(
+      filenames::kDecodedImagesFilename);
   if (!file_util::WriteFile(path, static_cast<const char*>(pickle.data()),
                             pickle.size())) {
     SetError("Could not write image data to disk.");
@@ -224,7 +216,7 @@
   IPC::WriteParam(&pickle, *parsed_catalogs_.get());
 
   FilePath path = extension_path_.DirName().AppendASCII(
-      kDecodedMessageCatalogsFilename);
+      filenames::kDecodedMessageCatalogsFilename);
   if (!file_util::WriteFile(path, static_cast<const char*>(pickle.data()),
                             pickle.size())) {
     SetError("Could not write message catalogs to disk.");
@@ -237,7 +229,7 @@
 // static
 bool ExtensionUnpacker::ReadImagesFromFile(const FilePath& extension_path,
                                            DecodedImages* images) {
-  FilePath path = extension_path.AppendASCII(kDecodedImagesFilename);
+  FilePath path = extension_path.AppendASCII(filenames::kDecodedImagesFilename);
   std::string file_str;
   if (!file_util::ReadFileToString(path, &file_str))
     return false;
@@ -250,7 +242,8 @@
 // static
 bool ExtensionUnpacker::ReadMessageCatalogsFromFile(
     const FilePath& extension_path, DictionaryValue* catalogs) {
-  FilePath path = extension_path.AppendASCII(kDecodedMessageCatalogsFilename);
+  FilePath path = extension_path.AppendASCII(
+      filenames::kDecodedMessageCatalogsFilename);
   std::string file_str;
   if (!file_util::ReadFileToString(path, &file_str))
     return false;