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 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: 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 83 base::FilePath EnsureHtmlExtension(const base::FilePath& name) { 84 return SavePackage::EnsureHtmlExtension(name); 85 } 86 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 92 GURL GetUrlToBeSaved() { 93 return save_package_success_->GetUrlToBeSaved(); 94 } 95 96 protected: 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 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 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 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 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 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 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 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. 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 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