| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/download/download_util.h" |
| |
| #if defined(OS_POSIX) && !defined(OS_MACOSX) |
| #include <locale.h> |
| #endif |
| |
| #include "base/string_util.h" |
| #include "base/test/test_file_util.h" |
| #include "googleurl/src/gurl.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #if defined(OS_WIN) |
| #define JPEG_EXT L".jpg" |
| #define HTML_EXT L".htm" |
| #define TXT_EXT L".txt" |
| #define TAR_EXT L".tar" |
| #elif defined(OS_MACOSX) |
| #define JPEG_EXT L".jpeg" |
| #define HTML_EXT L".html" |
| #define TXT_EXT L".txt" |
| #define TAR_EXT L".tar" |
| #else |
| #define JPEG_EXT L".jpg" |
| #define HTML_EXT L".html" |
| #define TXT_EXT L".txt" |
| #define TAR_EXT L".tar" |
| #endif |
| |
| namespace { |
| |
| const struct { |
| const char* disposition; |
| const char* url; |
| const char* mime_type; |
| const wchar_t* expected_name; |
| } kGenerateFileNameTestCases[] = { |
| // No 'filename' keyword in the disposition, use the URL |
| {"a_file_name.txt", |
| "http://www.evil.com/my_download.txt", |
| "text/plain", |
| L"my_download.txt"}, |
| |
| // Disposition has relative paths, remove directory separators |
| {"filename=../../../../././../a_file_name.txt", |
| "http://www.evil.com/my_download.txt", |
| "text/plain", |
| L"_.._.._.._._._.._a_file_name.txt"}, |
| |
| // Disposition has parent directories, remove directory separators |
| {"filename=dir1/dir2/a_file_name.txt", |
| "http://www.evil.com/my_download.txt", |
| "text/plain", |
| L"dir1_dir2_a_file_name.txt"}, |
| |
| // Disposition has relative paths, remove directory separators |
| {"filename=..\\..\\..\\..\\.\\.\\..\\a_file_name.txt", |
| "http://www.evil.com/my_download.txt", |
| "text/plain", |
| L"_.._.._.._._._.._a_file_name.txt"}, |
| |
| // Disposition has parent directories, remove directory separators |
| {"filename=dir1\\dir2\\a_file_name.txt", |
| "http://www.evil.com/my_download.txt", |
| "text/plain", |
| L"dir1_dir2_a_file_name.txt"}, |
| |
| // No useful information in disposition or URL, use default |
| {"", "http://www.truncated.com/path/", "text/plain", |
| L"download" TXT_EXT |
| }, |
| |
| // A normal avi should get .avi and not .avi.avi |
| {"", "https://blah.google.com/misc/2.avi", "video/x-msvideo", L"2.avi"}, |
| |
| // Spaces in the disposition file name |
| {"filename=My Downloaded File.exe", |
| "http://www.frontpagehacker.com/a_download.exe", |
| "application/octet-stream", |
| L"My Downloaded File.exe"}, |
| |
| {"filename=my-cat", |
| "http://www.example.com/my-cat", |
| "image/jpeg", |
| L"my-cat" JPEG_EXT |
| }, |
| |
| {"filename=my-cat", |
| "http://www.example.com/my-cat", |
| "text/plain", |
| L"my-cat.txt"}, |
| |
| {"filename=my-cat", |
| "http://www.example.com/my-cat", |
| "text/html", |
| L"my-cat" HTML_EXT |
| }, |
| |
| {"filename=my-cat", |
| "http://www.example.com/my-cat", |
| "dance/party", |
| L"my-cat"}, |
| |
| {"filename=my-cat.jpg", |
| "http://www.example.com/my-cat.jpg", |
| "text/plain", |
| L"my-cat.jpg"}, |
| |
| // .exe tests. |
| #if defined(OS_WIN) |
| {"filename=evil.exe", |
| "http://www.goodguy.com/evil.exe", |
| "image/jpeg", |
| L"evil.exe"}, |
| |
| {"filename=ok.exe", |
| "http://www.goodguy.com/ok.exe", |
| "binary/octet-stream", |
| L"ok.exe"}, |
| |
| {"filename=evil.dll", |
| "http://www.goodguy.com/evil.dll", |
| "dance/party", |
| L"evil.dll"}, |
| |
| {"filename=evil", |
| "http://www.goodguy.com/evil.exe", |
| "application/rss+xml", |
| L"evil"}, |
| |
| // Test truncation of trailing dots and spaces |
| {"filename=evil.exe ", |
| "http://www.goodguy.com/evil.exe ", |
| "binary/octet-stream", |
| L"evil.exe"}, |
| |
| {"filename=evil.exe.", |
| "http://www.goodguy.com/evil.exe.", |
| "binary/octet-stream", |
| L"evil.exe"}, |
| |
| {"filename=evil.exe. . .", |
| "http://www.goodguy.com/evil.exe. . .", |
| "binary/octet-stream", |
| L"evil.exe"}, |
| |
| {"filename=evil.", |
| "http://www.goodguy.com/evil.", |
| "binary/octet-stream", |
| L"evil"}, |
| |
| {"filename=. . . . .", |
| "http://www.goodguy.com/. . . . .", |
| "binary/octet-stream", |
| L"download"}, |
| |
| #endif // OS_WIN |
| |
| {"filename=utils.js", |
| "http://www.goodguy.com/utils.js", |
| "application/x-javascript", |
| L"utils.js"}, |
| |
| {"filename=contacts.js", |
| "http://www.goodguy.com/contacts.js", |
| "application/json", |
| L"contacts.js"}, |
| |
| {"filename=utils.js", |
| "http://www.goodguy.com/utils.js", |
| "text/javascript", |
| L"utils.js"}, |
| |
| {"filename=utils.js", |
| "http://www.goodguy.com/utils.js", |
| "text/javascript;version=2", |
| L"utils.js"}, |
| |
| {"filename=utils.js", |
| "http://www.goodguy.com/utils.js", |
| "application/ecmascript", |
| L"utils.js"}, |
| |
| {"filename=utils.js", |
| "http://www.goodguy.com/utils.js", |
| "application/ecmascript;version=4", |
| L"utils.js"}, |
| |
| {"filename=program.exe", |
| "http://www.goodguy.com/program.exe", |
| "application/foo-bar", |
| L"program.exe"}, |
| |
| {"filename=../foo.txt", |
| "http://www.evil.com/../foo.txt", |
| "text/plain", |
| L"_foo.txt"}, |
| |
| {"filename=..\\foo.txt", |
| "http://www.evil.com/..\\foo.txt", |
| "text/plain", |
| L"_foo.txt" |
| }, |
| |
| {"filename=.hidden", |
| "http://www.evil.com/.hidden", |
| "text/plain", |
| L"hidden" TXT_EXT |
| }, |
| |
| {"filename=trailing.", |
| "http://www.evil.com/trailing.", |
| "dance/party", |
| L"trailing" |
| }, |
| |
| {"filename=trailing.", |
| "http://www.evil.com/trailing.", |
| "text/plain", |
| L"trailing" TXT_EXT |
| }, |
| |
| {"filename=.", |
| "http://www.evil.com/.", |
| "dance/party", |
| L"download"}, |
| |
| {"filename=..", |
| "http://www.evil.com/..", |
| "dance/party", |
| L"download"}, |
| |
| {"filename=...", |
| "http://www.evil.com/...", |
| "dance/party", |
| L"download"}, |
| |
| // Note that this one doesn't have "filename=" on it. |
| {"a_file_name.txt", |
| "http://www.evil.com/", |
| "image/jpeg", |
| L"download" JPEG_EXT |
| }, |
| |
| {"filename=", |
| "http://www.evil.com/", |
| "image/jpeg", |
| L"download" JPEG_EXT |
| }, |
| |
| {"filename=simple", |
| "http://www.example.com/simple", |
| "application/octet-stream", |
| L"simple"}, |
| |
| {"filename=COM1", |
| "http://www.goodguy.com/COM1", |
| "application/foo-bar", |
| #if defined(OS_WIN) |
| L"_COM1" |
| #else |
| L"COM1" |
| #endif |
| }, |
| |
| {"filename=COM4.txt", |
| "http://www.goodguy.com/COM4.txt", |
| "text/plain", |
| #if defined(OS_WIN) |
| L"_COM4.txt" |
| #else |
| L"COM4.txt" |
| #endif |
| }, |
| |
| {"filename=lpt1.TXT", |
| "http://www.goodguy.com/lpt1.TXT", |
| "text/plain", |
| #if defined(OS_WIN) |
| L"_lpt1.TXT" |
| #else |
| L"lpt1.TXT" |
| #endif |
| }, |
| |
| {"filename=clock$.txt", |
| "http://www.goodguy.com/clock$.txt", |
| "text/plain", |
| #if defined(OS_WIN) |
| L"_clock$.txt" |
| #else |
| L"clock$.txt" |
| #endif |
| }, |
| |
| {"filename=mycom1.foo", |
| "http://www.goodguy.com/mycom1.foo", |
| "text/plain", |
| L"mycom1.foo"}, |
| |
| {"filename=Setup.exe.local", |
| "http://www.badguy.com/Setup.exe.local", |
| "application/foo-bar", |
| #if defined(OS_WIN) |
| L"Setup.exe.download" |
| #else |
| L"Setup.exe.local" |
| #endif |
| }, |
| |
| {"filename=Setup.exe.local.local", |
| "http://www.badguy.com/Setup.exe.local", |
| "application/foo-bar", |
| #if defined(OS_WIN) |
| L"Setup.exe.local.download" |
| #else |
| L"Setup.exe.local.local" |
| #endif |
| }, |
| |
| {"filename=Setup.exe.lnk", |
| "http://www.badguy.com/Setup.exe.lnk", |
| "application/foo-bar", |
| #if defined(OS_WIN) |
| L"Setup.exe.download" |
| #else |
| L"Setup.exe.lnk" |
| #endif |
| }, |
| |
| {"filename=Desktop.ini", |
| "http://www.badguy.com/Desktop.ini", |
| "application/foo-bar", |
| #if defined(OS_WIN) |
| L"_Desktop.ini" |
| #else |
| L"Desktop.ini" |
| #endif |
| }, |
| |
| {"filename=Thumbs.db", |
| "http://www.badguy.com/Thumbs.db", |
| "application/foo-bar", |
| #if defined(OS_WIN) |
| L"_Thumbs.db" |
| #else |
| L"Thumbs.db" |
| #endif |
| }, |
| |
| {"filename=source.jpg", |
| "http://www.hotmail.com", |
| "application/x-javascript", |
| L"source.jpg" |
| }, |
| |
| // NetUtilTest.{GetSuggestedFilename, GetFileNameFromCD} test these |
| // more thoroughly. Tested below are a small set of samples. |
| {"attachment; filename=\"%EC%98%88%EC%88%A0%20%EC%98%88%EC%88%A0.jpg\"", |
| "http://www.examples.com/", |
| "image/jpeg", |
| L"\uc608\uc220 \uc608\uc220.jpg"}, |
| |
| {"attachment; name=abc de.pdf", |
| "http://www.examples.com/q.cgi?id=abc", |
| "application/octet-stream", |
| L"abc de.pdf"}, |
| |
| {"filename=\"=?EUC-JP?Q?=B7=DD=BD=D13=2Epng?=\"", |
| "http://www.example.com/path", |
| "image/png", |
| L"\x82b8\x8853" L"3.png"}, |
| |
| // The following two have invalid CD headers and filenames come |
| // from the URL. |
| {"attachment; filename==?iiso88591?Q?caf=EG?=", |
| "http://www.example.com/test%20123", |
| "image/jpeg", |
| L"test 123" JPEG_EXT |
| }, |
| |
| {"malformed_disposition", |
| "http://www.google.com/%EC%98%88%EC%88%A0%20%EC%98%88%EC%88%A0.jpg", |
| "image/jpeg", |
| L"\uc608\uc220 \uc608\uc220.jpg"}, |
| |
| // Invalid C-D. No filename from URL. Falls back to 'download'. |
| {"attachment; filename==?iso88591?Q?caf=E3?", |
| "http://www.google.com/path1/path2/", |
| "image/jpeg", |
| L"download" JPEG_EXT |
| }, |
| |
| // Issue=5772. |
| {"", |
| "http://www.example.com/foo.tar.gz", |
| "application/x-tar", |
| L"foo.tar.gz"}, |
| |
| // Issue=52250. |
| {"", |
| "http://www.example.com/foo.tgz", |
| "application/x-tar", |
| L"foo.tgz"}, |
| |
| // Issue=7337. |
| {"", |
| "http://maged.lordaeron.org/blank.reg", |
| "text/x-registry", |
| L"blank.reg"}, |
| |
| {"", |
| "http://www.example.com/bar.tar", |
| "application/x-tar", |
| L"bar.tar"}, |
| |
| {"", |
| "http://www.example.com/bar.bogus", |
| "application/x-tar", |
| L"bar.bogus" |
| }, |
| |
| // http://code.google.com/p/chromium/issues/detail?id=20337 |
| {"filename=.download.txt", |
| "http://www.example.com/.download.txt", |
| "text/plain", |
| L"download.txt"}, |
| |
| // Issue=56855. |
| {"", |
| "http://www.example.com/bar.sh", |
| "application/x-sh", |
| L"bar.sh" |
| }, |
| }; |
| |
| // Tests to ensure that the file names we generate from hints from the server |
| // (content-disposition, URL name, etc) don't cause security holes. |
| TEST(DownloadUtilTest, GenerateFileName) { |
| #if defined(OS_POSIX) && !defined(OS_MACOSX) |
| // This test doesn't run when the locale is not UTF-8 because some of the |
| // string conversions fail. This is OK (we have the default value) but they |
| // don't match our expectations. |
| std::string locale = setlocale(LC_CTYPE, NULL); |
| StringToLowerASCII(&locale); |
| EXPECT_NE(std::string::npos, locale.find("utf-8")) |
| << "Your locale (" << locale << ") must be set to UTF-8 " |
| << "for this test to pass!"; |
| #endif |
| |
| for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kGenerateFileNameTestCases); ++i) { |
| FilePath generated_name; |
| download_util::GenerateFileName(GURL(kGenerateFileNameTestCases[i].url), |
| kGenerateFileNameTestCases[i].disposition, |
| "", |
| kGenerateFileNameTestCases[i].mime_type, |
| &generated_name); |
| EXPECT_EQ(kGenerateFileNameTestCases[i].expected_name, |
| file_util::FilePathAsWString(generated_name)) << i; |
| } |
| |
| for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kGenerateFileNameTestCases); ++i) { |
| FilePath generated_name; |
| download_util::GenerateFileName(GURL(kGenerateFileNameTestCases[i].url), |
| kGenerateFileNameTestCases[i].disposition, |
| "GBK", |
| kGenerateFileNameTestCases[i].mime_type, |
| &generated_name); |
| EXPECT_EQ(kGenerateFileNameTestCases[i].expected_name, |
| file_util::FilePathAsWString(generated_name)) << i; |
| } |
| |
| // A couple of cases with raw 8bit characters in C-D. |
| { |
| FilePath generated_name; |
| download_util::GenerateFileName(GURL("http://www.example.com/images?id=3"), |
| "attachment; filename=caf\xc3\xa9.png", |
| "iso-8859-1", |
| "image/png", |
| &generated_name); |
| EXPECT_EQ(L"caf\u00e9.png", file_util::FilePathAsWString(generated_name)); |
| } |
| |
| { |
| FilePath generated_name; |
| download_util::GenerateFileName(GURL("http://www.example.com/images?id=3"), |
| "attachment; filename=caf\xe5.png", |
| "windows-1253", |
| "image/png", |
| &generated_name); |
| EXPECT_EQ(L"caf\u03b5.png", file_util::FilePathAsWString(generated_name)); |
| } |
| } |
| |
| const struct { |
| const FilePath::CharType* path; |
| const char* mime_type; |
| const FilePath::CharType* expected_path; |
| } kSafeFilenameCases[] = { |
| #if defined(OS_WIN) |
| { FILE_PATH_LITERAL("C:\\foo\\bar.htm"), |
| "text/html", |
| FILE_PATH_LITERAL("C:\\foo\\bar.htm") }, |
| { FILE_PATH_LITERAL("C:\\foo\\bar.html"), |
| "text/html", |
| FILE_PATH_LITERAL("C:\\foo\\bar.html") }, |
| { FILE_PATH_LITERAL("C:\\foo\\bar"), |
| "text/html", |
| FILE_PATH_LITERAL("C:\\foo\\bar.htm") }, |
| |
| { FILE_PATH_LITERAL("C:\\bar.html"), |
| "image/png", |
| FILE_PATH_LITERAL("C:\\bar.html") }, |
| { FILE_PATH_LITERAL("C:\\bar"), |
| "image/png", |
| FILE_PATH_LITERAL("C:\\bar.png") }, |
| |
| { FILE_PATH_LITERAL("C:\\foo\\bar.exe"), |
| "text/html", |
| FILE_PATH_LITERAL("C:\\foo\\bar.exe") }, |
| { FILE_PATH_LITERAL("C:\\foo\\bar.exe"), |
| "image/gif", |
| FILE_PATH_LITERAL("C:\\foo\\bar.exe") }, |
| |
| { FILE_PATH_LITERAL("C:\\foo\\google.com"), |
| "text/html", |
| FILE_PATH_LITERAL("C:\\foo\\google.com") }, |
| |
| { FILE_PATH_LITERAL("C:\\foo\\con.htm"), |
| "text/html", |
| FILE_PATH_LITERAL("C:\\foo\\_con.htm") }, |
| { FILE_PATH_LITERAL("C:\\foo\\con"), |
| "text/html", |
| FILE_PATH_LITERAL("C:\\foo\\_con.htm") }, |
| #else // !defined(OS_WIN) |
| { FILE_PATH_LITERAL("/foo/bar.htm"), |
| "text/html", |
| FILE_PATH_LITERAL("/foo/bar.htm") }, |
| { FILE_PATH_LITERAL("/foo/bar.html"), |
| "text/html", |
| FILE_PATH_LITERAL("/foo/bar.html") }, |
| { FILE_PATH_LITERAL("/foo/bar"), |
| "text/html", |
| FILE_PATH_LITERAL("/foo/bar.html") }, |
| |
| { FILE_PATH_LITERAL("/bar.html"), |
| "image/png", |
| FILE_PATH_LITERAL("/bar.html") }, |
| { FILE_PATH_LITERAL("/bar"), |
| "image/png", |
| FILE_PATH_LITERAL("/bar.png") }, |
| |
| { FILE_PATH_LITERAL("/foo/bar.exe"), |
| "image/gif", |
| FILE_PATH_LITERAL("/foo/bar.exe") }, |
| |
| { FILE_PATH_LITERAL("/foo/google.com"), |
| "text/html", |
| FILE_PATH_LITERAL("/foo/google.com") }, |
| |
| { FILE_PATH_LITERAL("/foo/con.htm"), |
| "text/html", |
| FILE_PATH_LITERAL("/foo/con.htm") }, |
| { FILE_PATH_LITERAL("/foo/con"), |
| "text/html", |
| FILE_PATH_LITERAL("/foo/con.html") }, |
| #endif // !defined(OS_WIN) |
| }; |
| |
| TEST(DownloadUtilTest, GenerateSafeFileName) { |
| for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSafeFilenameCases); ++i) { |
| FilePath path(kSafeFilenameCases[i].path); |
| download_util::GenerateSafeFileName(kSafeFilenameCases[i].mime_type, &path); |
| EXPECT_EQ(kSafeFilenameCases[i].expected_path, path.value()) << i; |
| } |
| } |
| |
| } // namespace |
| |