OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 // | 4 // |
5 // Download utility implementation | 5 // Download utility implementation |
6 | 6 |
7 #include "chrome/browser/download/download_util.h" | 7 #include "chrome/browser/download/download_util.h" |
8 | 8 |
9 #if defined(OS_WIN) | 9 #if defined(OS_WIN) |
10 #include <shobjidl.h> | 10 #include <shobjidl.h> |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
92 | 92 |
93 // The maximum number of 'uniquified' files we will try to create. | 93 // The maximum number of 'uniquified' files we will try to create. |
94 // This is used when the filename we're trying to download is already in use, | 94 // This is used when the filename we're trying to download is already in use, |
95 // so we create a new unique filename by appending " (nnn)" before the | 95 // so we create a new unique filename by appending " (nnn)" before the |
96 // extension, where 1 <= nnn <= kMaxUniqueFiles. | 96 // extension, where 1 <= nnn <= kMaxUniqueFiles. |
97 // Also used by code that cleans up said files. | 97 // Also used by code that cleans up said files. |
98 static const int kMaxUniqueFiles = 100; | 98 static const int kMaxUniqueFiles = 100; |
99 | 99 |
100 namespace { | 100 namespace { |
101 | 101 |
102 #if defined(OS_WIN) | |
103 // Returns whether the specified extension is automatically integrated into the | |
104 // windows shell. | |
105 bool IsShellIntegratedExtension(const string16& extension) { | |
106 string16 extension_lower = StringToLowerASCII(extension); | |
107 | |
108 static const wchar_t* const integrated_extensions[] = { | |
109 // See <http://msdn.microsoft.com/en-us/library/ms811694.aspx>. | |
110 L"local", | |
111 // Right-clicking on shortcuts can be magical. | |
112 L"lnk", | |
113 }; | |
114 | |
115 for (int i = 0; i < arraysize(integrated_extensions); ++i) { | |
116 if (extension_lower == integrated_extensions[i]) | |
117 return true; | |
118 } | |
119 | |
120 // See <http://www.juniper.net/security/auto/vulnerabilities/vuln2612.html>. | |
121 // That vulnerability report is not exactly on point, but files become magical | |
122 // if their end in a CLSID. Here we block extensions that look like CLSIDs. | |
123 if (!extension_lower.empty() && extension_lower[0] == L'{' && | |
124 extension_lower[extension_lower.length() - 1] == L'}') | |
125 return true; | |
126 | |
127 return false; | |
128 } | |
129 | |
130 // Returns whether the specified file name is a reserved name on windows. | |
131 // This includes names like "com2.zip" (which correspond to devices) and | |
132 // desktop.ini and thumbs.db which have special meaning to the windows shell. | |
133 bool IsReservedName(const string16& filename) { | |
134 // This list is taken from the MSDN article "Naming a file" | |
135 // http://msdn2.microsoft.com/en-us/library/aa365247(VS.85).aspx | |
136 // I also added clock$ because GetSaveFileName seems to consider it as a | |
137 // reserved name too. | |
138 static const wchar_t* const known_devices[] = { | |
139 L"con", L"prn", L"aux", L"nul", L"com1", L"com2", L"com3", L"com4", L"com5", | |
140 L"com6", L"com7", L"com8", L"com9", L"lpt1", L"lpt2", L"lpt3", L"lpt4", | |
141 L"lpt5", L"lpt6", L"lpt7", L"lpt8", L"lpt9", L"clock$" | |
142 }; | |
143 string16 filename_lower = StringToLowerASCII(filename); | |
144 | |
145 for (int i = 0; i < arraysize(known_devices); ++i) { | |
146 // Exact match. | |
147 if (filename_lower == known_devices[i]) | |
148 return true; | |
149 // Starts with "DEVICE.". | |
150 if (filename_lower.find(string16(known_devices[i]) + L".") == 0) | |
151 return true; | |
152 } | |
153 | |
154 static const wchar_t* const magic_names[] = { | |
155 // These file names are used by the "Customize folder" feature of the shell. | |
156 L"desktop.ini", | |
157 L"thumbs.db", | |
158 }; | |
159 | |
160 for (int i = 0; i < arraysize(magic_names); ++i) { | |
161 if (filename_lower == magic_names[i]) | |
162 return true; | |
163 } | |
164 | |
165 return false; | |
166 } | |
167 #endif // OS_WIN | |
168 | |
169 void GenerateFileNameInternal(const GURL& url, | 102 void GenerateFileNameInternal(const GURL& url, |
170 const std::string& content_disposition, | 103 const std::string& content_disposition, |
171 const std::string& referrer_charset, | 104 const std::string& referrer_charset, |
172 const std::string& suggested_name, | 105 const std::string& suggested_name, |
173 const std::string& mime_type, | 106 const std::string& mime_type, |
174 FilePath* generated_name) { | 107 FilePath* generated_name) { |
175 | |
176 string16 default_file_name( | 108 string16 default_file_name( |
177 l10n_util::GetStringUTF16(IDS_DEFAULT_DOWNLOAD_FILENAME)); | 109 l10n_util::GetStringUTF16(IDS_DEFAULT_DOWNLOAD_FILENAME)); |
178 | 110 |
179 string16 new_name = net::GetSuggestedFilename(GURL(url), | 111 *generated_name = net::GenerateFileName(url, content_disposition, |
180 content_disposition, | 112 referrer_charset, suggested_name, |
181 referrer_charset, | 113 mime_type, default_file_name); |
182 suggested_name, | |
183 default_file_name); | |
184 | |
185 // TODO(evan): this code is totally wrong -- we should just generate | |
186 // Unicode filenames and do all this encoding switching at the end. | |
187 // However, I'm just shuffling wrong code around, at least not adding | |
188 // to it. | |
189 #if defined(OS_WIN) | |
190 *generated_name = FilePath(new_name); | |
191 #else | |
192 *generated_name = FilePath( | |
193 base::SysWideToNativeMB(UTF16ToWide(new_name))); | |
194 #endif | |
195 | |
196 DCHECK(!generated_name->empty()); | |
197 | |
198 GenerateSafeFileName(mime_type, generated_name); | |
199 } | 114 } |
200 | 115 |
201 // All possible error codes from the network module. Note that the error codes | 116 // All possible error codes from the network module. Note that the error codes |
202 // are all positive (since histograms expect positive sample values). | 117 // are all positive (since histograms expect positive sample values). |
203 const int kAllNetErrorCodes[] = { | 118 const int kAllNetErrorCodes[] = { |
204 #define NET_ERROR(label, value) -(value), | 119 #define NET_ERROR(label, value) -(value), |
205 #include "net/base/net_error_list.h" | 120 #include "net/base/net_error_list.h" |
206 #undef NET_ERROR | 121 #undef NET_ERROR |
207 }; | 122 }; |
208 | 123 |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
244 | 159 |
245 bool DownloadPathIsDangerous(const FilePath& download_path) { | 160 bool DownloadPathIsDangerous(const FilePath& download_path) { |
246 FilePath desktop_dir; | 161 FilePath desktop_dir; |
247 if (!PathService::Get(chrome::DIR_USER_DESKTOP, &desktop_dir)) { | 162 if (!PathService::Get(chrome::DIR_USER_DESKTOP, &desktop_dir)) { |
248 NOTREACHED(); | 163 NOTREACHED(); |
249 return false; | 164 return false; |
250 } | 165 } |
251 return (download_path == desktop_dir); | 166 return (download_path == desktop_dir); |
252 } | 167 } |
253 | 168 |
254 void GenerateExtension(const FilePath& file_name, | |
255 const std::string& mime_type, | |
256 FilePath::StringType* generated_extension) { | |
257 // We're worried about two things here: | |
258 // | |
259 // 1) Usability. If the site fails to provide a file extension, we want to | |
260 // guess a reasonable file extension based on the content type. | |
261 // | |
262 // 2) Shell integration. Some file extensions automatically integrate with | |
263 // the shell. We block these extensions to prevent a malicious web site | |
264 // from integrating with the user's shell. | |
265 | |
266 // See if our file name already contains an extension. | |
267 FilePath::StringType extension = file_name.Extension(); | |
268 if (!extension.empty()) | |
269 extension.erase(extension.begin()); // Erase preceding '.'. | |
270 | |
271 #if defined(OS_WIN) | |
272 static const FilePath::CharType default_extension[] = | |
273 FILE_PATH_LITERAL("download"); | |
274 | |
275 // Rename shell-integrated extensions. | |
276 if (IsShellIntegratedExtension(extension)) | |
277 extension.assign(default_extension); | |
278 #endif | |
279 | |
280 if (extension.empty()) { | |
281 // The GetPreferredExtensionForMimeType call will end up going to disk. Do | |
282 // this on another thread to avoid slowing the IO thread. | |
283 // http://crbug.com/61827 | |
284 base::ThreadRestrictions::ScopedAllowIO allow_io; | |
285 net::GetPreferredExtensionForMimeType(mime_type, &extension); | |
286 } | |
287 | |
288 generated_extension->swap(extension); | |
289 } | |
290 | |
291 void GenerateFileNameFromRequest(const DownloadItem& download_item, | 169 void GenerateFileNameFromRequest(const DownloadItem& download_item, |
292 FilePath* generated_name) { | 170 FilePath* generated_name) { |
293 GenerateFileNameInternal(download_item.GetURL(), | 171 GenerateFileNameInternal(download_item.GetURL(), |
294 download_item.content_disposition(), | 172 download_item.content_disposition(), |
295 download_item.referrer_charset(), | 173 download_item.referrer_charset(), |
296 download_item.suggested_filename(), | 174 download_item.suggested_filename(), |
297 download_item.mime_type(), | 175 download_item.mime_type(), |
298 generated_name); | 176 generated_name); |
299 } | 177 } |
300 | 178 |
301 void GenerateFileNameFromSuggestedName(const GURL& url, | 179 void GenerateFileNameFromSuggestedName(const GURL& url, |
302 const std::string& suggested_name, | 180 const std::string& suggested_name, |
303 const std::string& mime_type, | 181 const std::string& mime_type, |
304 FilePath* generated_name) { | 182 FilePath* generated_name) { |
| 183 // TODO(asanka): We should pass in a valid referrer_charset here. |
305 GenerateFileNameInternal(url, std::string(), std::string(), | 184 GenerateFileNameInternal(url, std::string(), std::string(), |
306 suggested_name, mime_type, generated_name); | 185 suggested_name, mime_type, generated_name); |
307 } | 186 } |
308 | 187 |
309 void GenerateFileName(const GURL& url, | |
310 const std::string& content_disposition, | |
311 const std::string& referrer_charset, | |
312 const std::string& mime_type, | |
313 FilePath* generated_name) { | |
314 GenerateFileNameInternal(url, content_disposition, referrer_charset, | |
315 std::string(), mime_type, generated_name); | |
316 } | |
317 | |
318 void GenerateSafeFileName(const std::string& mime_type, FilePath* file_name) { | |
319 // Make sure we get the right file extension | |
320 FilePath::StringType extension; | |
321 GenerateExtension(*file_name, mime_type, &extension); | |
322 *file_name = file_name->ReplaceExtension(extension); | |
323 | |
324 #if defined(OS_WIN) | |
325 // Prepend "_" to the file name if it's a reserved name | |
326 FilePath::StringType leaf_name = file_name->BaseName().value(); | |
327 DCHECK(!leaf_name.empty()); | |
328 if (IsReservedName(leaf_name)) { | |
329 leaf_name = FilePath::StringType(FILE_PATH_LITERAL("_")) + leaf_name; | |
330 *file_name = file_name->DirName(); | |
331 if (file_name->value() == FilePath::kCurrentDirectory) { | |
332 *file_name = FilePath(leaf_name); | |
333 } else { | |
334 *file_name = file_name->Append(leaf_name); | |
335 } | |
336 } | |
337 #endif | |
338 } | |
339 | |
340 void RecordDownloadCount(DownloadCountTypes type) { | 188 void RecordDownloadCount(DownloadCountTypes type) { |
341 UMA_HISTOGRAM_ENUMERATION( | 189 UMA_HISTOGRAM_ENUMERATION( |
342 "Download.Counts", type, DOWNLOAD_COUNT_TYPES_LAST_ENTRY); | 190 "Download.Counts", type, DOWNLOAD_COUNT_TYPES_LAST_ENTRY); |
343 } | 191 } |
344 | 192 |
345 void RecordDownloadCompleted(const base::TimeTicks& start) { | 193 void RecordDownloadCompleted(const base::TimeTicks& start) { |
346 download_util::RecordDownloadCount(download_util::COMPLETED_COUNT); | 194 download_util::RecordDownloadCount(download_util::COMPLETED_COUNT); |
347 UMA_HISTOGRAM_LONG_TIMES("Download.Time", (base::TimeTicks::Now() - start)); | 195 UMA_HISTOGRAM_LONG_TIMES("Download.Time", (base::TimeTicks::Now() - start)); |
348 } | 196 } |
349 | 197 |
(...skipping 604 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
954 AppendNumberToPath(&new_path, count); | 802 AppendNumberToPath(&new_path, count); |
955 | 803 |
956 if (!file_util::PathExists(new_path) && | 804 if (!file_util::PathExists(new_path) && |
957 !file_util::PathExists(GetCrDownloadPath(new_path))) | 805 !file_util::PathExists(GetCrDownloadPath(new_path))) |
958 return count; | 806 return count; |
959 } | 807 } |
960 | 808 |
961 return -1; | 809 return -1; |
962 } | 810 } |
963 | 811 |
964 namespace { | |
965 | |
966 // NOTE: If index is 0, deletes files that do not have the " (nnn)" appended. | |
967 void DeleteUniqueDownloadFile(const FilePath& path, int index) { | |
968 FilePath new_path(path); | |
969 if (index > 0) | |
970 AppendNumberToPath(&new_path, index); | |
971 file_util::Delete(new_path, false); | |
972 } | |
973 | |
974 } // namespace | |
975 | |
976 void EraseUniqueDownloadFiles(const FilePath& path) { | |
977 FilePath cr_path = GetCrDownloadPath(path); | |
978 | |
979 for (int index = 0; index <= kMaxUniqueFiles; ++index) { | |
980 DeleteUniqueDownloadFile(path, index); | |
981 DeleteUniqueDownloadFile(cr_path, index); | |
982 } | |
983 } | |
984 | |
985 FilePath GetCrDownloadPath(const FilePath& suggested_path) { | 812 FilePath GetCrDownloadPath(const FilePath& suggested_path) { |
986 FilePath::StringType file_name; | 813 FilePath::StringType file_name; |
987 base::SStringPrintf( | 814 base::SStringPrintf( |
988 &file_name, | 815 &file_name, |
989 PRFilePathLiteral FILE_PATH_LITERAL(".crdownload"), | 816 PRFilePathLiteral FILE_PATH_LITERAL(".crdownload"), |
990 suggested_path.value().c_str()); | 817 suggested_path.value().c_str()); |
991 return FilePath(file_name); | 818 return FilePath(file_name); |
992 } | 819 } |
993 | 820 |
994 } // namespace download_util | 821 } // namespace download_util |
OLD | NEW |