• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • 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 
HasOrdinalNumber(const base::FilePath::StringType & filename)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:
GetGeneratedFilename(bool need_success_generate_filename,const std::string & disposition,const std::string & url,bool need_htm_ext,base::FilePath::StringType * generated_name)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 
EnsureHtmlExtension(const base::FilePath & name)84   base::FilePath EnsureHtmlExtension(const base::FilePath& name) {
85     return SavePackage::EnsureHtmlExtension(name);
86   }
87 
EnsureMimeExtension(const base::FilePath & name,const std::string & content_mime_type)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 
GetUrlToBeSaved()93   GURL GetUrlToBeSaved() {
94     return save_package_success_->GetUrlToBeSaved();
95   }
96 
97  protected:
SetUp()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 
TEST_F(SavePackageTest,TestSuccessfullyGenerateSavePackageFilename)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 
TEST_F(SavePackageTest,TestUnSuccessfullyGenerateSavePackageFilename)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
TEST_F(SavePackageTest,MAYBE_TestLongSavePackageFilename)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
TEST_F(SavePackageTest,MAYBE_TestLongSafePureFilename)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
TEST_F(SavePackageTest,MAYBE_TestEnsureHtmlExtension)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
TEST_F(SavePackageTest,MAYBE_TestEnsureMimeExtension)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
TEST_F(SavePackageTest,MAYBE_TestSuggestedSaveNames)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.
TEST_F(SavePackageTest,TestGetUrlToBeSaved)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
TEST_F(SavePackageTest,TestGetUrlToBeSavedViewSource)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