Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / common / extensions / extension_file_util_unittest.cc
blob5258847a121b7dbbd42d58e718da5586b33d27a0
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.
5 #include "chrome/common/extensions/extension_file_util.h"
7 #include "base/file_util.h"
8 #include "base/files/scoped_temp_dir.h"
9 #include "base/json/json_string_value_serializer.h"
10 #include "base/path_service.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/common/chrome_paths.h"
14 #include "extensions/common/constants.h"
15 #include "extensions/common/extension.h"
16 #include "extensions/common/manifest.h"
17 #include "extensions/common/manifest_constants.h"
18 #include "grit/generated_resources.h"
19 #include "testing/gmock/include/gmock/gmock.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21 #include "ui/base/l10n/l10n_util.h"
23 using extensions::Extension;
24 using extensions::Manifest;
26 namespace keys = extensions::manifest_keys;
28 class ExtensionFileUtilTest : public testing::Test {
31 TEST_F(ExtensionFileUtilTest, InstallUninstallGarbageCollect) {
32 base::ScopedTempDir temp;
33 ASSERT_TRUE(temp.CreateUniqueTempDir());
35 // Create a source extension.
36 std::string extension_id("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
37 std::string version("1.0");
38 base::FilePath src = temp.path().AppendASCII(extension_id);
39 ASSERT_TRUE(base::CreateDirectory(src));
41 // Create a extensions tree.
42 base::FilePath all_extensions = temp.path().AppendASCII("extensions");
43 ASSERT_TRUE(base::CreateDirectory(all_extensions));
45 // Install in empty directory. Should create parent directories as needed.
46 base::FilePath version_1 = extension_file_util::InstallExtension(
47 src,
48 extension_id,
49 version,
50 all_extensions);
51 ASSERT_EQ(version_1.value(),
52 all_extensions.AppendASCII(extension_id).AppendASCII("1.0_0")
53 .value());
54 ASSERT_TRUE(base::DirectoryExists(version_1));
56 // Should have moved the source.
57 ASSERT_FALSE(base::DirectoryExists(src));
59 // Install again. Should create a new one with different name.
60 ASSERT_TRUE(base::CreateDirectory(src));
61 base::FilePath version_2 = extension_file_util::InstallExtension(
62 src,
63 extension_id,
64 version,
65 all_extensions);
66 ASSERT_EQ(version_2.value(),
67 all_extensions.AppendASCII(extension_id).AppendASCII("1.0_1")
68 .value());
69 ASSERT_TRUE(base::DirectoryExists(version_2));
71 // Should have moved the source.
72 ASSERT_FALSE(base::DirectoryExists(src));
74 // Install yet again. Should create a new one with a different name.
75 ASSERT_TRUE(base::CreateDirectory(src));
76 base::FilePath version_3 = extension_file_util::InstallExtension(
77 src,
78 extension_id,
79 version,
80 all_extensions);
81 ASSERT_EQ(version_3.value(),
82 all_extensions.AppendASCII(extension_id).AppendASCII("1.0_2")
83 .value());
84 ASSERT_TRUE(base::DirectoryExists(version_3));
86 // Collect garbage. Should remove first one.
87 std::multimap<std::string, base::FilePath> extension_paths;
88 extension_paths.insert(std::make_pair(extension_id,
89 base::FilePath().AppendASCII(extension_id).Append(version_2.BaseName())));
90 extension_paths.insert(std::make_pair(extension_id,
91 base::FilePath().AppendASCII(extension_id).Append(version_3.BaseName())));
92 extension_file_util::GarbageCollectExtensions(all_extensions,
93 extension_paths,
94 true);
95 ASSERT_FALSE(base::DirectoryExists(version_1));
96 ASSERT_TRUE(base::DirectoryExists(version_2));
97 ASSERT_TRUE(base::DirectoryExists(version_3));
99 // Uninstall. Should remove entire extension subtree.
100 extension_file_util::UninstallExtension(all_extensions, extension_id);
101 ASSERT_FALSE(base::DirectoryExists(version_2.DirName()));
102 ASSERT_FALSE(base::DirectoryExists(version_3.DirName()));
103 ASSERT_TRUE(base::DirectoryExists(all_extensions));
106 TEST_F(ExtensionFileUtilTest, LoadExtensionWithValidLocales) {
107 base::FilePath install_dir;
108 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
109 install_dir = install_dir.AppendASCII("extensions")
110 .AppendASCII("good")
111 .AppendASCII("Extensions")
112 .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
113 .AppendASCII("1.0.0.0");
115 std::string error;
116 scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
117 install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
118 ASSERT_TRUE(extension.get() != NULL);
119 EXPECT_EQ("The first extension that I made.", extension->description());
122 TEST_F(ExtensionFileUtilTest, LoadExtensionWithoutLocalesFolder) {
123 base::FilePath install_dir;
124 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
125 install_dir = install_dir.AppendASCII("extensions")
126 .AppendASCII("good")
127 .AppendASCII("Extensions")
128 .AppendASCII("bjafgdebaacbbbecmhlhpofkepfkgcpa")
129 .AppendASCII("1.0");
131 std::string error;
132 scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
133 install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
134 ASSERT_FALSE(extension.get() == NULL);
135 EXPECT_TRUE(error.empty());
138 TEST_F(ExtensionFileUtilTest, CheckIllegalFilenamesNoUnderscores) {
139 base::ScopedTempDir temp;
140 ASSERT_TRUE(temp.CreateUniqueTempDir());
142 base::FilePath src_path = temp.path().AppendASCII("some_dir");
143 ASSERT_TRUE(base::CreateDirectory(src_path));
145 std::string data = "{ \"name\": { \"message\": \"foobar\" } }";
146 ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("some_file.txt"),
147 data.c_str(), data.length()));
148 std::string error;
149 EXPECT_TRUE(extension_file_util::CheckForIllegalFilenames(temp.path(),
150 &error));
153 TEST_F(ExtensionFileUtilTest, CheckIllegalFilenamesOnlyReserved) {
154 base::ScopedTempDir temp;
155 ASSERT_TRUE(temp.CreateUniqueTempDir());
157 const base::FilePath::CharType* folders[] =
158 { extensions::kLocaleFolder, extensions::kPlatformSpecificFolder };
160 for (size_t i = 0; i < arraysize(folders); i++) {
161 base::FilePath src_path = temp.path().Append(folders[i]);
162 ASSERT_TRUE(base::CreateDirectory(src_path));
165 std::string error;
166 EXPECT_TRUE(extension_file_util::CheckForIllegalFilenames(temp.path(),
167 &error));
170 TEST_F(ExtensionFileUtilTest, CheckIllegalFilenamesReservedAndIllegal) {
171 base::ScopedTempDir temp;
172 ASSERT_TRUE(temp.CreateUniqueTempDir());
174 base::FilePath src_path = temp.path().Append(extensions::kLocaleFolder);
175 ASSERT_TRUE(base::CreateDirectory(src_path));
177 src_path = temp.path().AppendASCII("_some_dir");
178 ASSERT_TRUE(base::CreateDirectory(src_path));
180 std::string error;
181 EXPECT_FALSE(extension_file_util::CheckForIllegalFilenames(temp.path(),
182 &error));
185 TEST_F(ExtensionFileUtilTest,
186 LoadExtensionGivesHelpfullErrorOnMissingManifest) {
187 base::FilePath install_dir;
188 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
189 install_dir = install_dir.AppendASCII("extensions")
190 .AppendASCII("bad")
191 .AppendASCII("Extensions")
192 .AppendASCII("dddddddddddddddddddddddddddddddd")
193 .AppendASCII("1.0");
195 std::string error;
196 scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
197 install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
198 ASSERT_TRUE(extension.get() == NULL);
199 ASSERT_FALSE(error.empty());
200 ASSERT_STREQ("Manifest file is missing or unreadable.", error.c_str());
203 TEST_F(ExtensionFileUtilTest, LoadExtensionGivesHelpfullErrorOnBadManifest) {
204 base::FilePath install_dir;
205 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
206 install_dir = install_dir.AppendASCII("extensions")
207 .AppendASCII("bad")
208 .AppendASCII("Extensions")
209 .AppendASCII("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee")
210 .AppendASCII("1.0");
212 std::string error;
213 scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
214 install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
215 ASSERT_TRUE(extension.get() == NULL);
216 ASSERT_FALSE(error.empty());
217 ASSERT_STREQ("Manifest is not valid JSON. "
218 "Line: 2, column: 16, Syntax error.", error.c_str());
221 TEST_F(ExtensionFileUtilTest, FailLoadingNonUTF8Scripts) {
222 base::FilePath install_dir;
223 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
224 install_dir = install_dir.AppendASCII("extensions")
225 .AppendASCII("bad")
226 .AppendASCII("bad_encoding");
228 std::string error;
229 scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
230 install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
231 ASSERT_TRUE(extension.get() == NULL);
232 ASSERT_STREQ("Could not load file 'bad_encoding.js' for content script. "
233 "It isn't UTF-8 encoded.",
234 error.c_str());
237 static scoped_refptr<Extension> LoadExtensionManifest(
238 base::DictionaryValue* manifest,
239 const base::FilePath& manifest_dir,
240 Manifest::Location location,
241 int extra_flags,
242 std::string* error) {
243 scoped_refptr<Extension> extension = Extension::Create(
244 manifest_dir, location, *manifest, extra_flags, error);
245 return extension;
248 static scoped_refptr<Extension> LoadExtensionManifest(
249 const std::string& manifest_value,
250 const base::FilePath& manifest_dir,
251 Manifest::Location location,
252 int extra_flags,
253 std::string* error) {
254 JSONStringValueSerializer serializer(manifest_value);
255 scoped_ptr<base::Value> result(serializer.Deserialize(NULL, error));
256 if (!result.get())
257 return NULL;
258 CHECK_EQ(base::Value::TYPE_DICTIONARY, result->GetType());
259 return LoadExtensionManifest(
260 static_cast<base::DictionaryValue*>(result.get()),
261 manifest_dir,
262 location,
263 extra_flags,
264 error);
267 TEST_F(ExtensionFileUtilTest, ValidateThemeUTF8) {
268 base::ScopedTempDir temp;
269 ASSERT_TRUE(temp.CreateUniqueTempDir());
271 // "aeo" with accents. Use http://0xcc.net/jsescape/ to decode them.
272 std::string non_ascii_file = "\xC3\xA0\xC3\xA8\xC3\xB2.png";
273 base::FilePath non_ascii_path =
274 temp.path().Append(base::FilePath::FromUTF8Unsafe(non_ascii_file));
275 file_util::WriteFile(non_ascii_path, "", 0);
277 std::string kManifest =
278 base::StringPrintf(
279 "{ \"name\": \"Test\", \"version\": \"1.0\", "
280 " \"theme\": { \"images\": { \"theme_frame\": \"%s\" } }"
281 "}", non_ascii_file.c_str());
282 std::string error;
283 scoped_refptr<Extension> extension = LoadExtensionManifest(
284 kManifest, temp.path(), Manifest::UNPACKED, 0, &error);
285 ASSERT_TRUE(extension.get()) << error;
287 std::vector<extensions::InstallWarning> warnings;
288 EXPECT_TRUE(extension_file_util::ValidateExtension(
289 extension.get(), &error, &warnings)) << error;
290 EXPECT_EQ(0U, warnings.size());
293 TEST_F(ExtensionFileUtilTest, BackgroundScriptsMustExist) {
294 base::ScopedTempDir temp;
295 ASSERT_TRUE(temp.CreateUniqueTempDir());
297 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
298 value->SetString("name", "test");
299 value->SetString("version", "1");
300 value->SetInteger("manifest_version", 1);
302 base::ListValue* scripts = new base::ListValue();
303 scripts->Append(new base::StringValue("foo.js"));
304 value->Set("background.scripts", scripts);
306 std::string error;
307 std::vector<extensions::InstallWarning> warnings;
308 scoped_refptr<Extension> extension = LoadExtensionManifest(
309 value.get(), temp.path(), Manifest::UNPACKED, 0, &error);
310 ASSERT_TRUE(extension.get()) << error;
312 EXPECT_FALSE(extension_file_util::ValidateExtension(
313 extension.get(), &error, &warnings));
314 EXPECT_EQ(l10n_util::GetStringFUTF8(
315 IDS_EXTENSION_LOAD_BACKGROUND_SCRIPT_FAILED,
316 base::ASCIIToUTF16("foo.js")),
317 error);
318 EXPECT_EQ(0U, warnings.size());
320 scripts->Clear();
321 scripts->Append(new base::StringValue("http://google.com/foo.js"));
323 extension = LoadExtensionManifest(value.get(), temp.path(),
324 Manifest::UNPACKED, 0, &error);
325 ASSERT_TRUE(extension.get()) << error;
327 warnings.clear();
328 EXPECT_FALSE(extension_file_util::ValidateExtension(
329 extension.get(), &error, &warnings));
330 EXPECT_EQ(l10n_util::GetStringFUTF8(
331 IDS_EXTENSION_LOAD_BACKGROUND_SCRIPT_FAILED,
332 base::ASCIIToUTF16("http://google.com/foo.js")),
333 error);
334 EXPECT_EQ(0U, warnings.size());
337 // Private key, generated by Chrome specifically for this test, and
338 // never used elsewhere.
339 const char private_key[] =
340 "-----BEGIN PRIVATE KEY-----\n"
341 "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKt02SR0FYaYy6fpW\n"
342 "MAA+kU1BgK3d+OmmWfdr+JATIjhRkyeSF4lTd/71JQsyKqPzYkQPi3EeROWM+goTv\n"
343 "EhJqq07q63BolpsFmlV+S4ny+sBA2B4aWwRYXlBWikdrQSA0mJMzvEHc6nKzBgXik\n"
344 "QSVbyyBNAsxlDB9WaCxRVOpK3AgMBAAECgYBGvSPlrVtAOAQ2V8j9FqorKZA8SLPX\n"
345 "IeJC/yzU3RB2nPMjI17aMOvrUHxJUhzMeh4jwabVvSzzDtKFozPGupW3xaI8sQdi2\n"
346 "WWMTQIk/Q9HHDWoQ9qA6SwX2qWCc5SyjCKqVp78ye+000kqTJYjBsDgXeAlzKcx2B\n"
347 "4GAAeWonDdkQJBANNb8wrqNWFn7DqyQTfELzcRTRnqQ/r1pdeJo6obzbnwGnlqe3t\n"
348 "KhLjtJNIGrQg5iC0OVLWFuvPJs0t3z62A1ckCQQDPq2JZuwTwu5Pl4DJ0r9O1FdqN\n"
349 "JgqPZyMptokCDQ3khLLGakIu+TqB9YtrzI69rJMSG2Egb+6McaDX+dh3XmR/AkB9t\n"
350 "xJf6qDnmA2td/tMtTc0NOk8Qdg/fD8xbZ/YfYMnVoYYs9pQoilBaWRePDRNURMLYZ\n"
351 "vHAI0Llmw7tj7jv17pAkEAz44uXRpjRKtllUIvi5pUENAHwDz+HvdpGH68jpU3hmb\n"
352 "uOwrmnQYxaMReFV68Z2w9DcLZn07f7/R9Wn72z89CxwJAFsDoNaDes4h48bX7plct\n"
353 "s9ACjmTwcCigZjN2K7AGv7ntCLF3DnV5dK0dTHNaAdD3SbY3jl29Rk2CwiURSX6Ee\n"
354 "g==\n"
355 "-----END PRIVATE KEY-----\n";
357 TEST_F(ExtensionFileUtilTest, FindPrivateKeyFiles) {
358 base::ScopedTempDir temp;
359 ASSERT_TRUE(temp.CreateUniqueTempDir());
361 base::FilePath src_path = temp.path().AppendASCII("some_dir");
362 ASSERT_TRUE(base::CreateDirectory(src_path));
364 ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("a_key.pem"),
365 private_key, arraysize(private_key)));
366 ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("second_key.pem"),
367 private_key, arraysize(private_key)));
368 // Shouldn't find a key with a different extension.
369 ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("key.diff_ext"),
370 private_key, arraysize(private_key)));
371 // Shouldn't find a key that isn't parsable.
372 ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("unparsable_key.pem"),
373 private_key, arraysize(private_key) - 30));
374 std::vector<base::FilePath> private_keys =
375 extension_file_util::FindPrivateKeyFiles(temp.path());
376 EXPECT_EQ(2U, private_keys.size());
377 EXPECT_THAT(private_keys,
378 testing::Contains(src_path.AppendASCII("a_key.pem")));
379 EXPECT_THAT(private_keys,
380 testing::Contains(src_path.AppendASCII("second_key.pem")));
383 TEST_F(ExtensionFileUtilTest, WarnOnPrivateKey) {
384 base::ScopedTempDir temp;
385 ASSERT_TRUE(temp.CreateUniqueTempDir());
387 base::FilePath ext_path = temp.path().AppendASCII("ext_root");
388 ASSERT_TRUE(base::CreateDirectory(ext_path));
390 const char manifest[] =
391 "{\n"
392 " \"name\": \"Test Extension\",\n"
393 " \"version\": \"1.0\",\n"
394 " \"manifest_version\": 2,\n"
395 " \"description\": \"The first extension that I made.\"\n"
396 "}\n";
397 ASSERT_TRUE(file_util::WriteFile(ext_path.AppendASCII("manifest.json"),
398 manifest, strlen(manifest)));
399 ASSERT_TRUE(file_util::WriteFile(ext_path.AppendASCII("a_key.pem"),
400 private_key, strlen(private_key)));
402 std::string error;
403 scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
404 ext_path, "the_id", Manifest::EXTERNAL_PREF,
405 Extension::NO_FLAGS, &error));
406 ASSERT_TRUE(extension.get()) << error;
407 ASSERT_EQ(1u, extension->install_warnings().size());
408 EXPECT_THAT(
409 extension->install_warnings(),
410 testing::ElementsAre(
411 testing::Field(
412 &extensions::InstallWarning::message,
413 testing::ContainsRegex(
414 "extension includes the key file.*ext_root.a_key.pem"))));
416 // Turn the warning into an error with ERROR_ON_PRIVATE_KEY.
417 extension = extension_file_util::LoadExtension(
418 ext_path, "the_id", Manifest::EXTERNAL_PREF,
419 Extension::ERROR_ON_PRIVATE_KEY, &error);
420 EXPECT_FALSE(extension.get());
421 EXPECT_THAT(error,
422 testing::ContainsRegex(
423 "extension includes the key file.*ext_root.a_key.pem"));
426 TEST_F(ExtensionFileUtilTest, CheckZeroLengthImageFile) {
427 base::FilePath install_dir;
428 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
430 // Try to install an extension with a zero-length icon file.
431 base::FilePath ext_dir = install_dir.AppendASCII("extensions")
432 .AppendASCII("bad")
433 .AppendASCII("Extensions")
434 .AppendASCII("ffffffffffffffffffffffffffffffff");
436 std::string error;
437 scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
438 ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
439 EXPECT_TRUE(extension.get() == NULL);
440 EXPECT_STREQ("Could not load extension icon 'icon.png'.", error.c_str());
442 // Try to install an extension with a zero-length browser action icon file.
443 ext_dir = install_dir.AppendASCII("extensions")
444 .AppendASCII("bad")
445 .AppendASCII("Extensions")
446 .AppendASCII("gggggggggggggggggggggggggggggggg");
448 scoped_refptr<Extension> extension2(extension_file_util::LoadExtension(
449 ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
450 EXPECT_TRUE(extension2.get() == NULL);
451 EXPECT_STREQ("Could not load icon 'icon.png' for browser action.",
452 error.c_str());
454 // Try to install an extension with a zero-length page action icon file.
455 ext_dir = install_dir.AppendASCII("extensions")
456 .AppendASCII("bad")
457 .AppendASCII("Extensions")
458 .AppendASCII("hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh");
460 scoped_refptr<Extension> extension3(extension_file_util::LoadExtension(
461 ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
462 EXPECT_TRUE(extension3.get() == NULL);
463 EXPECT_STREQ("Could not load icon 'icon.png' for page action.",
464 error.c_str());
467 // TODO(aa): More tests as motivation allows. Maybe steal some from
468 // ExtensionService? Many of them could probably be tested here without the
469 // MessageLoop shenanigans.