1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "base/file_util.h" 6 #include "base/memory/ref_counted.h" 7 #include "base/memory/scoped_temp_dir.h" 8 #include "base/path_service.h" 9 #include "base/string_util.h" 10 #include "base/memory/scoped_temp_dir.h" 11 #include "chrome/browser/extensions/sandboxed_extension_unpacker.h" 12 #include "chrome/common/chrome_paths.h" 13 #include "chrome/common/extensions/extension.h" 14 #include "chrome/common/extensions/extension_constants.h" 15 #include "chrome/common/extensions/extension_unpacker.h" 16 #include "testing/gmock/include/gmock/gmock.h" 17 #include "testing/gtest/include/gtest/gtest.h" 18 #include "third_party/skia/include/core/SkBitmap.h" 19 20 namespace errors = extension_manifest_errors; 21 namespace keys = extension_manifest_keys; 22 23 using testing::_; 24 using testing::Invoke; 25 26 namespace { 27 28 void OnUnpackSuccess(const FilePath& temp_dir, 29 const FilePath& extension_root, 30 const Extension* extension) { 31 // Don't delete temp_dir here, we need to do some post op checking. 32 } 33 34 } // namespace 35 36 class MockSandboxedExtensionUnpackerClient 37 : public SandboxedExtensionUnpackerClient { 38 public: 39 virtual ~MockSandboxedExtensionUnpackerClient() {} 40 41 MOCK_METHOD3(OnUnpackSuccess, 42 void(const FilePath& temp_dir, 43 const FilePath& extension_root, 44 const Extension* extension)); 45 46 MOCK_METHOD1(OnUnpackFailure, 47 void(const std::string& error)); 48 49 void DelegateToFake() { 50 ON_CALL(*this, OnUnpackSuccess(_, _, _)) 51 .WillByDefault(Invoke(::OnUnpackSuccess)); 52 } 53 }; 54 55 class SandboxedExtensionUnpackerTest : public testing::Test { 56 public: 57 virtual void SetUp() { 58 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 59 file_thread_.reset(new BrowserThread(BrowserThread::FILE, &loop_)); 60 // It will delete itself. 61 client_ = new MockSandboxedExtensionUnpackerClient; 62 client_->DelegateToFake(); 63 } 64 65 virtual void TearDown() { 66 // Need to destruct SandboxedExtensionUnpacker before the message loop since 67 // it posts a task to it. 68 sandboxed_unpacker_ = NULL; 69 loop_.RunAllPending(); 70 } 71 72 void SetupUnpacker(const std::string& crx_name) { 73 FilePath original_path; 74 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &original_path)); 75 original_path = original_path.AppendASCII("extensions") 76 .AppendASCII("unpacker") 77 .AppendASCII(crx_name); 78 ASSERT_TRUE(file_util::PathExists(original_path)) << original_path.value(); 79 80 // Try bots won't let us write into DIR_TEST_DATA, so we have to write the 81 // CRX to the temp directory, and create a subdirectory into which to 82 // unpack it. 83 FilePath crx_path = temp_dir_.path().AppendASCII(crx_name); 84 ASSERT_TRUE(file_util::CopyFile(original_path, crx_path)) << 85 "Original path: " << original_path.value() << 86 ", Crx path: " << crx_path.value(); 87 88 unpacker_.reset(new ExtensionUnpacker(crx_path)); 89 90 // Build a temp area where the extension will be unpacked. 91 temp_path_ = 92 temp_dir_.path().AppendASCII("sandboxed_extension_unpacker_test_Temp"); 93 ASSERT_TRUE(file_util::CreateDirectory(temp_path_)); 94 95 sandboxed_unpacker_ = 96 new SandboxedExtensionUnpacker(crx_path, NULL, client_); 97 98 // Hack since SandboxedExtensionUnpacker gets its background thread id from 99 // the Start call, but we don't call it here. 100 sandboxed_unpacker_->thread_identifier_ = BrowserThread::FILE; 101 EXPECT_TRUE(PrepareUnpackerEnv()); 102 } 103 104 bool PrepareUnpackerEnv() { 105 sandboxed_unpacker_->extension_root_ = 106 temp_dir_.path().AppendASCII(extension_filenames::kTempExtensionName); 107 108 if (!sandboxed_unpacker_->temp_dir_.Set(temp_dir_.path())) 109 return false; 110 sandboxed_unpacker_->public_key_ = 111 "ocnapchkplbmjmpfehjocmjnipfmogkh"; 112 return true; 113 } 114 115 void OnUnpackSucceeded() { 116 sandboxed_unpacker_->OnUnpackExtensionSucceeded( 117 *unpacker_->parsed_manifest()); 118 } 119 120 FilePath GetInstallPath() { 121 return temp_dir_.path().AppendASCII( 122 extension_filenames::kTempExtensionName); 123 } 124 125 bool TempFilesRemoved() { 126 // Check that temporary files were cleaned up. 127 file_util::FileEnumerator::FILE_TYPE files_and_dirs = 128 static_cast<file_util::FileEnumerator::FILE_TYPE>( 129 file_util::FileEnumerator::DIRECTORIES | 130 file_util::FileEnumerator::FILES); 131 132 file_util::FileEnumerator temp_iterator( 133 temp_path_, 134 true, // recursive 135 files_and_dirs 136 ); 137 int items_not_removed = 0; 138 FilePath item_in_temp; 139 item_in_temp = temp_iterator.Next(); 140 while (!item_in_temp.value().empty()) { 141 items_not_removed++; 142 EXPECT_STREQ(FILE_PATH_LITERAL(""), item_in_temp.value().c_str()) 143 << "File was not removed on success."; 144 item_in_temp = temp_iterator.Next(); 145 } 146 return (items_not_removed == 0); 147 } 148 149 protected: 150 ScopedTempDir temp_dir_; 151 FilePath temp_path_; 152 MockSandboxedExtensionUnpackerClient* client_; 153 scoped_ptr<ExtensionUnpacker> unpacker_; 154 scoped_refptr<SandboxedExtensionUnpacker> sandboxed_unpacker_; 155 MessageLoop loop_; 156 scoped_ptr<BrowserThread> file_thread_; 157 }; 158 159 TEST_F(SandboxedExtensionUnpackerTest, NoCatalogsSuccess) { 160 EXPECT_CALL(*client_, OnUnpackSuccess(_, _, _)); 161 EXPECT_CALL(*client_, OnUnpackFailure(_)).Times(0); 162 163 SetupUnpacker("no_l10n.crx"); 164 ASSERT_TRUE(unpacker_->Run()); 165 ASSERT_TRUE(unpacker_->DumpImagesToFile()); 166 ASSERT_TRUE(unpacker_->DumpMessageCatalogsToFile()); 167 168 // Check that there is no _locales folder. 169 FilePath install_path = 170 GetInstallPath().Append(Extension::kLocaleFolder); 171 EXPECT_FALSE(file_util::PathExists(install_path)); 172 173 OnUnpackSucceeded(); 174 175 // Check that there still is no _locales folder. 176 EXPECT_FALSE(file_util::PathExists(install_path)); 177 178 ASSERT_TRUE(TempFilesRemoved()); 179 } 180 181 TEST_F(SandboxedExtensionUnpackerTest, WithCatalogsSuccess) { 182 EXPECT_CALL(*client_, OnUnpackSuccess(_, _, _)); 183 EXPECT_CALL(*client_, OnUnpackFailure(_)).Times(0); 184 185 SetupUnpacker("good_l10n.crx"); 186 ASSERT_TRUE(unpacker_->Run()); 187 ASSERT_TRUE(unpacker_->DumpImagesToFile()); 188 ASSERT_TRUE(unpacker_->DumpMessageCatalogsToFile()); 189 190 // Set timestamp on _locales/en_US/messages.json into the past. 191 FilePath messages_file; 192 messages_file = GetInstallPath().Append(Extension::kLocaleFolder) 193 .AppendASCII("en_US") 194 .Append(Extension::kMessagesFilename); 195 base::PlatformFileInfo old_info; 196 EXPECT_TRUE(file_util::GetFileInfo(messages_file, &old_info)); 197 base::Time old_time = 198 old_info.last_modified - base::TimeDelta::FromSeconds(2); 199 EXPECT_TRUE(file_util::SetLastModifiedTime(messages_file, old_time)); 200 // Refresh old_info, just to be sure. 201 EXPECT_TRUE(file_util::GetFileInfo(messages_file, &old_info)); 202 203 OnUnpackSucceeded(); 204 205 // Check that there is newer _locales/en_US/messages.json file. 206 base::PlatformFileInfo new_info; 207 EXPECT_TRUE(file_util::GetFileInfo(messages_file, &new_info)); 208 209 EXPECT_TRUE(new_info.last_modified > old_info.last_modified); 210 211 ASSERT_TRUE(TempFilesRemoved()); 212 } 213