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