Home | History | Annotate | Download | only in common
      1 // Copyright 2013 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 "extensions/common/file_util.h"
      6 
      7 #include "base/basictypes.h"
      8 #include "base/file_util.h"
      9 #include "base/files/scoped_temp_dir.h"
     10 #include "base/json/json_string_value_serializer.h"
     11 #include "base/path_service.h"
     12 #include "base/strings/stringprintf.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "chrome/common/chrome_paths.h"
     15 #include "extensions/common/constants.h"
     16 #include "extensions/common/extension.h"
     17 #include "extensions/common/manifest.h"
     18 #include "extensions/common/manifest_constants.h"
     19 #include "grit/extensions_strings.h"
     20 #include "testing/gmock/include/gmock/gmock.h"
     21 #include "testing/gtest/include/gtest/gtest.h"
     22 #include "ui/base/l10n/l10n_util.h"
     23 #include "url/gurl.h"
     24 
     25 namespace extensions {
     26 
     27 namespace {
     28 
     29 scoped_refptr<Extension> LoadExtensionManifest(
     30     base::DictionaryValue* manifest,
     31     const base::FilePath& manifest_dir,
     32     Manifest::Location location,
     33     int extra_flags,
     34     std::string* error) {
     35   scoped_refptr<Extension> extension =
     36       Extension::Create(manifest_dir, location, *manifest, extra_flags, error);
     37   return extension;
     38 }
     39 
     40 scoped_refptr<Extension> LoadExtensionManifest(
     41     const std::string& manifest_value,
     42     const base::FilePath& manifest_dir,
     43     Manifest::Location location,
     44     int extra_flags,
     45     std::string* error) {
     46   JSONStringValueSerializer serializer(manifest_value);
     47   scoped_ptr<base::Value> result(serializer.Deserialize(NULL, error));
     48   if (!result.get())
     49     return NULL;
     50   CHECK_EQ(base::Value::TYPE_DICTIONARY, result->GetType());
     51   return LoadExtensionManifest(
     52       static_cast<base::DictionaryValue*>(result.get()),
     53       manifest_dir,
     54       location,
     55       extra_flags,
     56       error);
     57 }
     58 
     59 }  // namespace
     60 
     61 typedef testing::Test FileUtilTest;
     62 
     63 TEST_F(FileUtilTest, InstallUninstallGarbageCollect) {
     64   base::ScopedTempDir temp;
     65   ASSERT_TRUE(temp.CreateUniqueTempDir());
     66 
     67   // Create a source extension.
     68   std::string extension_id("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
     69   std::string version("1.0");
     70   base::FilePath src = temp.path().AppendASCII(extension_id);
     71   ASSERT_TRUE(base::CreateDirectory(src));
     72 
     73   // Create a extensions tree.
     74   base::FilePath all_extensions = temp.path().AppendASCII("extensions");
     75   ASSERT_TRUE(base::CreateDirectory(all_extensions));
     76 
     77   // Install in empty directory. Should create parent directories as needed.
     78   base::FilePath version_1 =
     79       file_util::InstallExtension(src, extension_id, version, all_extensions);
     80   ASSERT_EQ(
     81       version_1.value(),
     82       all_extensions.AppendASCII(extension_id).AppendASCII("1.0_0").value());
     83   ASSERT_TRUE(base::DirectoryExists(version_1));
     84 
     85   // Should have moved the source.
     86   ASSERT_FALSE(base::DirectoryExists(src));
     87 
     88   // Install again. Should create a new one with different name.
     89   ASSERT_TRUE(base::CreateDirectory(src));
     90   base::FilePath version_2 =
     91       file_util::InstallExtension(src, extension_id, version, all_extensions);
     92   ASSERT_EQ(
     93       version_2.value(),
     94       all_extensions.AppendASCII(extension_id).AppendASCII("1.0_1").value());
     95   ASSERT_TRUE(base::DirectoryExists(version_2));
     96 
     97   // Should have moved the source.
     98   ASSERT_FALSE(base::DirectoryExists(src));
     99 
    100   // Install yet again. Should create a new one with a different name.
    101   ASSERT_TRUE(base::CreateDirectory(src));
    102   base::FilePath version_3 =
    103       file_util::InstallExtension(src, extension_id, version, all_extensions);
    104   ASSERT_EQ(
    105       version_3.value(),
    106       all_extensions.AppendASCII(extension_id).AppendASCII("1.0_2").value());
    107   ASSERT_TRUE(base::DirectoryExists(version_3));
    108 
    109   // Uninstall. Should remove entire extension subtree.
    110   file_util::UninstallExtension(all_extensions, extension_id);
    111   ASSERT_FALSE(base::DirectoryExists(version_1.DirName()));
    112   ASSERT_FALSE(base::DirectoryExists(version_2.DirName()));
    113   ASSERT_FALSE(base::DirectoryExists(version_3.DirName()));
    114   ASSERT_TRUE(base::DirectoryExists(all_extensions));
    115 }
    116 
    117 TEST_F(FileUtilTest, LoadExtensionWithValidLocales) {
    118   base::FilePath install_dir;
    119   ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
    120   install_dir = install_dir.AppendASCII("extensions")
    121                     .AppendASCII("good")
    122                     .AppendASCII("Extensions")
    123                     .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
    124                     .AppendASCII("1.0.0.0");
    125 
    126   std::string error;
    127   scoped_refptr<Extension> extension(file_util::LoadExtension(
    128       install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
    129   ASSERT_TRUE(extension.get() != NULL);
    130   EXPECT_EQ("The first extension that I made.", extension->description());
    131 }
    132 
    133 TEST_F(FileUtilTest, LoadExtensionWithoutLocalesFolder) {
    134   base::FilePath install_dir;
    135   ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
    136   install_dir = install_dir.AppendASCII("extensions")
    137                     .AppendASCII("good")
    138                     .AppendASCII("Extensions")
    139                     .AppendASCII("bjafgdebaacbbbecmhlhpofkepfkgcpa")
    140                     .AppendASCII("1.0");
    141 
    142   std::string error;
    143   scoped_refptr<Extension> extension(file_util::LoadExtension(
    144       install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
    145   ASSERT_FALSE(extension.get() == NULL);
    146   EXPECT_TRUE(error.empty());
    147 }
    148 
    149 TEST_F(FileUtilTest, CheckIllegalFilenamesNoUnderscores) {
    150   base::ScopedTempDir temp;
    151   ASSERT_TRUE(temp.CreateUniqueTempDir());
    152 
    153   base::FilePath src_path = temp.path().AppendASCII("some_dir");
    154   ASSERT_TRUE(base::CreateDirectory(src_path));
    155 
    156   std::string data = "{ \"name\": { \"message\": \"foobar\" } }";
    157   ASSERT_TRUE(base::WriteFile(
    158       src_path.AppendASCII("some_file.txt"), data.c_str(), data.length()));
    159   std::string error;
    160   EXPECT_TRUE(file_util::CheckForIllegalFilenames(temp.path(), &error));
    161 }
    162 
    163 TEST_F(FileUtilTest, CheckIllegalFilenamesOnlyReserved) {
    164   base::ScopedTempDir temp;
    165   ASSERT_TRUE(temp.CreateUniqueTempDir());
    166 
    167   const base::FilePath::CharType* folders[] = {
    168       extensions::kLocaleFolder, extensions::kPlatformSpecificFolder};
    169 
    170   for (size_t i = 0; i < arraysize(folders); i++) {
    171     base::FilePath src_path = temp.path().Append(folders[i]);
    172     ASSERT_TRUE(base::CreateDirectory(src_path));
    173   }
    174 
    175   std::string error;
    176   EXPECT_TRUE(file_util::CheckForIllegalFilenames(temp.path(), &error));
    177 }
    178 
    179 TEST_F(FileUtilTest, CheckIllegalFilenamesReservedAndIllegal) {
    180   base::ScopedTempDir temp;
    181   ASSERT_TRUE(temp.CreateUniqueTempDir());
    182 
    183   base::FilePath src_path = temp.path().Append(extensions::kLocaleFolder);
    184   ASSERT_TRUE(base::CreateDirectory(src_path));
    185 
    186   src_path = temp.path().AppendASCII("_some_dir");
    187   ASSERT_TRUE(base::CreateDirectory(src_path));
    188 
    189   std::string error;
    190   EXPECT_FALSE(file_util::CheckForIllegalFilenames(temp.path(), &error));
    191 }
    192 
    193 TEST_F(FileUtilTest, LoadExtensionGivesHelpfullErrorOnMissingManifest) {
    194   base::FilePath install_dir;
    195   ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
    196   install_dir = install_dir.AppendASCII("extensions")
    197                     .AppendASCII("bad")
    198                     .AppendASCII("Extensions")
    199                     .AppendASCII("dddddddddddddddddddddddddddddddd")
    200                     .AppendASCII("1.0");
    201 
    202   std::string error;
    203   scoped_refptr<Extension> extension(file_util::LoadExtension(
    204       install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
    205   ASSERT_TRUE(extension.get() == NULL);
    206   ASSERT_FALSE(error.empty());
    207   ASSERT_STREQ("Manifest file is missing or unreadable.", error.c_str());
    208 }
    209 
    210 TEST_F(FileUtilTest, LoadExtensionGivesHelpfullErrorOnBadManifest) {
    211   base::FilePath install_dir;
    212   ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
    213   install_dir = install_dir.AppendASCII("extensions")
    214                     .AppendASCII("bad")
    215                     .AppendASCII("Extensions")
    216                     .AppendASCII("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee")
    217                     .AppendASCII("1.0");
    218 
    219   std::string error;
    220   scoped_refptr<Extension> extension(file_util::LoadExtension(
    221       install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
    222   ASSERT_TRUE(extension.get() == NULL);
    223   ASSERT_FALSE(error.empty());
    224   ASSERT_STREQ(
    225       "Manifest is not valid JSON.  "
    226       "Line: 2, column: 16, Syntax error.",
    227       error.c_str());
    228 }
    229 
    230 TEST_F(FileUtilTest, FailLoadingNonUTF8Scripts) {
    231   base::FilePath install_dir;
    232   ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
    233   install_dir = install_dir.AppendASCII("extensions")
    234                     .AppendASCII("bad")
    235                     .AppendASCII("bad_encoding");
    236 
    237   std::string error;
    238   scoped_refptr<Extension> extension(file_util::LoadExtension(
    239       install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
    240   ASSERT_TRUE(extension.get() == NULL);
    241   ASSERT_STREQ(
    242       "Could not load file 'bad_encoding.js' for content script. "
    243       "It isn't UTF-8 encoded.",
    244       error.c_str());
    245 }
    246 
    247 TEST_F(FileUtilTest, ValidateThemeUTF8) {
    248   base::ScopedTempDir temp;
    249   ASSERT_TRUE(temp.CreateUniqueTempDir());
    250 
    251   // "aeo" with accents. Use http://0xcc.net/jsescape/ to decode them.
    252   std::string non_ascii_file = "\xC3\xA0\xC3\xA8\xC3\xB2.png";
    253   base::FilePath non_ascii_path =
    254       temp.path().Append(base::FilePath::FromUTF8Unsafe(non_ascii_file));
    255   base::WriteFile(non_ascii_path, "", 0);
    256 
    257   std::string kManifest = base::StringPrintf(
    258       "{ \"name\": \"Test\", \"version\": \"1.0\", "
    259       "  \"theme\": { \"images\": { \"theme_frame\": \"%s\" } }"
    260       "}",
    261       non_ascii_file.c_str());
    262   std::string error;
    263   scoped_refptr<Extension> extension = LoadExtensionManifest(
    264       kManifest, temp.path(), Manifest::UNPACKED, 0, &error);
    265   ASSERT_TRUE(extension.get()) << error;
    266 
    267   std::vector<extensions::InstallWarning> warnings;
    268   EXPECT_TRUE(file_util::ValidateExtension(extension.get(), &error, &warnings))
    269       << error;
    270   EXPECT_EQ(0U, warnings.size());
    271 }
    272 
    273 TEST_F(FileUtilTest, BackgroundScriptsMustExist) {
    274   base::ScopedTempDir temp;
    275   ASSERT_TRUE(temp.CreateUniqueTempDir());
    276 
    277   scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
    278   value->SetString("name", "test");
    279   value->SetString("version", "1");
    280   value->SetInteger("manifest_version", 1);
    281 
    282   base::ListValue* scripts = new base::ListValue();
    283   scripts->Append(new base::StringValue("foo.js"));
    284   value->Set("background.scripts", scripts);
    285 
    286   std::string error;
    287   std::vector<extensions::InstallWarning> warnings;
    288   scoped_refptr<Extension> extension = LoadExtensionManifest(
    289       value.get(), temp.path(), Manifest::UNPACKED, 0, &error);
    290   ASSERT_TRUE(extension.get()) << error;
    291 
    292   EXPECT_FALSE(
    293       file_util::ValidateExtension(extension.get(), &error, &warnings));
    294   EXPECT_EQ(
    295       l10n_util::GetStringFUTF8(IDS_EXTENSION_LOAD_BACKGROUND_SCRIPT_FAILED,
    296                                 base::ASCIIToUTF16("foo.js")),
    297       error);
    298   EXPECT_EQ(0U, warnings.size());
    299 
    300   scripts->Clear();
    301   scripts->Append(new base::StringValue("http://google.com/foo.js"));
    302 
    303   extension = LoadExtensionManifest(
    304       value.get(), temp.path(), Manifest::UNPACKED, 0, &error);
    305   ASSERT_TRUE(extension.get()) << error;
    306 
    307   warnings.clear();
    308   EXPECT_FALSE(
    309       file_util::ValidateExtension(extension.get(), &error, &warnings));
    310   EXPECT_EQ(
    311       l10n_util::GetStringFUTF8(IDS_EXTENSION_LOAD_BACKGROUND_SCRIPT_FAILED,
    312                                 base::ASCIIToUTF16("http://google.com/foo.js")),
    313       error);
    314   EXPECT_EQ(0U, warnings.size());
    315 }
    316 
    317 // Private key, generated by Chrome specifically for this test, and
    318 // never used elsewhere.
    319 const char private_key[] =
    320     "-----BEGIN PRIVATE KEY-----\n"
    321     "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKt02SR0FYaYy6fpW\n"
    322     "MAA+kU1BgK3d+OmmWfdr+JATIjhRkyeSF4lTd/71JQsyKqPzYkQPi3EeROWM+goTv\n"
    323     "EhJqq07q63BolpsFmlV+S4ny+sBA2B4aWwRYXlBWikdrQSA0mJMzvEHc6nKzBgXik\n"
    324     "QSVbyyBNAsxlDB9WaCxRVOpK3AgMBAAECgYBGvSPlrVtAOAQ2V8j9FqorKZA8SLPX\n"
    325     "IeJC/yzU3RB2nPMjI17aMOvrUHxJUhzMeh4jwabVvSzzDtKFozPGupW3xaI8sQdi2\n"
    326     "WWMTQIk/Q9HHDWoQ9qA6SwX2qWCc5SyjCKqVp78ye+000kqTJYjBsDgXeAlzKcx2B\n"
    327     "4GAAeWonDdkQJBANNb8wrqNWFn7DqyQTfELzcRTRnqQ/r1pdeJo6obzbnwGnlqe3t\n"
    328     "KhLjtJNIGrQg5iC0OVLWFuvPJs0t3z62A1ckCQQDPq2JZuwTwu5Pl4DJ0r9O1FdqN\n"
    329     "JgqPZyMptokCDQ3khLLGakIu+TqB9YtrzI69rJMSG2Egb+6McaDX+dh3XmR/AkB9t\n"
    330     "xJf6qDnmA2td/tMtTc0NOk8Qdg/fD8xbZ/YfYMnVoYYs9pQoilBaWRePDRNURMLYZ\n"
    331     "vHAI0Llmw7tj7jv17pAkEAz44uXRpjRKtllUIvi5pUENAHwDz+HvdpGH68jpU3hmb\n"
    332     "uOwrmnQYxaMReFV68Z2w9DcLZn07f7/R9Wn72z89CxwJAFsDoNaDes4h48bX7plct\n"
    333     "s9ACjmTwcCigZjN2K7AGv7ntCLF3DnV5dK0dTHNaAdD3SbY3jl29Rk2CwiURSX6Ee\n"
    334     "g==\n"
    335     "-----END PRIVATE KEY-----\n";
    336 
    337 TEST_F(FileUtilTest, FindPrivateKeyFiles) {
    338   base::ScopedTempDir temp;
    339   ASSERT_TRUE(temp.CreateUniqueTempDir());
    340 
    341   base::FilePath src_path = temp.path().AppendASCII("some_dir");
    342   ASSERT_TRUE(base::CreateDirectory(src_path));
    343 
    344   ASSERT_TRUE(base::WriteFile(
    345       src_path.AppendASCII("a_key.pem"), private_key, arraysize(private_key)));
    346   ASSERT_TRUE(base::WriteFile(src_path.AppendASCII("second_key.pem"),
    347                               private_key,
    348                               arraysize(private_key)));
    349   // Shouldn't find a key with a different extension.
    350   ASSERT_TRUE(base::WriteFile(src_path.AppendASCII("key.diff_ext"),
    351                               private_key,
    352                               arraysize(private_key)));
    353   // Shouldn't find a key that isn't parsable.
    354   ASSERT_TRUE(base::WriteFile(src_path.AppendASCII("unparsable_key.pem"),
    355                               private_key,
    356                               arraysize(private_key) - 30));
    357   std::vector<base::FilePath> private_keys =
    358       file_util::FindPrivateKeyFiles(temp.path());
    359   EXPECT_EQ(2U, private_keys.size());
    360   EXPECT_THAT(private_keys,
    361               testing::Contains(src_path.AppendASCII("a_key.pem")));
    362   EXPECT_THAT(private_keys,
    363               testing::Contains(src_path.AppendASCII("second_key.pem")));
    364 }
    365 
    366 TEST_F(FileUtilTest, WarnOnPrivateKey) {
    367   base::ScopedTempDir temp;
    368   ASSERT_TRUE(temp.CreateUniqueTempDir());
    369 
    370   base::FilePath ext_path = temp.path().AppendASCII("ext_root");
    371   ASSERT_TRUE(base::CreateDirectory(ext_path));
    372 
    373   const char manifest[] =
    374       "{\n"
    375       "  \"name\": \"Test Extension\",\n"
    376       "  \"version\": \"1.0\",\n"
    377       "  \"manifest_version\": 2,\n"
    378       "  \"description\": \"The first extension that I made.\"\n"
    379       "}\n";
    380   ASSERT_TRUE(base::WriteFile(
    381       ext_path.AppendASCII("manifest.json"), manifest, strlen(manifest)));
    382   ASSERT_TRUE(base::WriteFile(
    383       ext_path.AppendASCII("a_key.pem"), private_key, strlen(private_key)));
    384 
    385   std::string error;
    386   scoped_refptr<Extension> extension(
    387       file_util::LoadExtension(ext_path,
    388                                "the_id",
    389                                Manifest::EXTERNAL_PREF,
    390                                Extension::NO_FLAGS,
    391                                &error));
    392   ASSERT_TRUE(extension.get()) << error;
    393   ASSERT_EQ(1u, extension->install_warnings().size());
    394   EXPECT_THAT(extension->install_warnings(),
    395               testing::ElementsAre(testing::Field(
    396                   &extensions::InstallWarning::message,
    397                   testing::ContainsRegex(
    398                       "extension includes the key file.*ext_root.a_key.pem"))));
    399 
    400   // Turn the warning into an error with ERROR_ON_PRIVATE_KEY.
    401   extension = file_util::LoadExtension(ext_path,
    402                                        "the_id",
    403                                        Manifest::EXTERNAL_PREF,
    404                                        Extension::ERROR_ON_PRIVATE_KEY,
    405                                        &error);
    406   EXPECT_FALSE(extension.get());
    407   EXPECT_THAT(error,
    408               testing::ContainsRegex(
    409                   "extension includes the key file.*ext_root.a_key.pem"));
    410 }
    411 
    412 TEST_F(FileUtilTest, CheckZeroLengthImageFile) {
    413   base::FilePath install_dir;
    414   ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
    415 
    416   // Try to install an extension with a zero-length icon file.
    417   base::FilePath ext_dir = install_dir.AppendASCII("extensions")
    418                                .AppendASCII("bad")
    419                                .AppendASCII("Extensions")
    420                                .AppendASCII("ffffffffffffffffffffffffffffffff");
    421 
    422   std::string error;
    423   scoped_refptr<Extension> extension(file_util::LoadExtension(
    424       ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
    425   EXPECT_TRUE(extension.get() == NULL);
    426   EXPECT_STREQ("Could not load extension icon 'icon.png'.", error.c_str());
    427 
    428   // Try to install an extension with a zero-length browser action icon file.
    429   ext_dir = install_dir.AppendASCII("extensions")
    430                 .AppendASCII("bad")
    431                 .AppendASCII("Extensions")
    432                 .AppendASCII("gggggggggggggggggggggggggggggggg");
    433 
    434   scoped_refptr<Extension> extension2(file_util::LoadExtension(
    435       ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
    436   EXPECT_TRUE(extension2.get() == NULL);
    437   EXPECT_STREQ("Could not load icon 'icon.png' for browser action.",
    438                error.c_str());
    439 
    440   // Try to install an extension with a zero-length page action icon file.
    441   ext_dir = install_dir.AppendASCII("extensions")
    442                 .AppendASCII("bad")
    443                 .AppendASCII("Extensions")
    444                 .AppendASCII("hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh");
    445 
    446   scoped_refptr<Extension> extension3(file_util::LoadExtension(
    447       ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
    448   EXPECT_TRUE(extension3.get() == NULL);
    449   EXPECT_STREQ("Could not load icon 'icon.png' for page action.",
    450                error.c_str());
    451 }
    452 
    453 TEST_F(FileUtilTest, ExtensionURLToRelativeFilePath) {
    454 #define URL_PREFIX "chrome-extension://extension-id/"
    455   struct TestCase {
    456     const char* url;
    457     const char* expected_relative_path;
    458   } test_cases[] = {
    459     { URL_PREFIX "simple.html",
    460       "simple.html" },
    461     { URL_PREFIX "directory/to/file.html",
    462       "directory/to/file.html" },
    463     { URL_PREFIX "escape%20spaces.html",
    464       "escape spaces.html" },
    465     { URL_PREFIX "%C3%9Cber.html",
    466       "\xC3\x9C" "ber.html" },
    467 #if defined(OS_WIN)
    468     { URL_PREFIX "C%3A/simple.html",
    469       "" },
    470 #endif
    471     { URL_PREFIX "////simple.html",
    472       "simple.html" },
    473     { URL_PREFIX "/simple.html",
    474       "simple.html" },
    475     { URL_PREFIX "\\simple.html",
    476       "simple.html" },
    477     { URL_PREFIX "\\\\foo\\simple.html",
    478       "foo/simple.html" },
    479   };
    480 #undef URL_PREFIX
    481 
    482   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    483     GURL url(test_cases[i].url);
    484     base::FilePath expected_path =
    485         base::FilePath::FromUTF8Unsafe(test_cases[i].expected_relative_path);
    486     base::FilePath actual_path =
    487         extensions::file_util::ExtensionURLToRelativeFilePath(url);
    488     EXPECT_FALSE(actual_path.IsAbsolute()) <<
    489       " For the path " << actual_path.value();
    490     EXPECT_EQ(expected_path.value(), actual_path.value()) <<
    491       " For the path " << url;
    492   }
    493 }
    494 
    495 TEST_F(FileUtilTest, ExtensionResourceURLToFilePath) {
    496   // Setup filesystem for testing.
    497   base::FilePath root_path;
    498   ASSERT_TRUE(base::CreateNewTempDirectory(
    499       base::FilePath::StringType(), &root_path));
    500   root_path = base::MakeAbsoluteFilePath(root_path);
    501   ASSERT_FALSE(root_path.empty());
    502 
    503   base::FilePath api_path = root_path.Append(FILE_PATH_LITERAL("apiname"));
    504   ASSERT_TRUE(base::CreateDirectory(api_path));
    505 
    506   const char data[] = "Test Data";
    507   base::FilePath resource_path = api_path.Append(FILE_PATH_LITERAL("test.js"));
    508   ASSERT_TRUE(base::WriteFile(resource_path, data, sizeof(data)));
    509   resource_path = api_path.Append(FILE_PATH_LITERAL("escape spaces.js"));
    510   ASSERT_TRUE(base::WriteFile(resource_path, data, sizeof(data)));
    511 
    512 #ifdef FILE_PATH_USES_WIN_SEPARATORS
    513 #define SEP "\\"
    514 #else
    515 #define SEP "/"
    516 #endif
    517 #define URL_PREFIX "chrome-extension-resource://"
    518   struct TestCase {
    519     const char* url;
    520     const base::FilePath::CharType* expected_path;
    521   } test_cases[] = {
    522     { URL_PREFIX "apiname/test.js",
    523       FILE_PATH_LITERAL("test.js") },
    524     { URL_PREFIX "/apiname/test.js",
    525       FILE_PATH_LITERAL("test.js") },
    526     // Test % escape
    527     { URL_PREFIX "apiname/%74%65st.js",
    528       FILE_PATH_LITERAL("test.js") },
    529     { URL_PREFIX "apiname/escape%20spaces.js",
    530       FILE_PATH_LITERAL("escape spaces.js") },
    531     // Test file does not exist.
    532     { URL_PREFIX "apiname/directory/to/file.js",
    533       NULL },
    534     // Test apiname/../../test.js
    535     { URL_PREFIX "apiname/../../test.js",
    536       FILE_PATH_LITERAL("test.js") },
    537     { URL_PREFIX "apiname/..%2F../test.js",
    538       NULL },
    539     { URL_PREFIX "apiname/f/../../../test.js",
    540       FILE_PATH_LITERAL("test.js") },
    541     { URL_PREFIX "apiname/f%2F..%2F..%2F../test.js",
    542       NULL },
    543   };
    544 #undef SEP
    545 #undef URL_PREFIX
    546 
    547   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
    548     GURL url(test_cases[i].url);
    549     base::FilePath expected_path;
    550     if (test_cases[i].expected_path)
    551       expected_path = root_path.Append(FILE_PATH_LITERAL("apiname")).Append(
    552           test_cases[i].expected_path);
    553     base::FilePath actual_path =
    554         extensions::file_util::ExtensionResourceURLToFilePath(url, root_path);
    555     EXPECT_EQ(expected_path.value(), actual_path.value()) <<
    556       " For the path " << url;
    557   }
    558   // Remove temp files.
    559   ASSERT_TRUE(base::DeleteFile(root_path, true));
    560 }
    561 
    562 }  // namespace extensions
    563