Home | History | Annotate | Download | only in download
      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