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.
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"
22 #define FPL FILE_PATH_LITERAL
23 #define HTML_EXTENSION ".html"
25 #define FPL_HTML_EXTENSION L".html"
27 #define FPL_HTML_EXTENSION ".html"
32 // This constant copied from save_package.cc.
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
;
41 // Used to make long filenames.
42 std::string
long_file_name(
43 "EFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz01234567"
44 "89ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz012345"
45 "6789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0123"
46 "456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789a");
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
)
56 for (base::FilePath::StringType::size_type i
= l_paren_index
+ 1;
57 i
!= r_paren_index
; ++i
) {
58 if (!IsAsciiDigit(filename
[i
]))
67 class SavePackageTest
: public RenderViewHostImplTestHarness
{
69 bool GetGeneratedFilename(bool need_success_generate_filename
,
70 const std::string
& disposition
,
71 const std::string
& url
,
73 base::FilePath::StringType
* generated_name
) {
74 SavePackage
* save_package
;
75 if (need_success_generate_filename
)
76 save_package
= save_package_success_
.get();
78 save_package
= save_package_fail_
.get();
79 return save_package
->GenerateFileName(disposition
, GURL(url
), need_htm_ext
,
83 base::FilePath
EnsureHtmlExtension(const base::FilePath
& name
) {
84 return SavePackage::EnsureHtmlExtension(name
);
87 base::FilePath
EnsureMimeExtension(const base::FilePath
& name
,
88 const std::string
& content_mime_type
) {
89 return SavePackage::EnsureMimeExtension(name
, content_mime_type
);
92 GURL
GetUrlToBeSaved() {
93 return save_package_success_
->GetUrlToBeSaved();
97 void SetUp() override
{
98 RenderViewHostImplTestHarness::SetUp();
100 // Do the initialization in SetUp so contents() is initialized by
101 // RenderViewHostImplTestHarness::SetUp.
102 ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir());
104 save_package_success_
= new SavePackage(contents(),
105 temp_dir_
.path().AppendASCII("testfile" HTML_EXTENSION
),
106 temp_dir_
.path().AppendASCII("testfile_files"));
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());
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"));
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_
;
126 base::ScopedTempDir temp_dir_
;
129 static const struct {
130 const char* disposition
;
132 const base::FilePath::CharType
* expected_name
;
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.
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},
142 // No duplicate occurs.
143 {"filename=1.css", "http://www.savepage.com", FPL("1.css"), false},
145 // No duplicate occurs.
146 {"filename=1.js", "http://www.savepage.com", FPL("1.js"), false},
148 // Append numbers for duplicated names.
149 {"filename=1.css", "http://www.savepage.com", FPL("1(1).css"), false},
151 // No duplicate occurs.
152 {"filename=1(1).js", "http://www.savepage.com", FPL("1(1).js"), false},
154 // Append numbers for duplicated names.
155 {"filename=1.css", "http://www.savepage.com", FPL("1(2).css"), false},
157 // Change number for duplicated names.
158 {"filename=1(1).css", "http://www.savepage.com", FPL("1(3).css"), false},
160 // No duplicate occurs.
161 {"filename=1(11).css", "http://www.savepage.com", FPL("1(11).css"), false},
163 // Test for case-insensitive file names.
164 {"filename=readme.txt", "http://www.savepage.com",
165 FPL("readme.txt"), false},
167 {"filename=readme.TXT", "http://www.savepage.com",
168 FPL("readme(1).TXT"), false},
170 {"filename=READme.txt", "http://www.savepage.com",
171 FPL("readme(2).txt"), false},
173 {"filename=Readme(1).txt", "http://www.savepage.com",
174 FPL("readme(3).txt"), false},
177 TEST_F(SavePackageTest
, TestSuccessfullyGenerateSavePackageFilename
) {
178 for (size_t i
= 0; i
< arraysize(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
,
186 EXPECT_EQ(kGeneratedFiles
[i
].expected_name
, file_name
);
190 TEST_F(SavePackageTest
, TestUnSuccessfullyGenerateSavePackageFilename
) {
191 for (size_t i
= 0; i
< arraysize(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
,
202 // Crashing on Windows, see http://crbug.com/79365
204 #define MAYBE_TestLongSavePackageFilename DISABLED_TestLongSavePackageFilename
206 #define MAYBE_TestLongSavePackageFilename 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
;
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
));
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
));
225 // Test that the filename is successfully shortened to fit, and gets a
226 // different ordinal appended.
227 base::FilePath::StringType filename2
;
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
);
235 // Crashing on Windows, see http://crbug.com/79365
237 #define MAYBE_TestLongSafePureFilename DISABLED_TestLongSafePureFilename
239 #define MAYBE_TestLongSafePureFilename 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
=
246 base::ASCIIToWide(long_file_name
);
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
,
255 EXPECT_TRUE(filename
.length() <= kMaxFileNameLength
-ext
.length());
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
},
277 // Crashing on Windows, see http://crbug.com/79365
279 #define MAYBE_TestEnsureHtmlExtension DISABLED_TestEnsureHtmlExtension
281 #define MAYBE_TestEnsureHtmlExtension TestEnsureHtmlExtension
283 TEST_F(SavePackageTest
, MAYBE_TestEnsureHtmlExtension
) {
284 for (size_t i
= 0; i
< arraysize(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
;
294 // Crashing on Windows, see http://crbug.com/79365
296 #define MAYBE_TestEnsureMimeExtension DISABLED_TestEnsureMimeExtension
298 #define MAYBE_TestEnsureMimeExtension 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" },
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" },
329 for (uint32 i
= 0; i
< arraysize(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
;
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".
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.
354 base::ASCIIToUTF16("A page title"),
355 FPL("A page title") FPL_HTML_EXTENSION
,
358 // Extension is preserved.
360 base::ASCIIToUTF16("A page title with.ext"),
361 FPL("A page title with.ext"),
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"),
370 // If the title matches the URL, but there is no "filename" component,
373 base::ASCIIToUTF16("foo.com"),
377 // Make sure fuzzy matching works.
378 { "http://foo.com/bar",
379 base::ASCIIToUTF16("foo.com/bar"),
383 // A URL-like title that does not match the title is respected in full.
385 base::ASCIIToUTF16("http://www.foo.com/path/title.txt"),
386 FPL("http www.foo.com path title.txt"),
391 // Crashing on Windows, see http://crbug.com/79365
393 #define MAYBE_TestSuggestedSaveNames DISABLED_TestSuggestedSaveNames
395 #define MAYBE_TestSuggestedSaveNames 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
;
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()) <<
412 static const base::FilePath::CharType
* kTestDir
=
413 FILE_PATH_LITERAL("save_page");
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());
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());
441 } // namespace content