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