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 #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 | |
OLD | NEW |