Home | History | Annotate | Download | only in extensions
      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 "chrome/common/extensions/extension_file_util.h"
      6 
      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"
     22 
     23 using extensions::Extension;
     24 using extensions::Manifest;
     25 
     26 namespace keys = extensions::manifest_keys;
     27 
     28 class ExtensionFileUtilTest : public testing::Test {
     29 };
     30 
     31 TEST_F(ExtensionFileUtilTest, InstallUninstallGarbageCollect) {
     32   base::ScopedTempDir temp;
     33   ASSERT_TRUE(temp.CreateUniqueTempDir());
     34 
     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));
     40 
     41   // Create a extensions tree.
     42   base::FilePath all_extensions = temp.path().AppendASCII("extensions");
     43   ASSERT_TRUE(base::CreateDirectory(all_extensions));
     44 
     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));
     55 
     56   // Should have moved the source.
     57   ASSERT_FALSE(base::DirectoryExists(src));
     58 
     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));
     70 
     71   // Should have moved the source.
     72   ASSERT_FALSE(base::DirectoryExists(src));
     73 
     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));
     85 
     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   ASSERT_FALSE(base::DirectoryExists(version_1));
     95   ASSERT_TRUE(base::DirectoryExists(version_2));
     96   ASSERT_TRUE(base::DirectoryExists(version_3));
     97 
     98   // Uninstall. Should remove entire extension subtree.
     99   extension_file_util::UninstallExtension(all_extensions, extension_id);
    100   ASSERT_FALSE(base::DirectoryExists(version_2.DirName()));
    101   ASSERT_FALSE(base::DirectoryExists(version_3.DirName()));
    102   ASSERT_TRUE(base::DirectoryExists(all_extensions));
    103 }
    104 
    105 TEST_F(ExtensionFileUtilTest, LoadExtensionWithValidLocales) {
    106   base::FilePath install_dir;
    107   ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
    108   install_dir = install_dir.AppendASCII("extensions")
    109       .AppendASCII("good")
    110       .AppendASCII("Extensions")
    111       .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
    112       .AppendASCII("1.0.0.0");
    113 
    114   std::string error;
    115   scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
    116       install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
    117   ASSERT_TRUE(extension.get() != NULL);
    118   EXPECT_EQ("The first extension that I made.", extension->description());
    119 }
    120 
    121 TEST_F(ExtensionFileUtilTest, LoadExtensionWithoutLocalesFolder) {
    122   base::FilePath install_dir;
    123   ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
    124   install_dir = install_dir.AppendASCII("extensions")
    125       .AppendASCII("good")
    126       .AppendASCII("Extensions")
    127       .AppendASCII("bjafgdebaacbbbecmhlhpofkepfkgcpa")
    128       .AppendASCII("1.0");
    129 
    130   std::string error;
    131   scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
    132       install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
    133   ASSERT_FALSE(extension.get() == NULL);
    134   EXPECT_TRUE(error.empty());
    135 }
    136 
    137 TEST_F(ExtensionFileUtilTest, CheckIllegalFilenamesNoUnderscores) {
    138   base::ScopedTempDir temp;
    139   ASSERT_TRUE(temp.CreateUniqueTempDir());
    140 
    141   base::FilePath src_path = temp.path().AppendASCII("some_dir");
    142   ASSERT_TRUE(base::CreateDirectory(src_path));
    143 
    144   std::string data = "{ \"name\": { \"message\": \"foobar\" } }";
    145   ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("some_file.txt"),
    146                                    data.c_str(), data.length()));
    147   std::string error;
    148   EXPECT_TRUE(extension_file_util::CheckForIllegalFilenames(temp.path(),
    149                                                             &error));
    150 }
    151 
    152 TEST_F(ExtensionFileUtilTest, CheckIllegalFilenamesOnlyReserved) {
    153   base::ScopedTempDir temp;
    154   ASSERT_TRUE(temp.CreateUniqueTempDir());
    155 
    156   const base::FilePath::CharType* folders[] =
    157       { extensions::kLocaleFolder, extensions::kPlatformSpecificFolder };
    158 
    159   for (size_t i = 0; i < arraysize(folders); i++) {
    160     base::FilePath src_path = temp.path().Append(folders[i]);
    161     ASSERT_TRUE(base::CreateDirectory(src_path));
    162   }
    163 
    164   std::string error;
    165   EXPECT_TRUE(extension_file_util::CheckForIllegalFilenames(temp.path(),
    166                                                             &error));
    167 }
    168 
    169 TEST_F(ExtensionFileUtilTest, CheckIllegalFilenamesReservedAndIllegal) {
    170   base::ScopedTempDir temp;
    171   ASSERT_TRUE(temp.CreateUniqueTempDir());
    172 
    173   base::FilePath src_path = temp.path().Append(extensions::kLocaleFolder);
    174   ASSERT_TRUE(base::CreateDirectory(src_path));
    175 
    176   src_path = temp.path().AppendASCII("_some_dir");
    177   ASSERT_TRUE(base::CreateDirectory(src_path));
    178 
    179   std::string error;
    180   EXPECT_FALSE(extension_file_util::CheckForIllegalFilenames(temp.path(),
    181                                                              &error));
    182 }
    183 
    184 TEST_F(ExtensionFileUtilTest,
    185        LoadExtensionGivesHelpfullErrorOnMissingManifest) {
    186   base::FilePath install_dir;
    187   ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
    188   install_dir = install_dir.AppendASCII("extensions")
    189       .AppendASCII("bad")
    190       .AppendASCII("Extensions")
    191       .AppendASCII("dddddddddddddddddddddddddddddddd")
    192       .AppendASCII("1.0");
    193 
    194   std::string error;
    195   scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
    196       install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
    197   ASSERT_TRUE(extension.get() == NULL);
    198   ASSERT_FALSE(error.empty());
    199   ASSERT_STREQ("Manifest file is missing or unreadable.", error.c_str());
    200 }
    201 
    202 TEST_F(ExtensionFileUtilTest, LoadExtensionGivesHelpfullErrorOnBadManifest) {
    203   base::FilePath install_dir;
    204   ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
    205   install_dir = install_dir.AppendASCII("extensions")
    206       .AppendASCII("bad")
    207       .AppendASCII("Extensions")
    208       .AppendASCII("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee")
    209       .AppendASCII("1.0");
    210 
    211   std::string error;
    212   scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
    213       install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
    214   ASSERT_TRUE(extension.get() == NULL);
    215   ASSERT_FALSE(error.empty());
    216   ASSERT_STREQ("Manifest is not valid JSON.  "
    217                "Line: 2, column: 16, Syntax error.", error.c_str());
    218 }
    219 
    220 TEST_F(ExtensionFileUtilTest, FailLoadingNonUTF8Scripts) {
    221   base::FilePath install_dir;
    222   ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
    223   install_dir = install_dir.AppendASCII("extensions")
    224       .AppendASCII("bad")
    225       .AppendASCII("bad_encoding");
    226 
    227   std::string error;
    228   scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
    229       install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
    230   ASSERT_TRUE(extension.get() == NULL);
    231   ASSERT_STREQ("Could not load file 'bad_encoding.js' for content script. "
    232                "It isn't UTF-8 encoded.",
    233                error.c_str());
    234 }
    235 
    236 static scoped_refptr<Extension> LoadExtensionManifest(
    237     base::DictionaryValue* manifest,
    238     const base::FilePath& manifest_dir,
    239     Manifest::Location location,
    240     int extra_flags,
    241     std::string* error) {
    242   scoped_refptr<Extension> extension = Extension::Create(
    243       manifest_dir, location, *manifest, extra_flags, error);
    244   return extension;
    245 }
    246 
    247 static scoped_refptr<Extension> LoadExtensionManifest(
    248     const std::string& manifest_value,
    249     const base::FilePath& manifest_dir,
    250     Manifest::Location location,
    251     int extra_flags,
    252     std::string* error) {
    253   JSONStringValueSerializer serializer(manifest_value);
    254   scoped_ptr<base::Value> result(serializer.Deserialize(NULL, error));
    255   if (!result.get())
    256     return NULL;
    257   CHECK_EQ(base::Value::TYPE_DICTIONARY, result->GetType());
    258   return LoadExtensionManifest(
    259       static_cast<base::DictionaryValue*>(result.get()),
    260       manifest_dir,
    261       location,
    262       extra_flags,
    263       error);
    264 }
    265 
    266 TEST_F(ExtensionFileUtilTest, ValidateThemeUTF8) {
    267   base::ScopedTempDir temp;
    268   ASSERT_TRUE(temp.CreateUniqueTempDir());
    269 
    270   // "aeo" with accents. Use http://0xcc.net/jsescape/ to decode them.
    271   std::string non_ascii_file = "\xC3\xA0\xC3\xA8\xC3\xB2.png";
    272   base::FilePath non_ascii_path =
    273       temp.path().Append(base::FilePath::FromUTF8Unsafe(non_ascii_file));
    274   file_util::WriteFile(non_ascii_path, "", 0);
    275 
    276   std::string kManifest =
    277       base::StringPrintf(
    278           "{ \"name\": \"Test\", \"version\": \"1.0\", "
    279           "  \"theme\": { \"images\": { \"theme_frame\": \"%s\" } }"
    280           "}", non_ascii_file.c_str());
    281   std::string error;
    282   scoped_refptr<Extension> extension = LoadExtensionManifest(
    283       kManifest, temp.path(), Manifest::UNPACKED, 0, &error);
    284   ASSERT_TRUE(extension.get()) << error;
    285 
    286   std::vector<extensions::InstallWarning> warnings;
    287   EXPECT_TRUE(extension_file_util::ValidateExtension(
    288       extension.get(), &error, &warnings)) << error;
    289   EXPECT_EQ(0U, warnings.size());
    290 }
    291 
    292 TEST_F(ExtensionFileUtilTest, BackgroundScriptsMustExist) {
    293   base::ScopedTempDir temp;
    294   ASSERT_TRUE(temp.CreateUniqueTempDir());
    295 
    296   scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
    297   value->SetString("name", "test");
    298   value->SetString("version", "1");
    299   value->SetInteger("manifest_version", 1);
    300 
    301   base::ListValue* scripts = new base::ListValue();
    302   scripts->Append(new base::StringValue("foo.js"));
    303   value->Set("background.scripts", scripts);
    304 
    305   std::string error;
    306   std::vector<extensions::InstallWarning> warnings;
    307   scoped_refptr<Extension> extension = LoadExtensionManifest(
    308       value.get(), temp.path(), Manifest::UNPACKED, 0, &error);
    309   ASSERT_TRUE(extension.get()) << error;
    310 
    311   EXPECT_FALSE(extension_file_util::ValidateExtension(
    312       extension.get(), &error, &warnings));
    313   EXPECT_EQ(l10n_util::GetStringFUTF8(
    314       IDS_EXTENSION_LOAD_BACKGROUND_SCRIPT_FAILED, ASCIIToUTF16("foo.js")),
    315            error);
    316   EXPECT_EQ(0U, warnings.size());
    317 
    318   scripts->Clear();
    319   scripts->Append(new base::StringValue("http://google.com/foo.js"));
    320 
    321   extension = LoadExtensionManifest(value.get(), temp.path(),
    322                                     Manifest::UNPACKED, 0, &error);
    323   ASSERT_TRUE(extension.get()) << error;
    324 
    325   warnings.clear();
    326   EXPECT_FALSE(extension_file_util::ValidateExtension(
    327       extension.get(), &error, &warnings));
    328   EXPECT_EQ(l10n_util::GetStringFUTF8(
    329       IDS_EXTENSION_LOAD_BACKGROUND_SCRIPT_FAILED,
    330       ASCIIToUTF16("http://google.com/foo.js")),
    331            error);
    332   EXPECT_EQ(0U, warnings.size());
    333 }
    334 
    335 // Private key, generated by Chrome specifically for this test, and
    336 // never used elsewhere.
    337 const char private_key[] =
    338     "-----BEGIN PRIVATE KEY-----\n"
    339     "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKt02SR0FYaYy6fpW\n"
    340     "MAA+kU1BgK3d+OmmWfdr+JATIjhRkyeSF4lTd/71JQsyKqPzYkQPi3EeROWM+goTv\n"
    341     "EhJqq07q63BolpsFmlV+S4ny+sBA2B4aWwRYXlBWikdrQSA0mJMzvEHc6nKzBgXik\n"
    342     "QSVbyyBNAsxlDB9WaCxRVOpK3AgMBAAECgYBGvSPlrVtAOAQ2V8j9FqorKZA8SLPX\n"
    343     "IeJC/yzU3RB2nPMjI17aMOvrUHxJUhzMeh4jwabVvSzzDtKFozPGupW3xaI8sQdi2\n"
    344     "WWMTQIk/Q9HHDWoQ9qA6SwX2qWCc5SyjCKqVp78ye+000kqTJYjBsDgXeAlzKcx2B\n"
    345     "4GAAeWonDdkQJBANNb8wrqNWFn7DqyQTfELzcRTRnqQ/r1pdeJo6obzbnwGnlqe3t\n"
    346     "KhLjtJNIGrQg5iC0OVLWFuvPJs0t3z62A1ckCQQDPq2JZuwTwu5Pl4DJ0r9O1FdqN\n"
    347     "JgqPZyMptokCDQ3khLLGakIu+TqB9YtrzI69rJMSG2Egb+6McaDX+dh3XmR/AkB9t\n"
    348     "xJf6qDnmA2td/tMtTc0NOk8Qdg/fD8xbZ/YfYMnVoYYs9pQoilBaWRePDRNURMLYZ\n"
    349     "vHAI0Llmw7tj7jv17pAkEAz44uXRpjRKtllUIvi5pUENAHwDz+HvdpGH68jpU3hmb\n"
    350     "uOwrmnQYxaMReFV68Z2w9DcLZn07f7/R9Wn72z89CxwJAFsDoNaDes4h48bX7plct\n"
    351     "s9ACjmTwcCigZjN2K7AGv7ntCLF3DnV5dK0dTHNaAdD3SbY3jl29Rk2CwiURSX6Ee\n"
    352     "g==\n"
    353     "-----END PRIVATE KEY-----\n";
    354 
    355 TEST_F(ExtensionFileUtilTest, FindPrivateKeyFiles) {
    356   base::ScopedTempDir temp;
    357   ASSERT_TRUE(temp.CreateUniqueTempDir());
    358 
    359   base::FilePath src_path = temp.path().AppendASCII("some_dir");
    360   ASSERT_TRUE(base::CreateDirectory(src_path));
    361 
    362   ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("a_key.pem"),
    363                                    private_key, arraysize(private_key)));
    364   ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("second_key.pem"),
    365                                    private_key, arraysize(private_key)));
    366   // Shouldn't find a key with a different extension.
    367   ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("key.diff_ext"),
    368                                    private_key, arraysize(private_key)));
    369   // Shouldn't find a key that isn't parsable.
    370   ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("unparsable_key.pem"),
    371                                    private_key, arraysize(private_key) - 30));
    372   std::vector<base::FilePath> private_keys =
    373       extension_file_util::FindPrivateKeyFiles(temp.path());
    374   EXPECT_EQ(2U, private_keys.size());
    375   EXPECT_THAT(private_keys,
    376               testing::Contains(src_path.AppendASCII("a_key.pem")));
    377   EXPECT_THAT(private_keys,
    378               testing::Contains(src_path.AppendASCII("second_key.pem")));
    379 }
    380 
    381 TEST_F(ExtensionFileUtilTest, WarnOnPrivateKey) {
    382   base::ScopedTempDir temp;
    383   ASSERT_TRUE(temp.CreateUniqueTempDir());
    384 
    385   base::FilePath ext_path = temp.path().AppendASCII("ext_root");
    386   ASSERT_TRUE(base::CreateDirectory(ext_path));
    387 
    388   const char manifest[] =
    389       "{\n"
    390       "  \"name\": \"Test Extension\",\n"
    391       "  \"version\": \"1.0\",\n"
    392       "  \"manifest_version\": 2,\n"
    393       "  \"description\": \"The first extension that I made.\"\n"
    394       "}\n";
    395   ASSERT_TRUE(file_util::WriteFile(ext_path.AppendASCII("manifest.json"),
    396                                    manifest, strlen(manifest)));
    397   ASSERT_TRUE(file_util::WriteFile(ext_path.AppendASCII("a_key.pem"),
    398                                    private_key, strlen(private_key)));
    399 
    400   std::string error;
    401   scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
    402       ext_path, "the_id", Manifest::EXTERNAL_PREF,
    403       Extension::NO_FLAGS, &error));
    404   ASSERT_TRUE(extension.get()) << error;
    405   ASSERT_EQ(1u, extension->install_warnings().size());
    406   EXPECT_THAT(
    407       extension->install_warnings(),
    408       testing::ElementsAre(
    409           testing::Field(
    410               &extensions::InstallWarning::message,
    411               testing::ContainsRegex(
    412                   "extension includes the key file.*ext_root.a_key.pem"))));
    413 
    414   // Turn the warning into an error with ERROR_ON_PRIVATE_KEY.
    415   extension = extension_file_util::LoadExtension(
    416       ext_path, "the_id", Manifest::EXTERNAL_PREF,
    417       Extension::ERROR_ON_PRIVATE_KEY, &error);
    418   EXPECT_FALSE(extension.get());
    419   EXPECT_THAT(error,
    420               testing::ContainsRegex(
    421                   "extension includes the key file.*ext_root.a_key.pem"));
    422 }
    423 
    424 TEST_F(ExtensionFileUtilTest, CheckZeroLengthImageFile) {
    425   base::FilePath install_dir;
    426   ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
    427 
    428   // Try to install an extension with a zero-length icon file.
    429   base::FilePath ext_dir = install_dir.AppendASCII("extensions")
    430       .AppendASCII("bad")
    431       .AppendASCII("Extensions")
    432       .AppendASCII("ffffffffffffffffffffffffffffffff");
    433 
    434   std::string error;
    435   scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
    436       ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
    437   EXPECT_TRUE(extension.get() == NULL);
    438   EXPECT_STREQ("Could not load extension icon 'icon.png'.", error.c_str());
    439 
    440   // Try to install an extension with a zero-length browser action icon file.
    441   ext_dir = install_dir.AppendASCII("extensions")
    442       .AppendASCII("bad")
    443       .AppendASCII("Extensions")
    444       .AppendASCII("gggggggggggggggggggggggggggggggg");
    445 
    446   scoped_refptr<Extension> extension2(extension_file_util::LoadExtension(
    447       ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
    448   EXPECT_TRUE(extension2.get() == NULL);
    449   EXPECT_STREQ("Could not load icon 'icon.png' for browser action.",
    450                error.c_str());
    451 
    452   // Try to install an extension with a zero-length page action icon file.
    453   ext_dir = install_dir.AppendASCII("extensions")
    454       .AppendASCII("bad")
    455       .AppendASCII("Extensions")
    456       .AppendASCII("hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh");
    457 
    458   scoped_refptr<Extension> extension3(extension_file_util::LoadExtension(
    459       ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
    460   EXPECT_TRUE(extension3.get() == NULL);
    461   EXPECT_STREQ("Could not load icon 'icon.png' for page action.",
    462                error.c_str());
    463 }
    464 
    465 // TODO(aa): More tests as motivation allows. Maybe steal some from
    466 // ExtensionService? Many of them could probably be tested here without the
    467 // MessageLoop shenanigans.
    468