Home | History | Annotate | Download | only in download
      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 <string>
      6 
      7 #include "base/files/file_path.h"
      8 #include "base/files/scoped_temp_dir.h"
      9 #include "base/path_service.h"
     10 #include "base/strings/string_util.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "content/browser/download/save_package.h"
     13 #include "content/public/common/url_constants.h"
     14 #include "content/test/test_render_view_host.h"
     15 #include "content/test/test_web_contents.h"
     16 #include "net/test/url_request/url_request_mock_http_job.h"
     17 #include "testing/gtest/include/gtest/gtest.h"
     18 #include "url/gurl.h"
     19 
     20 namespace content {
     21 
     22 #define FPL FILE_PATH_LITERAL
     23 #define HTML_EXTENSION ".html"
     24 #if defined(OS_WIN)
     25 #define FPL_HTML_EXTENSION L".html"
     26 #else
     27 #define FPL_HTML_EXTENSION ".html"
     28 #endif
     29 
     30 namespace {
     31 
     32 // This constant copied from save_package.cc.
     33 #if defined(OS_WIN)
     34 const uint32 kMaxFilePathLength = MAX_PATH - 1;
     35 const uint32 kMaxFileNameLength = MAX_PATH - 1;
     36 #elif defined(OS_POSIX)
     37 const uint32 kMaxFilePathLength = PATH_MAX - 1;
     38 const uint32 kMaxFileNameLength = NAME_MAX;
     39 #endif
     40 
     41 // Used to make long filenames.
     42 std::string long_file_name(
     43     "EFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz01234567"
     44     "89ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz012345"
     45     "6789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0123"
     46     "456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789a");
     47 
     48 bool HasOrdinalNumber(const base::FilePath::StringType& filename) {
     49   base::FilePath::StringType::size_type r_paren_index =
     50       filename.rfind(FPL(')'));
     51   base::FilePath::StringType::size_type l_paren_index =
     52       filename.rfind(FPL('('));
     53   if (l_paren_index >= r_paren_index)
     54     return false;
     55 
     56   for (base::FilePath::StringType::size_type i = l_paren_index + 1;
     57        i != r_paren_index; ++i) {
     58     if (!IsAsciiDigit(filename[i]))
     59       return false;
     60   }
     61 
     62   return true;
     63 }
     64 
     65 }  // namespace
     66 
     67 class SavePackageTest : public RenderViewHostImplTestHarness {
     68  public:
     69   bool GetGeneratedFilename(bool need_success_generate_filename,
     70                             const std::string& disposition,
     71                             const std::string& url,
     72                             bool need_htm_ext,
     73                             base::FilePath::StringType* generated_name) {
     74     SavePackage* save_package;
     75     if (need_success_generate_filename)
     76       save_package = save_package_success_.get();
     77     else
     78       save_package = save_package_fail_.get();
     79     return save_package->GenerateFileName(disposition, GURL(url), need_htm_ext,
     80                                           generated_name);
     81   }
     82 
     83   base::FilePath EnsureHtmlExtension(const base::FilePath& name) {
     84     return SavePackage::EnsureHtmlExtension(name);
     85   }
     86 
     87   base::FilePath EnsureMimeExtension(const base::FilePath& name,
     88                                const std::string& content_mime_type) {
     89     return SavePackage::EnsureMimeExtension(name, content_mime_type);
     90   }
     91 
     92   GURL GetUrlToBeSaved() {
     93     return save_package_success_->GetUrlToBeSaved();
     94   }
     95 
     96  protected:
     97   virtual void SetUp() {
     98     RenderViewHostImplTestHarness::SetUp();
     99 
    100     // Do the initialization in SetUp so contents() is initialized by
    101     // RenderViewHostImplTestHarness::SetUp.
    102     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    103 
    104     save_package_success_ = new SavePackage(contents(),
    105         temp_dir_.path().AppendASCII("testfile" HTML_EXTENSION),
    106         temp_dir_.path().AppendASCII("testfile_files"));
    107 
    108     // We need to construct a path that is *almost* kMaxFilePathLength long
    109     long_file_name.reserve(kMaxFilePathLength + long_file_name.length());
    110     while (long_file_name.length() < kMaxFilePathLength)
    111       long_file_name += long_file_name;
    112     long_file_name.resize(
    113         kMaxFilePathLength - 9 - temp_dir_.path().value().length());
    114 
    115     save_package_fail_ = new SavePackage(contents(),
    116         temp_dir_.path().AppendASCII(long_file_name + HTML_EXTENSION),
    117         temp_dir_.path().AppendASCII(long_file_name + "_files"));
    118   }
    119 
    120  private:
    121   // SavePackage for successfully generating file name.
    122   scoped_refptr<SavePackage> save_package_success_;
    123   // SavePackage for failed generating file name.
    124   scoped_refptr<SavePackage> save_package_fail_;
    125 
    126   base::ScopedTempDir temp_dir_;
    127 };
    128 
    129 static const struct {
    130   const char* disposition;
    131   const char* url;
    132   const base::FilePath::CharType* expected_name;
    133   bool need_htm_ext;
    134 } kGeneratedFiles[] = {
    135   // We mainly focus on testing duplicated names here, since retrieving file
    136   // name from disposition and url has been tested in DownloadManagerTest.
    137 
    138   // No useful information in disposition or URL, use default.
    139   {"1.html", "http://www.savepage.com/",
    140     FPL("saved_resource") FPL_HTML_EXTENSION, true},
    141 
    142   // No duplicate occurs.
    143   {"filename=1.css", "http://www.savepage.com", FPL("1.css"), false},
    144 
    145   // No duplicate occurs.
    146   {"filename=1.js", "http://www.savepage.com", FPL("1.js"), false},
    147 
    148   // Append numbers for duplicated names.
    149   {"filename=1.css", "http://www.savepage.com", FPL("1(1).css"), false},
    150 
    151   // No duplicate occurs.
    152   {"filename=1(1).js", "http://www.savepage.com", FPL("1(1).js"), false},
    153 
    154   // Append numbers for duplicated names.
    155   {"filename=1.css", "http://www.savepage.com", FPL("1(2).css"), false},
    156 
    157   // Change number for duplicated names.
    158   {"filename=1(1).css", "http://www.savepage.com", FPL("1(3).css"), false},
    159 
    160   // No duplicate occurs.
    161   {"filename=1(11).css", "http://www.savepage.com", FPL("1(11).css"), false},
    162 
    163   // Test for case-insensitive file names.
    164   {"filename=readme.txt", "http://www.savepage.com",
    165                           FPL("readme.txt"), false},
    166 
    167   {"filename=readme.TXT", "http://www.savepage.com",
    168                           FPL("readme(1).TXT"), false},
    169 
    170   {"filename=READme.txt", "http://www.savepage.com",
    171                           FPL("readme(2).txt"), false},
    172 
    173   {"filename=Readme(1).txt", "http://www.savepage.com",
    174                           FPL("readme(3).txt"), false},
    175 };
    176 
    177 TEST_F(SavePackageTest, TestSuccessfullyGenerateSavePackageFilename) {
    178   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kGeneratedFiles); ++i) {
    179     base::FilePath::StringType file_name;
    180     bool ok = GetGeneratedFilename(true,
    181                                    kGeneratedFiles[i].disposition,
    182                                    kGeneratedFiles[i].url,
    183                                    kGeneratedFiles[i].need_htm_ext,
    184                                    &file_name);
    185     ASSERT_TRUE(ok);
    186     EXPECT_EQ(kGeneratedFiles[i].expected_name, file_name);
    187   }
    188 }
    189 
    190 TEST_F(SavePackageTest, TestUnSuccessfullyGenerateSavePackageFilename) {
    191   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kGeneratedFiles); ++i) {
    192     base::FilePath::StringType file_name;
    193     bool ok = GetGeneratedFilename(false,
    194                                    kGeneratedFiles[i].disposition,
    195                                    kGeneratedFiles[i].url,
    196                                    kGeneratedFiles[i].need_htm_ext,
    197                                    &file_name);
    198     ASSERT_FALSE(ok);
    199   }
    200 }
    201 
    202 // Crashing on Windows, see http://crbug.com/79365
    203 #if defined(OS_WIN)
    204 #define MAYBE_TestLongSavePackageFilename DISABLED_TestLongSavePackageFilename
    205 #else
    206 #define MAYBE_TestLongSavePackageFilename TestLongSavePackageFilename
    207 #endif
    208 TEST_F(SavePackageTest, MAYBE_TestLongSavePackageFilename) {
    209   const std::string base_url("http://www.google.com/");
    210   const std::string long_file = long_file_name + ".css";
    211   const std::string url = base_url + long_file;
    212 
    213   base::FilePath::StringType filename;
    214   // Test that the filename is successfully shortened to fit.
    215   ASSERT_TRUE(GetGeneratedFilename(true, std::string(), url, false, &filename));
    216   EXPECT_TRUE(filename.length() < long_file.length());
    217   EXPECT_FALSE(HasOrdinalNumber(filename));
    218 
    219   // Test that the filename is successfully shortened to fit, and gets an
    220   // an ordinal appended.
    221   ASSERT_TRUE(GetGeneratedFilename(true, std::string(), url, false, &filename));
    222   EXPECT_TRUE(filename.length() < long_file.length());
    223   EXPECT_TRUE(HasOrdinalNumber(filename));
    224 
    225   // Test that the filename is successfully shortened to fit, and gets a
    226   // different ordinal appended.
    227   base::FilePath::StringType filename2;
    228   ASSERT_TRUE(
    229       GetGeneratedFilename(true, std::string(), url, false, &filename2));
    230   EXPECT_TRUE(filename2.length() < long_file.length());
    231   EXPECT_TRUE(HasOrdinalNumber(filename2));
    232   EXPECT_NE(filename, filename2);
    233 }
    234 
    235 // Crashing on Windows, see http://crbug.com/79365
    236 #if defined(OS_WIN)
    237 #define MAYBE_TestLongSafePureFilename DISABLED_TestLongSafePureFilename
    238 #else
    239 #define MAYBE_TestLongSafePureFilename TestLongSafePureFilename
    240 #endif
    241 TEST_F(SavePackageTest, MAYBE_TestLongSafePureFilename) {
    242   const base::FilePath save_dir(FPL("test_dir"));
    243   const base::FilePath::StringType ext(FPL_HTML_EXTENSION);
    244   base::FilePath::StringType filename =
    245 #if defined(OS_WIN)
    246       base::ASCIIToWide(long_file_name);
    247 #else
    248       long_file_name;
    249 #endif
    250 
    251   // Test that the filename + extension doesn't exceed kMaxFileNameLength
    252   uint32 max_path = SavePackage::GetMaxPathLengthForDirectory(save_dir);
    253   ASSERT_TRUE(SavePackage::GetSafePureFileName(save_dir, ext, max_path,
    254                                                &filename));
    255   EXPECT_TRUE(filename.length() <= kMaxFileNameLength-ext.length());
    256 }
    257 
    258 static const struct {
    259   const base::FilePath::CharType* page_title;
    260   const base::FilePath::CharType* expected_name;
    261 } kExtensionTestCases[] = {
    262   // Extension is preserved if it is already proper for HTML.
    263   {FPL("filename.html"), FPL("filename.html")},
    264   {FPL("filename.HTML"), FPL("filename.HTML")},
    265   {FPL("filename.XHTML"), FPL("filename.XHTML")},
    266   {FPL("filename.xhtml"), FPL("filename.xhtml")},
    267   {FPL("filename.htm"), FPL("filename.htm")},
    268   // ".htm" is added if the extension is improper for HTML.
    269   {FPL("hello.world"), FPL("hello.world") FPL_HTML_EXTENSION},
    270   {FPL("hello.txt"), FPL("hello.txt") FPL_HTML_EXTENSION},
    271   {FPL("is.html.good"), FPL("is.html.good") FPL_HTML_EXTENSION},
    272   // ".htm" is added if the name doesn't have an extension.
    273   {FPL("helloworld"), FPL("helloworld") FPL_HTML_EXTENSION},
    274   {FPL("helloworld."), FPL("helloworld.") FPL_HTML_EXTENSION},
    275 };
    276 
    277 // Crashing on Windows, see http://crbug.com/79365
    278 #if defined(OS_WIN)
    279 #define MAYBE_TestEnsureHtmlExtension DISABLED_TestEnsureHtmlExtension
    280 #else
    281 #define MAYBE_TestEnsureHtmlExtension TestEnsureHtmlExtension
    282 #endif
    283 TEST_F(SavePackageTest, MAYBE_TestEnsureHtmlExtension) {
    284   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kExtensionTestCases); ++i) {
    285     base::FilePath original = base::FilePath(kExtensionTestCases[i].page_title);
    286     base::FilePath expected =
    287         base::FilePath(kExtensionTestCases[i].expected_name);
    288     base::FilePath actual = EnsureHtmlExtension(original);
    289     EXPECT_EQ(expected.value(), actual.value()) << "Failed for page title: " <<
    290         kExtensionTestCases[i].page_title;
    291   }
    292 }
    293 
    294 // Crashing on Windows, see http://crbug.com/79365
    295 #if defined(OS_WIN)
    296 #define MAYBE_TestEnsureMimeExtension DISABLED_TestEnsureMimeExtension
    297 #else
    298 #define MAYBE_TestEnsureMimeExtension TestEnsureMimeExtension
    299 #endif
    300 TEST_F(SavePackageTest, MAYBE_TestEnsureMimeExtension) {
    301   static const struct {
    302     const base::FilePath::CharType* page_title;
    303     const base::FilePath::CharType* expected_name;
    304     const char* contents_mime_type;
    305   } kExtensionTests[] = {
    306     { FPL("filename.html"), FPL("filename.html"), "text/html" },
    307     { FPL("filename.htm"), FPL("filename.htm"), "text/html" },
    308     { FPL("filename.xhtml"), FPL("filename.xhtml"), "text/html" },
    309 #if defined(OS_WIN)
    310     { FPL("filename"), FPL("filename.htm"), "text/html" },
    311 #else  // defined(OS_WIN)
    312     { FPL("filename"), FPL("filename.html"), "text/html" },
    313 #endif  // defined(OS_WIN)
    314     { FPL("filename.html"), FPL("filename.html"), "text/xml" },
    315     { FPL("filename.xml"), FPL("filename.xml"), "text/xml" },
    316     { FPL("filename"), FPL("filename.xml"), "text/xml" },
    317     { FPL("filename.xhtml"), FPL("filename.xhtml"),
    318       "application/xhtml+xml" },
    319     { FPL("filename.html"), FPL("filename.html"),
    320       "application/xhtml+xml" },
    321     { FPL("filename"), FPL("filename.xhtml"), "application/xhtml+xml" },
    322     { FPL("filename.txt"), FPL("filename.txt"), "text/plain" },
    323     { FPL("filename"), FPL("filename.txt"), "text/plain" },
    324     { FPL("filename.css"), FPL("filename.css"), "text/css" },
    325     { FPL("filename"), FPL("filename.css"), "text/css" },
    326     { FPL("filename.abc"), FPL("filename.abc"), "unknown/unknown" },
    327     { FPL("filename"), FPL("filename"), "unknown/unknown" },
    328   };
    329   for (uint32 i = 0; i < ARRAYSIZE_UNSAFE(kExtensionTests); ++i) {
    330     base::FilePath original = base::FilePath(kExtensionTests[i].page_title);
    331     base::FilePath expected = base::FilePath(kExtensionTests[i].expected_name);
    332     std::string mime_type(kExtensionTests[i].contents_mime_type);
    333     base::FilePath actual = EnsureMimeExtension(original, mime_type);
    334     EXPECT_EQ(expected.value(), actual.value()) << "Failed for page title: " <<
    335         kExtensionTests[i].page_title << " MIME:" << mime_type;
    336   }
    337 }
    338 
    339 // Test that the suggested names generated by SavePackage are reasonable:
    340 // If the name is a URL, retrieve only the path component since the path name
    341 // generation code will turn the entire URL into the file name leading to bad
    342 // extension names. For example, a page with no title and a URL:
    343 // http://www.foo.com/a/path/name.txt will turn into file:
    344 // "http www.foo.com a path name.txt", when we want to save it as "name.txt".
    345 
    346 static const struct SuggestedSaveNameTestCase {
    347   const char* page_url;
    348   const base::string16 page_title;
    349   const base::FilePath::CharType* expected_name;
    350   bool ensure_html_extension;
    351 } kSuggestedSaveNames[] = {
    352   // Title overrides the URL.
    353   { "http://foo.com",
    354     base::ASCIIToUTF16("A page title"),
    355     FPL("A page title") FPL_HTML_EXTENSION,
    356     true
    357   },
    358   // Extension is preserved.
    359   { "http://foo.com",
    360     base::ASCIIToUTF16("A page title with.ext"),
    361     FPL("A page title with.ext"),
    362     false
    363   },
    364   // If the title matches the URL, use the last component of the URL.
    365   { "http://foo.com/bar",
    366     base::ASCIIToUTF16("foo.com/bar"),
    367     FPL("bar"),
    368     false
    369   },
    370   // If the title matches the URL, but there is no "filename" component,
    371   // use the domain.
    372   { "http://foo.com",
    373     base::ASCIIToUTF16("foo.com"),
    374     FPL("foo.com"),
    375     false
    376   },
    377   // Make sure fuzzy matching works.
    378   { "http://foo.com/bar",
    379     base::ASCIIToUTF16("foo.com/bar"),
    380     FPL("bar"),
    381     false
    382   },
    383   // A URL-like title that does not match the title is respected in full.
    384   { "http://foo.com",
    385     base::ASCIIToUTF16("http://www.foo.com/path/title.txt"),
    386     FPL("http   www.foo.com path title.txt"),
    387     false
    388   },
    389 };
    390 
    391 // Crashing on Windows, see http://crbug.com/79365
    392 #if defined(OS_WIN)
    393 #define MAYBE_TestSuggestedSaveNames DISABLED_TestSuggestedSaveNames
    394 #else
    395 #define MAYBE_TestSuggestedSaveNames TestSuggestedSaveNames
    396 #endif
    397 TEST_F(SavePackageTest, MAYBE_TestSuggestedSaveNames) {
    398   for (size_t i = 0; i < arraysize(kSuggestedSaveNames); ++i) {
    399     scoped_refptr<SavePackage> save_package(
    400         new SavePackage(contents(), base::FilePath(), base::FilePath()));
    401     save_package->page_url_ = GURL(kSuggestedSaveNames[i].page_url);
    402     save_package->title_ = kSuggestedSaveNames[i].page_title;
    403 
    404     base::FilePath save_name = save_package->GetSuggestedNameForSaveAs(
    405         kSuggestedSaveNames[i].ensure_html_extension,
    406         std::string(), std::string());
    407     EXPECT_EQ(kSuggestedSaveNames[i].expected_name, save_name.value()) <<
    408         "Test case " << i;
    409   }
    410 }
    411 
    412 static const base::FilePath::CharType* kTestDir =
    413     FILE_PATH_LITERAL("save_page");
    414 
    415 // GetUrlToBeSaved method should return correct url to be saved.
    416 TEST_F(SavePackageTest, TestGetUrlToBeSaved) {
    417   base::FilePath file_name(FILE_PATH_LITERAL("a.htm"));
    418   GURL url = net::URLRequestMockHTTPJob::GetMockUrl(
    419       base::FilePath(kTestDir).Append(file_name));
    420   NavigateAndCommit(url);
    421   EXPECT_EQ(url, GetUrlToBeSaved());
    422 }
    423 
    424 // GetUrlToBeSaved method sould return actual url to be saved,
    425 // instead of the displayed url used to view source of a page.
    426 // Ex:GetUrlToBeSaved method should return http://www.google.com
    427 // when user types view-source:http://www.google.com
    428 TEST_F(SavePackageTest, TestGetUrlToBeSavedViewSource) {
    429   base::FilePath file_name(FILE_PATH_LITERAL("a.htm"));
    430   GURL mock_url = net::URLRequestMockHTTPJob::GetMockUrl(
    431       base::FilePath(kTestDir).Append(file_name));
    432   GURL view_source_url =
    433       GURL(kViewSourceScheme + std::string(":") + mock_url.spec());
    434   GURL actual_url = net::URLRequestMockHTTPJob::GetMockUrl(
    435       base::FilePath(kTestDir).Append(file_name));
    436   NavigateAndCommit(view_source_url);
    437   EXPECT_EQ(actual_url, GetUrlToBeSaved());
    438   EXPECT_EQ(view_source_url, contents()->GetLastCommittedURL());
    439 }
    440 
    441 }  // namespace content
    442