1 // Copyright 2014 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 <vector>
6
7 #include "base/bind.h"
8 #include "base/callback_helpers.h"
9 #include "base/command_line.h"
10 #include "base/files/file_path.h"
11 #include "base/files/file_util.h"
12 #include "base/memory/ref_counted.h"
13 #include "base/path_service.h"
14 #include "base/run_loop.h"
15 #include "base/strings/string_util.h"
16 #include "base/threading/thread_task_runner_handle.h"
17 #include "base/values.h"
18 #include "build/build_config.h"
19 #include "chrome/browser/extensions/chrome_zipfile_installer.h"
20 #include "chrome/browser/extensions/extension_service.h"
21 #include "chrome/browser/extensions/load_error_reporter.h"
22 #include "chrome/browser/extensions/test_extension_system.h"
23 #include "chrome/common/chrome_paths.h"
24 #include "chrome/test/base/testing_profile.h"
25 #include "components/services/unzip/content/unzip_service.h"
26 #include "components/services/unzip/in_process_unzipper.h"
27 #include "content/public/test/browser_task_environment.h"
28 #include "content/public/test/test_utils.h"
29 #include "extensions/browser/extension_file_task_runner.h"
30 #include "extensions/browser/extension_registry.h"
31 #include "extensions/browser/extension_registry_observer.h"
32 #include "extensions/common/constants.h"
33 #include "extensions/common/extension.h"
34 #include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
35 #include "testing/gtest/include/gtest/gtest.h"
36
37 #if defined(OS_CHROMEOS)
38 #include "chrome/browser/chromeos/login/users/scoped_test_user_manager.h"
39 #include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
40 #endif
41
42 namespace extensions {
43
44 namespace {
45
46 struct MockExtensionRegistryObserver : public ExtensionRegistryObserver {
WaitForInstallextensions::__anonaba89ad70111::MockExtensionRegistryObserver47 void WaitForInstall(bool expect_error) {
48 extensions::LoadErrorReporter* error_reporter =
49 extensions::LoadErrorReporter::GetInstance();
50 error_reporter->ClearErrors();
51 while (true) {
52 base::RunLoop run_loop;
53 // We do not get a notification if installation fails. Make sure to wake
54 // up and check for errors to get an error better than the test
55 // timing-out.
56 // TODO(jcivelli): make LoadErrorReporter::Observer report installation
57 // failures for packaged extensions so we don't have to poll.
58 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
59 FROM_HERE, run_loop.QuitClosure(),
60 base::TimeDelta::FromMilliseconds(100));
61 quit_closure = run_loop.QuitClosure();
62 run_loop.Run();
63 const std::vector<base::string16>* errors = error_reporter->GetErrors();
64 if (!errors->empty()) {
65 if (!expect_error) {
66 FAIL() << "Error(s) happened when unzipping extension: "
67 << (*errors)[0];
68 }
69 break;
70 }
71 if (!last_extension_installed.empty()) {
72 // Extension install succeeded.
73 EXPECT_FALSE(expect_error);
74 break;
75 }
76 }
77 }
78
OnExtensionInstalledextensions::__anonaba89ad70111::MockExtensionRegistryObserver79 void OnExtensionInstalled(content::BrowserContext* browser_context,
80 const Extension* extension,
81 bool is_update) override {
82 last_extension_installed = extension->id();
83 quit_closure.Run();
84 }
85
86 std::string last_extension_installed;
87 base::Closure quit_closure;
88 };
89
90 struct UnzipFileFilterTestCase {
91 const base::FilePath::CharType* input;
92 const bool should_unzip;
93 };
94
95 } // namespace
96
97 class ZipFileInstallerTest : public testing::Test {
98 public:
ZipFileInstallerTest()99 ZipFileInstallerTest()
100 : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP) {}
101
SetUp()102 void SetUp() override {
103 extensions::LoadErrorReporter::Init(/*enable_noisy_errors=*/false);
104
105 in_process_utility_thread_helper_.reset(
106 new content::InProcessUtilityThreadHelper);
107 unzip::SetUnzipperLaunchOverrideForTesting(
108 base::BindRepeating(&unzip::LaunchInProcessUnzipper));
109
110 // Create profile for extension service.
111 profile_.reset(new TestingProfile());
112 TestExtensionSystem* system =
113 static_cast<TestExtensionSystem*>(ExtensionSystem::Get(profile_.get()));
114 extension_service_ = system->CreateExtensionService(
115 base::CommandLine::ForCurrentProcess(), base::FilePath(), false);
116 ExtensionRegistry* registry(ExtensionRegistry::Get(profile_.get()));
117 registry->AddObserver(&observer_);
118 }
119
TearDown()120 void TearDown() override {
121 // Need to destruct ZipFileInstaller before the message loop since
122 // it posts a task to it.
123 zipfile_installer_.reset();
124 ExtensionRegistry* registry(ExtensionRegistry::Get(profile_.get()));
125 registry->RemoveObserver(&observer_);
126 profile_.reset();
127 unzip::SetUnzipperLaunchOverrideForTesting(base::NullCallback());
128 base::RunLoop().RunUntilIdle();
129 }
130
RunInstaller(const std::string & zip_name,bool expect_error)131 void RunInstaller(const std::string& zip_name, bool expect_error) {
132 base::FilePath original_path;
133 ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &original_path));
134 original_path = original_path.AppendASCII("extensions")
135 .AppendASCII("zipfile_installer")
136 .AppendASCII(zip_name);
137 ASSERT_TRUE(base::PathExists(original_path)) << original_path.value();
138 zipfile_installer_ = ZipFileInstaller::Create(
139 GetExtensionFileTaskRunner(),
140 MakeRegisterInExtensionServiceCallback(extension_service_));
141
142 base::ThreadTaskRunnerHandle::Get()->PostTask(
143 FROM_HERE, base::BindOnce(&ZipFileInstaller::LoadFromZipFile,
144 zipfile_installer_, original_path));
145 observer_.WaitForInstall(expect_error);
146 }
147
RunZipFileFilterTest(const std::vector<UnzipFileFilterTestCase> & cases,base::RepeatingCallback<bool (const base::FilePath &)> & filter)148 void RunZipFileFilterTest(
149 const std::vector<UnzipFileFilterTestCase>& cases,
150 base::RepeatingCallback<bool(const base::FilePath&)>& filter) {
151 for (size_t i = 0; i < cases.size(); ++i) {
152 base::FilePath input(cases[i].input);
153 bool observed = filter.Run(input);
154 EXPECT_EQ(cases[i].should_unzip, observed)
155 << "i: " << i << ", input: " << input.value();
156 }
157 }
158
159 protected:
160 scoped_refptr<ZipFileInstaller> zipfile_installer_;
161
162 std::unique_ptr<TestingProfile> profile_;
163 ExtensionService* extension_service_;
164
165 content::BrowserTaskEnvironment task_environment_;
166 std::unique_ptr<content::InProcessUtilityThreadHelper>
167 in_process_utility_thread_helper_;
168 MockExtensionRegistryObserver observer_;
169
170 #if defined(OS_CHROMEOS)
171 chromeos::ScopedCrosSettingsTestHelper cros_settings_test_helper_;
172 // ChromeOS needs a user manager to instantiate an extension service.
173 chromeos::ScopedTestUserManager test_user_manager_;
174 #endif
175
176 private:
177 data_decoder::test::InProcessDataDecoder in_process_data_decoder_;
178 };
179
TEST_F(ZipFileInstallerTest,GoodZip)180 TEST_F(ZipFileInstallerTest, GoodZip) {
181 RunInstaller("good.zip", /*expect_error=*/false);
182 }
183
TEST_F(ZipFileInstallerTest,BadZip)184 TEST_F(ZipFileInstallerTest, BadZip) {
185 // Manifestless archive.
186 RunInstaller("bad.zip", /*expect_error=*/true);
187 }
188
TEST_F(ZipFileInstallerTest,ZipWithPublicKey)189 TEST_F(ZipFileInstallerTest, ZipWithPublicKey) {
190 RunInstaller("public_key.zip", /*expect_error=*/false);
191 const char kIdForPublicKey[] = "ikppjpenhoddphklkpdfdfdabbakkpal";
192 EXPECT_EQ(observer_.last_extension_installed, kIdForPublicKey);
193 }
194
TEST_F(ZipFileInstallerTest,NonTheme_FileExtractionFilter)195 TEST_F(ZipFileInstallerTest, NonTheme_FileExtractionFilter) {
196 const std::vector<UnzipFileFilterTestCase> cases = {
197 {FILE_PATH_LITERAL("foo"), true},
198 {FILE_PATH_LITERAL("foo.nexe"), true},
199 {FILE_PATH_LITERAL("foo.dll"), true},
200 {FILE_PATH_LITERAL("foo.jpg.exe"), false},
201 {FILE_PATH_LITERAL("foo.exe"), false},
202 {FILE_PATH_LITERAL("foo.EXE"), false},
203 {FILE_PATH_LITERAL("file_without_extension"), true},
204 };
205 base::RepeatingCallback<bool(const base::FilePath&)> filter =
206 base::BindRepeating(&ZipFileInstaller::ShouldExtractFile, false);
207 RunZipFileFilterTest(cases, filter);
208 }
209
TEST_F(ZipFileInstallerTest,Theme_FileExtractionFilter)210 TEST_F(ZipFileInstallerTest, Theme_FileExtractionFilter) {
211 const std::vector<UnzipFileFilterTestCase> cases = {
212 {FILE_PATH_LITERAL("image.jpg"), true},
213 {FILE_PATH_LITERAL("IMAGE.JPEG"), true},
214 {FILE_PATH_LITERAL("test/image.bmp"), true},
215 {FILE_PATH_LITERAL("test/IMAGE.gif"), true},
216 {FILE_PATH_LITERAL("test/image.WEBP"), true},
217 {FILE_PATH_LITERAL("test/dir/file.image.png"), true},
218 {FILE_PATH_LITERAL("manifest.json"), true},
219 {FILE_PATH_LITERAL("other.html"), false},
220 {FILE_PATH_LITERAL("file_without_extension"), true},
221 };
222 base::RepeatingCallback<bool(const base::FilePath&)> filter =
223 base::BindRepeating(&ZipFileInstaller::ShouldExtractFile, true);
224 RunZipFileFilterTest(cases, filter);
225 }
226
TEST_F(ZipFileInstallerTest,ManifestExtractionFilter)227 TEST_F(ZipFileInstallerTest, ManifestExtractionFilter) {
228 const std::vector<UnzipFileFilterTestCase> cases = {
229 {FILE_PATH_LITERAL("manifest.json"), true},
230 {FILE_PATH_LITERAL("MANIFEST.JSON"), true},
231 {FILE_PATH_LITERAL("test/manifest.json"), false},
232 {FILE_PATH_LITERAL("manifest.json/test"), false},
233 {FILE_PATH_LITERAL("other.file"), false},
234 };
235 base::RepeatingCallback<bool(const base::FilePath&)> filter =
236 base::BindRepeating(&ZipFileInstaller::IsManifestFile);
237 RunZipFileFilterTest(cases, filter);
238 }
239
240 } // namespace extensions
241