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