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