1 // Copyright (c) 2010 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 "chrome/browser/download/download_util.h" 6 7 #if defined(OS_POSIX) && !defined(OS_MACOSX) 8 #include <locale.h> 9 #endif 10 11 #include "base/string_util.h" 12 #include "base/test/test_file_util.h" 13 #include "googleurl/src/gurl.h" 14 #include "testing/gtest/include/gtest/gtest.h" 15 16 #if defined(OS_WIN) 17 #define JPEG_EXT L".jpg" 18 #define HTML_EXT L".htm" 19 #define TXT_EXT L".txt" 20 #define TAR_EXT L".tar" 21 #elif defined(OS_MACOSX) 22 #define JPEG_EXT L".jpeg" 23 #define HTML_EXT L".html" 24 #define TXT_EXT L".txt" 25 #define TAR_EXT L".tar" 26 #else 27 #define JPEG_EXT L".jpg" 28 #define HTML_EXT L".html" 29 #define TXT_EXT L".txt" 30 #define TAR_EXT L".tar" 31 #endif 32 33 namespace { 34 35 const struct { 36 const char* disposition; 37 const char* url; 38 const char* mime_type; 39 const wchar_t* expected_name; 40 } kGenerateFileNameTestCases[] = { 41 // No 'filename' keyword in the disposition, use the URL 42 {"a_file_name.txt", 43 "http://www.evil.com/my_download.txt", 44 "text/plain", 45 L"my_download.txt"}, 46 47 // Disposition has relative paths, remove directory separators 48 {"filename=../../../../././../a_file_name.txt", 49 "http://www.evil.com/my_download.txt", 50 "text/plain", 51 L"_.._.._.._._._.._a_file_name.txt"}, 52 53 // Disposition has parent directories, remove directory separators 54 {"filename=dir1/dir2/a_file_name.txt", 55 "http://www.evil.com/my_download.txt", 56 "text/plain", 57 L"dir1_dir2_a_file_name.txt"}, 58 59 // Disposition has relative paths, remove directory separators 60 {"filename=..\\..\\..\\..\\.\\.\\..\\a_file_name.txt", 61 "http://www.evil.com/my_download.txt", 62 "text/plain", 63 L"_.._.._.._._._.._a_file_name.txt"}, 64 65 // Disposition has parent directories, remove directory separators 66 {"filename=dir1\\dir2\\a_file_name.txt", 67 "http://www.evil.com/my_download.txt", 68 "text/plain", 69 L"dir1_dir2_a_file_name.txt"}, 70 71 // No useful information in disposition or URL, use default 72 {"", "http://www.truncated.com/path/", "text/plain", 73 L"download" TXT_EXT 74 }, 75 76 // A normal avi should get .avi and not .avi.avi 77 {"", "https://blah.google.com/misc/2.avi", "video/x-msvideo", L"2.avi"}, 78 79 // Spaces in the disposition file name 80 {"filename=My Downloaded File.exe", 81 "http://www.frontpagehacker.com/a_download.exe", 82 "application/octet-stream", 83 L"My Downloaded File.exe"}, 84 85 {"filename=my-cat", 86 "http://www.example.com/my-cat", 87 "image/jpeg", 88 L"my-cat" JPEG_EXT 89 }, 90 91 {"filename=my-cat", 92 "http://www.example.com/my-cat", 93 "text/plain", 94 L"my-cat.txt"}, 95 96 {"filename=my-cat", 97 "http://www.example.com/my-cat", 98 "text/html", 99 L"my-cat" HTML_EXT 100 }, 101 102 {"filename=my-cat", 103 "http://www.example.com/my-cat", 104 "dance/party", 105 L"my-cat"}, 106 107 {"filename=my-cat.jpg", 108 "http://www.example.com/my-cat.jpg", 109 "text/plain", 110 L"my-cat.jpg"}, 111 112 // .exe tests. 113 #if defined(OS_WIN) 114 {"filename=evil.exe", 115 "http://www.goodguy.com/evil.exe", 116 "image/jpeg", 117 L"evil.exe"}, 118 119 {"filename=ok.exe", 120 "http://www.goodguy.com/ok.exe", 121 "binary/octet-stream", 122 L"ok.exe"}, 123 124 {"filename=evil.dll", 125 "http://www.goodguy.com/evil.dll", 126 "dance/party", 127 L"evil.dll"}, 128 129 {"filename=evil", 130 "http://www.goodguy.com/evil.exe", 131 "application/rss+xml", 132 L"evil"}, 133 134 // Test truncation of trailing dots and spaces 135 {"filename=evil.exe ", 136 "http://www.goodguy.com/evil.exe ", 137 "binary/octet-stream", 138 L"evil.exe"}, 139 140 {"filename=evil.exe.", 141 "http://www.goodguy.com/evil.exe.", 142 "binary/octet-stream", 143 L"evil.exe"}, 144 145 {"filename=evil.exe. . .", 146 "http://www.goodguy.com/evil.exe. . .", 147 "binary/octet-stream", 148 L"evil.exe"}, 149 150 {"filename=evil.", 151 "http://www.goodguy.com/evil.", 152 "binary/octet-stream", 153 L"evil"}, 154 155 {"filename=. . . . .", 156 "http://www.goodguy.com/. . . . .", 157 "binary/octet-stream", 158 L"download"}, 159 160 #endif // OS_WIN 161 162 {"filename=utils.js", 163 "http://www.goodguy.com/utils.js", 164 "application/x-javascript", 165 L"utils.js"}, 166 167 {"filename=contacts.js", 168 "http://www.goodguy.com/contacts.js", 169 "application/json", 170 L"contacts.js"}, 171 172 {"filename=utils.js", 173 "http://www.goodguy.com/utils.js", 174 "text/javascript", 175 L"utils.js"}, 176 177 {"filename=utils.js", 178 "http://www.goodguy.com/utils.js", 179 "text/javascript;version=2", 180 L"utils.js"}, 181 182 {"filename=utils.js", 183 "http://www.goodguy.com/utils.js", 184 "application/ecmascript", 185 L"utils.js"}, 186 187 {"filename=utils.js", 188 "http://www.goodguy.com/utils.js", 189 "application/ecmascript;version=4", 190 L"utils.js"}, 191 192 {"filename=program.exe", 193 "http://www.goodguy.com/program.exe", 194 "application/foo-bar", 195 L"program.exe"}, 196 197 {"filename=../foo.txt", 198 "http://www.evil.com/../foo.txt", 199 "text/plain", 200 L"_foo.txt"}, 201 202 {"filename=..\\foo.txt", 203 "http://www.evil.com/..\\foo.txt", 204 "text/plain", 205 L"_foo.txt" 206 }, 207 208 {"filename=.hidden", 209 "http://www.evil.com/.hidden", 210 "text/plain", 211 L"hidden" TXT_EXT 212 }, 213 214 {"filename=trailing.", 215 "http://www.evil.com/trailing.", 216 "dance/party", 217 L"trailing" 218 }, 219 220 {"filename=trailing.", 221 "http://www.evil.com/trailing.", 222 "text/plain", 223 L"trailing" TXT_EXT 224 }, 225 226 {"filename=.", 227 "http://www.evil.com/.", 228 "dance/party", 229 L"download"}, 230 231 {"filename=..", 232 "http://www.evil.com/..", 233 "dance/party", 234 L"download"}, 235 236 {"filename=...", 237 "http://www.evil.com/...", 238 "dance/party", 239 L"download"}, 240 241 // Note that this one doesn't have "filename=" on it. 242 {"a_file_name.txt", 243 "http://www.evil.com/", 244 "image/jpeg", 245 L"download" JPEG_EXT 246 }, 247 248 {"filename=", 249 "http://www.evil.com/", 250 "image/jpeg", 251 L"download" JPEG_EXT 252 }, 253 254 {"filename=simple", 255 "http://www.example.com/simple", 256 "application/octet-stream", 257 L"simple"}, 258 259 {"filename=COM1", 260 "http://www.goodguy.com/COM1", 261 "application/foo-bar", 262 #if defined(OS_WIN) 263 L"_COM1" 264 #else 265 L"COM1" 266 #endif 267 }, 268 269 {"filename=COM4.txt", 270 "http://www.goodguy.com/COM4.txt", 271 "text/plain", 272 #if defined(OS_WIN) 273 L"_COM4.txt" 274 #else 275 L"COM4.txt" 276 #endif 277 }, 278 279 {"filename=lpt1.TXT", 280 "http://www.goodguy.com/lpt1.TXT", 281 "text/plain", 282 #if defined(OS_WIN) 283 L"_lpt1.TXT" 284 #else 285 L"lpt1.TXT" 286 #endif 287 }, 288 289 {"filename=clock$.txt", 290 "http://www.goodguy.com/clock$.txt", 291 "text/plain", 292 #if defined(OS_WIN) 293 L"_clock$.txt" 294 #else 295 L"clock$.txt" 296 #endif 297 }, 298 299 {"filename=mycom1.foo", 300 "http://www.goodguy.com/mycom1.foo", 301 "text/plain", 302 L"mycom1.foo"}, 303 304 {"filename=Setup.exe.local", 305 "http://www.badguy.com/Setup.exe.local", 306 "application/foo-bar", 307 #if defined(OS_WIN) 308 L"Setup.exe.download" 309 #else 310 L"Setup.exe.local" 311 #endif 312 }, 313 314 {"filename=Setup.exe.local.local", 315 "http://www.badguy.com/Setup.exe.local", 316 "application/foo-bar", 317 #if defined(OS_WIN) 318 L"Setup.exe.local.download" 319 #else 320 L"Setup.exe.local.local" 321 #endif 322 }, 323 324 {"filename=Setup.exe.lnk", 325 "http://www.badguy.com/Setup.exe.lnk", 326 "application/foo-bar", 327 #if defined(OS_WIN) 328 L"Setup.exe.download" 329 #else 330 L"Setup.exe.lnk" 331 #endif 332 }, 333 334 {"filename=Desktop.ini", 335 "http://www.badguy.com/Desktop.ini", 336 "application/foo-bar", 337 #if defined(OS_WIN) 338 L"_Desktop.ini" 339 #else 340 L"Desktop.ini" 341 #endif 342 }, 343 344 {"filename=Thumbs.db", 345 "http://www.badguy.com/Thumbs.db", 346 "application/foo-bar", 347 #if defined(OS_WIN) 348 L"_Thumbs.db" 349 #else 350 L"Thumbs.db" 351 #endif 352 }, 353 354 {"filename=source.jpg", 355 "http://www.hotmail.com", 356 "application/x-javascript", 357 L"source.jpg" 358 }, 359 360 // NetUtilTest.{GetSuggestedFilename, GetFileNameFromCD} test these 361 // more thoroughly. Tested below are a small set of samples. 362 {"attachment; filename=\"%EC%98%88%EC%88%A0%20%EC%98%88%EC%88%A0.jpg\"", 363 "http://www.examples.com/", 364 "image/jpeg", 365 L"\uc608\uc220 \uc608\uc220.jpg"}, 366 367 {"attachment; name=abc de.pdf", 368 "http://www.examples.com/q.cgi?id=abc", 369 "application/octet-stream", 370 L"abc de.pdf"}, 371 372 {"filename=\"=?EUC-JP?Q?=B7=DD=BD=D13=2Epng?=\"", 373 "http://www.example.com/path", 374 "image/png", 375 L"\x82b8\x8853" L"3.png"}, 376 377 // The following two have invalid CD headers and filenames come 378 // from the URL. 379 {"attachment; filename==?iiso88591?Q?caf=EG?=", 380 "http://www.example.com/test%20123", 381 "image/jpeg", 382 L"test 123" JPEG_EXT 383 }, 384 385 {"malformed_disposition", 386 "http://www.google.com/%EC%98%88%EC%88%A0%20%EC%98%88%EC%88%A0.jpg", 387 "image/jpeg", 388 L"\uc608\uc220 \uc608\uc220.jpg"}, 389 390 // Invalid C-D. No filename from URL. Falls back to 'download'. 391 {"attachment; filename==?iso88591?Q?caf=E3?", 392 "http://www.google.com/path1/path2/", 393 "image/jpeg", 394 L"download" JPEG_EXT 395 }, 396 397 // Issue=5772. 398 {"", 399 "http://www.example.com/foo.tar.gz", 400 "application/x-tar", 401 L"foo.tar.gz"}, 402 403 // Issue=52250. 404 {"", 405 "http://www.example.com/foo.tgz", 406 "application/x-tar", 407 L"foo.tgz"}, 408 409 // Issue=7337. 410 {"", 411 "http://maged.lordaeron.org/blank.reg", 412 "text/x-registry", 413 L"blank.reg"}, 414 415 {"", 416 "http://www.example.com/bar.tar", 417 "application/x-tar", 418 L"bar.tar"}, 419 420 {"", 421 "http://www.example.com/bar.bogus", 422 "application/x-tar", 423 L"bar.bogus" 424 }, 425 426 // http://code.google.com/p/chromium/issues/detail?id=20337 427 {"filename=.download.txt", 428 "http://www.example.com/.download.txt", 429 "text/plain", 430 L"download.txt"}, 431 432 // Issue=56855. 433 {"", 434 "http://www.example.com/bar.sh", 435 "application/x-sh", 436 L"bar.sh" 437 }, 438 }; 439 440 // Tests to ensure that the file names we generate from hints from the server 441 // (content-disposition, URL name, etc) don't cause security holes. 442 TEST(DownloadUtilTest, GenerateFileName) { 443 #if defined(OS_POSIX) && !defined(OS_MACOSX) 444 // This test doesn't run when the locale is not UTF-8 because some of the 445 // string conversions fail. This is OK (we have the default value) but they 446 // don't match our expectations. 447 std::string locale = setlocale(LC_CTYPE, NULL); 448 StringToLowerASCII(&locale); 449 EXPECT_NE(std::string::npos, locale.find("utf-8")) 450 << "Your locale (" << locale << ") must be set to UTF-8 " 451 << "for this test to pass!"; 452 #endif 453 454 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kGenerateFileNameTestCases); ++i) { 455 FilePath generated_name; 456 download_util::GenerateFileName(GURL(kGenerateFileNameTestCases[i].url), 457 kGenerateFileNameTestCases[i].disposition, 458 "", 459 kGenerateFileNameTestCases[i].mime_type, 460 &generated_name); 461 EXPECT_EQ(kGenerateFileNameTestCases[i].expected_name, 462 file_util::FilePathAsWString(generated_name)) << i; 463 } 464 465 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kGenerateFileNameTestCases); ++i) { 466 FilePath generated_name; 467 download_util::GenerateFileName(GURL(kGenerateFileNameTestCases[i].url), 468 kGenerateFileNameTestCases[i].disposition, 469 "GBK", 470 kGenerateFileNameTestCases[i].mime_type, 471 &generated_name); 472 EXPECT_EQ(kGenerateFileNameTestCases[i].expected_name, 473 file_util::FilePathAsWString(generated_name)) << i; 474 } 475 476 // A couple of cases with raw 8bit characters in C-D. 477 { 478 FilePath generated_name; 479 download_util::GenerateFileName(GURL("http://www.example.com/images?id=3"), 480 "attachment; filename=caf\xc3\xa9.png", 481 "iso-8859-1", 482 "image/png", 483 &generated_name); 484 EXPECT_EQ(L"caf\u00e9.png", file_util::FilePathAsWString(generated_name)); 485 } 486 487 { 488 FilePath generated_name; 489 download_util::GenerateFileName(GURL("http://www.example.com/images?id=3"), 490 "attachment; filename=caf\xe5.png", 491 "windows-1253", 492 "image/png", 493 &generated_name); 494 EXPECT_EQ(L"caf\u03b5.png", file_util::FilePathAsWString(generated_name)); 495 } 496 } 497 498 const struct { 499 const FilePath::CharType* path; 500 const char* mime_type; 501 const FilePath::CharType* expected_path; 502 } kSafeFilenameCases[] = { 503 #if defined(OS_WIN) 504 { FILE_PATH_LITERAL("C:\\foo\\bar.htm"), 505 "text/html", 506 FILE_PATH_LITERAL("C:\\foo\\bar.htm") }, 507 { FILE_PATH_LITERAL("C:\\foo\\bar.html"), 508 "text/html", 509 FILE_PATH_LITERAL("C:\\foo\\bar.html") }, 510 { FILE_PATH_LITERAL("C:\\foo\\bar"), 511 "text/html", 512 FILE_PATH_LITERAL("C:\\foo\\bar.htm") }, 513 514 { FILE_PATH_LITERAL("C:\\bar.html"), 515 "image/png", 516 FILE_PATH_LITERAL("C:\\bar.html") }, 517 { FILE_PATH_LITERAL("C:\\bar"), 518 "image/png", 519 FILE_PATH_LITERAL("C:\\bar.png") }, 520 521 { FILE_PATH_LITERAL("C:\\foo\\bar.exe"), 522 "text/html", 523 FILE_PATH_LITERAL("C:\\foo\\bar.exe") }, 524 { FILE_PATH_LITERAL("C:\\foo\\bar.exe"), 525 "image/gif", 526 FILE_PATH_LITERAL("C:\\foo\\bar.exe") }, 527 528 { FILE_PATH_LITERAL("C:\\foo\\google.com"), 529 "text/html", 530 FILE_PATH_LITERAL("C:\\foo\\google.com") }, 531 532 { FILE_PATH_LITERAL("C:\\foo\\con.htm"), 533 "text/html", 534 FILE_PATH_LITERAL("C:\\foo\\_con.htm") }, 535 { FILE_PATH_LITERAL("C:\\foo\\con"), 536 "text/html", 537 FILE_PATH_LITERAL("C:\\foo\\_con.htm") }, 538 #else // !defined(OS_WIN) 539 { FILE_PATH_LITERAL("/foo/bar.htm"), 540 "text/html", 541 FILE_PATH_LITERAL("/foo/bar.htm") }, 542 { FILE_PATH_LITERAL("/foo/bar.html"), 543 "text/html", 544 FILE_PATH_LITERAL("/foo/bar.html") }, 545 { FILE_PATH_LITERAL("/foo/bar"), 546 "text/html", 547 FILE_PATH_LITERAL("/foo/bar.html") }, 548 549 { FILE_PATH_LITERAL("/bar.html"), 550 "image/png", 551 FILE_PATH_LITERAL("/bar.html") }, 552 { FILE_PATH_LITERAL("/bar"), 553 "image/png", 554 FILE_PATH_LITERAL("/bar.png") }, 555 556 { FILE_PATH_LITERAL("/foo/bar.exe"), 557 "image/gif", 558 FILE_PATH_LITERAL("/foo/bar.exe") }, 559 560 { FILE_PATH_LITERAL("/foo/google.com"), 561 "text/html", 562 FILE_PATH_LITERAL("/foo/google.com") }, 563 564 { FILE_PATH_LITERAL("/foo/con.htm"), 565 "text/html", 566 FILE_PATH_LITERAL("/foo/con.htm") }, 567 { FILE_PATH_LITERAL("/foo/con"), 568 "text/html", 569 FILE_PATH_LITERAL("/foo/con.html") }, 570 #endif // !defined(OS_WIN) 571 }; 572 573 TEST(DownloadUtilTest, GenerateSafeFileName) { 574 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSafeFilenameCases); ++i) { 575 FilePath path(kSafeFilenameCases[i].path); 576 download_util::GenerateSafeFileName(kSafeFilenameCases[i].mime_type, &path); 577 EXPECT_EQ(kSafeFilenameCases[i].expected_path, path.value()) << i; 578 } 579 } 580 581 } // namespace 582 583