Add ICU message format support
[chromium-blink-merge.git] / content / browser / download / base_file_win.cc
blob870feb613d3b300a2d793b07cb4701a085bad72c
1 // Copyright (c) 2012 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.
5 #include "content/browser/download/base_file.h"
7 #include <windows.h>
8 #include <cguid.h>
9 #include <objbase.h>
10 #include <shellapi.h>
12 #include "base/files/file.h"
13 #include "base/files/file_util.h"
14 #include "base/guid.h"
15 #include "base/metrics/histogram.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/threading/thread_restrictions.h"
18 #include "content/browser/download/download_interrupt_reasons_impl.h"
19 #include "content/browser/download/download_stats.h"
20 #include "content/browser/safe_util_win.h"
21 #include "content/public/browser/browser_thread.h"
23 namespace content {
24 namespace {
26 const int kAllSpecialShFileOperationCodes[] = {
27 // Should be kept in sync with the case statement below.
28 ERROR_ACCESS_DENIED,
29 ERROR_SHARING_VIOLATION,
30 ERROR_INVALID_PARAMETER,
31 0x71,
32 0x72,
33 0x73,
34 0x74,
35 0x75,
36 0x76,
37 0x78,
38 0x79,
39 0x7A,
40 0x7C,
41 0x7D,
42 0x7E,
43 0x80,
44 0x81,
45 0x82,
46 0x83,
47 0x84,
48 0x85,
49 0x86,
50 0x87,
51 0x88,
52 0xB7,
53 0x402,
54 0x10000,
55 0x10074,
58 // Maps the result of a call to |SHFileOperation()| onto a
59 // |DownloadInterruptReason|.
61 // These return codes are *old* (as in, DOS era), and specific to
62 // |SHFileOperation()|.
63 // They do not appear in any windows header.
65 // See http://msdn.microsoft.com/en-us/library/bb762164(VS.85).aspx.
66 DownloadInterruptReason MapShFileOperationCodes(int code) {
67 DownloadInterruptReason result = DOWNLOAD_INTERRUPT_REASON_NONE;
69 // Check these pre-Win32 error codes first, then check for matches
70 // in Winerror.h.
71 // This switch statement should be kept in sync with the list of codes
72 // above.
73 switch (code) {
74 // Not a pre-Win32 error code; here so that this particular case shows up in
75 // our histograms. Unfortunately, it is used not just to signal actual
76 // ACCESS_DENIED errors, but many other errors as well. So we treat it as a
77 // transient error.
78 case ERROR_ACCESS_DENIED: // Access is denied.
79 result = DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR;
80 break;
82 // This isn't documented but returned from SHFileOperation. Sharing
83 // violations indicate that another process had the file open while we were
84 // trying to rename. Anti-virus is believed to be the cause of this error in
85 // the wild. Treated as a transient error on the assumption that the file
86 // will be made available for renaming at a later time.
87 case ERROR_SHARING_VIOLATION:
88 result = DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR;
89 break;
91 // This is also not a documented return value of SHFileOperation, but has
92 // been observed in the wild. We are treating it as a transient error based
93 // on the cases we have seen so far. See http://crbug.com/368455.
94 case ERROR_INVALID_PARAMETER:
95 result = DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR;
96 break;
98 // The source and destination files are the same file.
99 // DE_SAMEFILE == 0x71
100 case 0x71:
101 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
102 break;
104 // The operation was canceled by the user, or silently canceled if the
105 // appropriate flags were supplied to SHFileOperation.
106 // DE_OPCANCELLED == 0x75
107 case 0x75:
108 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
109 break;
111 // Security settings denied access to the source.
112 // DE_ACCESSDENIEDSRC == 0x78
113 case 0x78:
114 result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
115 break;
117 // The source or destination path exceeded or would exceed MAX_PATH.
118 // DE_PATHTOODEEP == 0x79
119 case 0x79:
120 result = DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
121 break;
123 // The path in the source or destination or both was invalid.
124 // DE_INVALIDFILES == 0x7C
125 case 0x7C:
126 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
127 break;
129 // The destination path is an existing file.
130 // DE_FLDDESTISFILE == 0x7E
131 case 0x7E:
132 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
133 break;
135 // The destination path is an existing folder.
136 // DE_FILEDESTISFLD == 0x80
137 case 0x80:
138 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
139 break;
141 // The name of the file exceeds MAX_PATH.
142 // DE_FILENAMETOOLONG == 0x81
143 case 0x81:
144 result = DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
145 break;
147 // The destination is a read-only CD-ROM, possibly unformatted.
148 // DE_DEST_IS_CDROM == 0x82
149 case 0x82:
150 result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
151 break;
153 // The destination is a read-only DVD, possibly unformatted.
154 // DE_DEST_IS_DVD == 0x83
155 case 0x83:
156 result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
157 break;
159 // The destination is a writable CD-ROM, possibly unformatted.
160 // DE_DEST_IS_CDRECORD == 0x84
161 case 0x84:
162 result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
163 break;
165 // The file involved in the operation is too large for the destination
166 // media or file system.
167 // DE_FILE_TOO_LARGE == 0x85
168 case 0x85:
169 result = DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE;
170 break;
172 // The source is a read-only CD-ROM, possibly unformatted.
173 // DE_SRC_IS_CDROM == 0x86
174 case 0x86:
175 result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
176 break;
178 // The source is a read-only DVD, possibly unformatted.
179 // DE_SRC_IS_DVD == 0x87
180 case 0x87:
181 result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
182 break;
184 // The source is a writable CD-ROM, possibly unformatted.
185 // DE_SRC_IS_CDRECORD == 0x88
186 case 0x88:
187 result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
188 break;
190 // MAX_PATH was exceeded during the operation.
191 // DE_ERROR_MAX == 0xB7
192 case 0xB7:
193 result = DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
194 break;
196 // An unspecified error occurred on the destination.
197 // XE_ERRORONDEST == 0x10000
198 case 0x10000:
199 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
200 break;
202 // Multiple file paths were specified in the source buffer, but only one
203 // destination file path.
204 // DE_MANYSRC1DEST == 0x72
205 case 0x72:
206 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
207 break;
209 // Rename operation was specified but the destination path is
210 // a different directory. Use the move operation instead.
211 // DE_DIFFDIR == 0x73
212 case 0x73:
213 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
214 break;
216 // The source is a root directory, which cannot be moved or renamed.
217 // DE_ROOTDIR == 0x74
218 case 0x74:
219 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
220 break;
222 // The destination is a subtree of the source.
223 // DE_DESTSUBTREE == 0x76
224 case 0x76:
225 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
226 break;
228 // The operation involved multiple destination paths,
229 // which can fail in the case of a move operation.
230 // DE_MANYDEST == 0x7A
231 case 0x7A:
232 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
233 break;
235 // The source and destination have the same parent folder.
236 // DE_DESTSAMETREE == 0x7D
237 case 0x7D:
238 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
239 break;
241 // An unknown error occurred. This is typically due to an invalid path in
242 // the source or destination. This error does not occur on Windows Vista
243 // and later.
244 // DE_UNKNOWN_ERROR == 0x402
245 case 0x402:
246 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
247 break;
249 // Destination is a root directory and cannot be renamed.
250 // DE_ROOTDIR | ERRORONDEST == 0x10074
251 case 0x10074:
252 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
253 break;
256 // Narrow down on the reason we're getting some catch-all interrupt reasons.
257 if (result == DOWNLOAD_INTERRUPT_REASON_FILE_FAILED) {
258 UMA_HISTOGRAM_CUSTOM_ENUMERATION(
259 "Download.MapWinShErrorFileFailed", code,
260 base::CustomHistogram::ArrayToCustomRanges(
261 kAllSpecialShFileOperationCodes,
262 arraysize(kAllSpecialShFileOperationCodes)));
265 if (result == DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED) {
266 UMA_HISTOGRAM_CUSTOM_ENUMERATION(
267 "Download.MapWinShErrorAccessDenied", code,
268 base::CustomHistogram::ArrayToCustomRanges(
269 kAllSpecialShFileOperationCodes,
270 arraysize(kAllSpecialShFileOperationCodes)));
273 if (result == DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR) {
274 UMA_HISTOGRAM_CUSTOM_ENUMERATION(
275 "Download.MapWinShErrorTransientError", code,
276 base::CustomHistogram::ArrayToCustomRanges(
277 kAllSpecialShFileOperationCodes,
278 arraysize(kAllSpecialShFileOperationCodes)));
281 if (result != DOWNLOAD_INTERRUPT_REASON_NONE)
282 return result;
284 // If not one of the above codes, it should be a standard Windows error code.
285 return ConvertFileErrorToInterruptReason(
286 base::File::OSErrorToFileError(code));
289 // Maps a return code from ScanAndSaveDownloadedFile() to a
290 // DownloadInterruptReason. The return code in |result| is usually from the
291 // final IAttachmentExecute::Save() call.
292 DownloadInterruptReason MapScanAndSaveErrorCodeToInterruptReason(
293 HRESULT result) {
294 if (SUCCEEDED(result))
295 return DOWNLOAD_INTERRUPT_REASON_NONE;
297 switch (result) {
298 case INET_E_SECURITY_PROBLEM: // 0x800c000e
299 // This is returned if the download was blocked due to security
300 // restrictions. E.g. if the source URL was in the Restricted Sites zone
301 // and downloads are blocked on that zone, then the download would be
302 // deleted and this error code is returned.
303 return DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED;
305 case E_FAIL: // 0x80004005
306 // Returned if an anti-virus product reports an infection in the
307 // downloaded file during IAE::Save().
308 return DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED;
310 default:
311 // Any other error that occurs during IAttachmentExecute::Save() likely
312 // indicates a problem with the security check, but not necessarily the
313 // download. See http://crbug.com/153212.
314 return DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED;
318 } // namespace
320 // Renames a file using the SHFileOperation API to ensure that the target file
321 // gets the correct default security descriptor in the new path.
322 // Returns a network error, or net::OK for success.
323 DownloadInterruptReason BaseFile::MoveFileAndAdjustPermissions(
324 const base::FilePath& new_path) {
325 base::ThreadRestrictions::AssertIOAllowed();
327 // The parameters to SHFileOperation must be terminated with 2 NULL chars.
328 base::FilePath::StringType source = full_path_.value();
329 base::FilePath::StringType target = new_path.value();
331 source.append(1, L'\0');
332 target.append(1, L'\0');
334 SHFILEOPSTRUCT move_info = {0};
335 move_info.wFunc = FO_MOVE;
336 move_info.pFrom = source.c_str();
337 move_info.pTo = target.c_str();
338 move_info.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI |
339 FOF_NOCONFIRMMKDIR | FOF_NOCOPYSECURITYATTRIBS;
341 int result = SHFileOperation(&move_info);
342 DownloadInterruptReason interrupt_reason = DOWNLOAD_INTERRUPT_REASON_NONE;
344 if (result == 0 && move_info.fAnyOperationsAborted)
345 interrupt_reason = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
346 else if (result != 0)
347 interrupt_reason = MapShFileOperationCodes(result);
349 if (interrupt_reason != DOWNLOAD_INTERRUPT_REASON_NONE)
350 return LogInterruptReason("SHFileOperation", result, interrupt_reason);
351 return interrupt_reason;
354 DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() {
355 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
356 DCHECK(!detached_);
358 bound_net_log_.BeginEvent(net::NetLog::TYPE_DOWNLOAD_FILE_ANNOTATED);
359 DownloadInterruptReason result = DOWNLOAD_INTERRUPT_REASON_NONE;
360 std::string braces_guid = "{" + client_guid_ + "}";
361 GUID guid = GUID_NULL;
362 if (base::IsValidGUID(client_guid_)) {
363 HRESULT hr = CLSIDFromString(
364 base::UTF8ToUTF16(braces_guid).c_str(), &guid);
365 if (FAILED(hr))
366 guid = GUID_NULL;
369 HRESULT hr = AVScanFile(full_path_, source_url_.spec(), guid);
371 // If the download file is missing after the call, then treat this as an
372 // interrupted download.
374 // If the ScanAndSaveDownloadedFile() call failed, but the downloaded file is
375 // still around, then don't interrupt the download. Attachment Execution
376 // Services deletes the submitted file if the downloaded file is blocked by
377 // policy or if it was found to be infected.
379 // If the file is still there, then the error could be due to AES not being
380 // available or some other error during the AES invocation. In either case,
381 // we don't surface the error to the user.
382 if (!base::PathExists(full_path_)) {
383 DCHECK(FAILED(hr));
384 result = MapScanAndSaveErrorCodeToInterruptReason(hr);
385 if (result == DOWNLOAD_INTERRUPT_REASON_NONE) {
386 RecordDownloadCount(FILE_MISSING_AFTER_SUCCESSFUL_SCAN_COUNT);
387 result = DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED;
389 LogInterruptReason("ScanAndSaveDownloadedFile", hr, result);
391 bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_ANNOTATED);
392 return result;
395 } // namespace content