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