// Copyright (c) 2011 The Chromium Embedded Framework Authors. // Portions copyright (c) 2011 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 "download_util.h" #include "base/file_path.h" #include "base/string16.h" #include "base/string_util.h" #include "base/sys_string_conversions.h" #include "base/utf_string_conversions.h" #include "base/threading/thread.h" #include "base/threading/thread_restrictions.h" #include "googleurl/src/gurl.h" #include "net/base/mime_util.h" #include "net/base/net_util.h" namespace { #if defined(OS_WIN) // Returns whether the specified extension is automatically integrated into the // windows shell. // From chrome/browser/download/download_util.cc bool IsShellIntegratedExtension(const string16& extension) { string16 extension_lower = StringToLowerASCII(extension); static const wchar_t* const integrated_extensions[] = { // See . L"local", // Right-clicking on shortcuts can be magical. L"lnk", }; for (int i = 0; i < arraysize(integrated_extensions); ++i) { if (extension_lower == integrated_extensions[i]) return true; } // See . // That vulnerability report is not exactly on point, but files become magical // if their end in a CLSID. Here we block extensions that look like CLSIDs. if (extension_lower.size() > 0 && extension_lower.at(0) == L'{' && extension_lower.at(extension_lower.length() - 1) == L'}') return true; return false; } // Returns whether the specified file name is a reserved name on windows. // This includes names like "com2.zip" (which correspond to devices) and // desktop.ini and thumbs.db which have special meaning to the windows shell. // From chrome/browser/download/download_util.cc bool IsReservedName(const string16& filename) { // This list is taken from the MSDN article "Naming a file" // http://msdn2.microsoft.com/en-us/library/aa365247(VS.85).aspx // I also added clock$ because GetSaveFileName seems to consider it as a // reserved name too. static const wchar_t* const known_devices[] = { L"con", L"prn", L"aux", L"nul", L"com1", L"com2", L"com3", L"com4", L"com5", L"com6", L"com7", L"com8", L"com9", L"lpt1", L"lpt2", L"lpt3", L"lpt4", L"lpt5", L"lpt6", L"lpt7", L"lpt8", L"lpt9", L"clock$" }; string16 filename_lower = StringToLowerASCII(filename); for (int i = 0; i < arraysize(known_devices); ++i) { // Exact match. if (filename_lower == known_devices[i]) return true; // Starts with "DEVICE.". if (filename_lower.find(string16(known_devices[i]) + L".") == 0) return true; } static const wchar_t* const magic_names[] = { // These file names are used by the "Customize folder" feature of the shell. L"desktop.ini", L"thumbs.db", }; for (int i = 0; i < arraysize(magic_names); ++i) { if (filename_lower == magic_names[i]) return true; } return false; } #endif // OS_WIN // Create an extension based on the file name and mime type. // From chrome/browser/download/download_util.cc void GenerateExtension(const FilePath& file_name, const std::string& mime_type, FilePath::StringType* generated_extension) { // We're worried about two things here: // // 1) Usability. If the site fails to provide a file extension, we want to // guess a reasonable file extension based on the content type. // // 2) Shell integration. Some file extensions automatically integrate with // the shell. We block these extensions to prevent a malicious web site // from integrating with the user's shell. // See if our file name already contains an extension. FilePath::StringType extension = file_name.Extension(); if (!extension.empty()) extension.erase(extension.begin()); // Erase preceding '.'. #if defined(OS_WIN) static const FilePath::CharType default_extension[] = FILE_PATH_LITERAL("download"); // Rename shell-integrated extensions. if (IsShellIntegratedExtension(extension)) extension.assign(default_extension); #endif if (extension.empty()) { // The GetPreferredExtensionForMimeType call will end up going to disk. Do // this on another thread to avoid slowing the IO thread. // http://crbug.com/61827 base::ThreadRestrictions::ScopedAllowIO allow_io; net::GetPreferredExtensionForMimeType(mime_type, &extension); } generated_extension->swap(extension); } // Used to make sure we have a safe file extension and filename for a // download. |file_name| can either be just the file name or it can be a // full path to a file. // From chrome/browser/download/download_util.cc void GenerateSafeFileName(const std::string& mime_type, FilePath* file_name) { // Make sure we get the right file extension FilePath::StringType extension; GenerateExtension(*file_name, mime_type, &extension); *file_name = file_name->ReplaceExtension(extension); #if defined(OS_WIN) // Prepend "_" to the file name if it's a reserved name FilePath::StringType leaf_name = file_name->BaseName().value(); DCHECK(!leaf_name.empty()); if (IsReservedName(leaf_name)) { leaf_name = FilePath::StringType(FILE_PATH_LITERAL("_")) + leaf_name; *file_name = file_name->DirName(); if (file_name->value() == FilePath::kCurrentDirectory) { *file_name = FilePath(leaf_name); } else { *file_name = file_name->Append(leaf_name); } } #endif } } // namespace namespace download_util { // Create a file name based on the response from the server. // From chrome/browser/download/download_util.cc void GenerateFileName(const GURL& url, const std::string& content_disposition, const std::string& referrer_charset, const std::string& mime_type, const std::string& suggested_name, FilePath* generated_name) { string16 new_name = net::GetSuggestedFilename(GURL(url), content_disposition, referrer_charset, suggested_name, ASCIIToUTF16("download")); // TODO(evan): this code is totally wrong -- we should just generate // Unicode filenames and do all this encoding switching at the end. // However, I'm just shuffling wrong code around, at least not adding // to it. #if defined(OS_WIN) *generated_name = FilePath(new_name); #else *generated_name = FilePath(base::SysWideToNativeMB(UTF16ToWide(new_name))); #endif DCHECK(!generated_name->empty()); GenerateSafeFileName(mime_type, generated_name); } } // namespace download_util