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