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
33namespace {
34
35const 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.
442TEST(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
498const 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
573TEST(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