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 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: 66 SavePackageTest() { 67 } 68 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 83 FilePath EnsureHtmlExtension(const FilePath& name) { 84 return SavePackage::EnsureHtmlExtension(name); 85 } 86 87 FilePath EnsureMimeExtension(const 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 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 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 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 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 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 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 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 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. 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 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