Save errno for logging before potentially overwriting it.
[chromium-blink-merge.git] / content / browser / download / base_file_win.cc
blob449134efb1c12e7774c26b2e0ae6b803f3e26700
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 <shellapi.h>
10 #include "base/file_util.h"
11 #include "base/metrics/histogram.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/threading/thread_restrictions.h"
14 #include "content/browser/download/download_interrupt_reasons_impl.h"
15 #include "content/browser/download/download_stats.h"
16 #include "content/browser/safe_util_win.h"
17 #include "content/public/browser/browser_thread.h"
19 namespace content {
20 namespace {
22 const int kAllSpecialShFileOperationCodes[] = {
23 // Should be kept in sync with the case statement below.
24 ERROR_ACCESS_DENIED,
25 0x71,
26 0x72,
27 0x73,
28 0x74,
29 0x75,
30 0x76,
31 0x78,
32 0x79,
33 0x7A,
34 0x7C,
35 0x7D,
36 0x7E,
37 0x80,
38 0x81,
39 0x82,
40 0x83,
41 0x84,
42 0x85,
43 0x86,
44 0x87,
45 0x88,
46 0xB7,
47 0x402,
48 0x10000,
49 0x10074,
52 // Maps the result of a call to |SHFileOperation()| onto a
53 // |DownloadInterruptReason|.
55 // These return codes are *old* (as in, DOS era), and specific to
56 // |SHFileOperation()|.
57 // They do not appear in any windows header.
59 // See http://msdn.microsoft.com/en-us/library/bb762164(VS.85).aspx.
60 DownloadInterruptReason MapShFileOperationCodes(int code) {
61 DownloadInterruptReason result = DOWNLOAD_INTERRUPT_REASON_NONE;
63 // Check these pre-Win32 error codes first, then check for matches
64 // in Winerror.h.
65 // This switch statement should be kept in sync with the list of codes
66 // above.
67 switch (code) {
68 // Not a pre-Win32 error code; here so that this particular
69 // case shows up in our histograms. This is redundant with the
70 // mapping function net::MapSystemError used later.
71 case ERROR_ACCESS_DENIED: // Access is denied.
72 result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
73 break;
75 // The source and destination files are the same file.
76 // DE_SAMEFILE == 0x71
77 case 0x71:
78 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
79 break;
81 // The operation was canceled by the user, or silently canceled if the
82 // appropriate flags were supplied to SHFileOperation.
83 // DE_OPCANCELLED == 0x75
84 case 0x75:
85 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
86 break;
88 // Security settings denied access to the source.
89 // DE_ACCESSDENIEDSRC == 0x78
90 case 0x78:
91 result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
92 break;
94 // The source or destination path exceeded or would exceed MAX_PATH.
95 // DE_PATHTOODEEP == 0x79
96 case 0x79:
97 result = DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
98 break;
100 // The path in the source or destination or both was invalid.
101 // DE_INVALIDFILES == 0x7C
102 case 0x7C:
103 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
104 break;
106 // The destination path is an existing file.
107 // DE_FLDDESTISFILE == 0x7E
108 case 0x7E:
109 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
110 break;
112 // The destination path is an existing folder.
113 // DE_FILEDESTISFLD == 0x80
114 case 0x80:
115 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
116 break;
118 // The name of the file exceeds MAX_PATH.
119 // DE_FILENAMETOOLONG == 0x81
120 case 0x81:
121 result = DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
122 break;
124 // The destination is a read-only CD-ROM, possibly unformatted.
125 // DE_DEST_IS_CDROM == 0x82
126 case 0x82:
127 result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
128 break;
130 // The destination is a read-only DVD, possibly unformatted.
131 // DE_DEST_IS_DVD == 0x83
132 case 0x83:
133 result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
134 break;
136 // The destination is a writable CD-ROM, possibly unformatted.
137 // DE_DEST_IS_CDRECORD == 0x84
138 case 0x84:
139 result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
140 break;
142 // The file involved in the operation is too large for the destination
143 // media or file system.
144 // DE_FILE_TOO_LARGE == 0x85
145 case 0x85:
146 result = DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE;
147 break;
149 // The source is a read-only CD-ROM, possibly unformatted.
150 // DE_SRC_IS_CDROM == 0x86
151 case 0x86:
152 result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
153 break;
155 // The source is a read-only DVD, possibly unformatted.
156 // DE_SRC_IS_DVD == 0x87
157 case 0x87:
158 result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
159 break;
161 // The source is a writable CD-ROM, possibly unformatted.
162 // DE_SRC_IS_CDRECORD == 0x88
163 case 0x88:
164 result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
165 break;
167 // MAX_PATH was exceeded during the operation.
168 // DE_ERROR_MAX == 0xB7
169 case 0xB7:
170 result = DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
171 break;
173 // An unspecified error occurred on the destination.
174 // XE_ERRORONDEST == 0x10000
175 case 0x10000:
176 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
177 break;
179 // Multiple file paths were specified in the source buffer, but only one
180 // destination file path.
181 // DE_MANYSRC1DEST == 0x72
182 case 0x72:
183 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
184 break;
186 // Rename operation was specified but the destination path is
187 // a different directory. Use the move operation instead.
188 // DE_DIFFDIR == 0x73
189 case 0x73:
190 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
191 break;
193 // The source is a root directory, which cannot be moved or renamed.
194 // DE_ROOTDIR == 0x74
195 case 0x74:
196 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
197 break;
199 // The destination is a subtree of the source.
200 // DE_DESTSUBTREE == 0x76
201 case 0x76:
202 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
203 break;
205 // The operation involved multiple destination paths,
206 // which can fail in the case of a move operation.
207 // DE_MANYDEST == 0x7A
208 case 0x7A:
209 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
210 break;
212 // The source and destination have the same parent folder.
213 // DE_DESTSAMETREE == 0x7D
214 case 0x7D:
215 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
216 break;
218 // An unknown error occurred. This is typically due to an invalid path in
219 // the source or destination. This error does not occur on Windows Vista
220 // and later.
221 // DE_UNKNOWN_ERROR == 0x402
222 case 0x402:
223 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
224 break;
226 // Destination is a root directory and cannot be renamed.
227 // DE_ROOTDIR | ERRORONDEST == 0x10074
228 case 0x10074:
229 result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
230 break;
233 // Narrow down on the reason we're getting some catch-all interrupt reasons.
234 if (result == DOWNLOAD_INTERRUPT_REASON_FILE_FAILED) {
235 UMA_HISTOGRAM_CUSTOM_ENUMERATION(
236 "Download.MapWinShErrorFileFailed", code,
237 base::CustomHistogram::ArrayToCustomRanges(
238 kAllSpecialShFileOperationCodes,
239 arraysize(kAllSpecialShFileOperationCodes)));
242 if (result == DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED) {
243 UMA_HISTOGRAM_CUSTOM_ENUMERATION(
244 "Download.MapWinShErrorAccessDenied", code,
245 base::CustomHistogram::ArrayToCustomRanges(
246 kAllSpecialShFileOperationCodes,
247 arraysize(kAllSpecialShFileOperationCodes)));
250 if (result != DOWNLOAD_INTERRUPT_REASON_NONE)
251 return result;
253 // If not one of the above codes, it should be a standard Windows error code.
254 return ConvertNetErrorToInterruptReason(
255 net::MapSystemError(code), DOWNLOAD_INTERRUPT_FROM_DISK);
258 // Maps a return code from ScanAndSaveDownloadedFile() to a
259 // DownloadInterruptReason. The return code in |result| is usually from the
260 // final IAttachmentExecute::Save() call.
261 DownloadInterruptReason MapScanAndSaveErrorCodeToInterruptReason(
262 HRESULT result) {
263 if (SUCCEEDED(result))
264 return DOWNLOAD_INTERRUPT_REASON_NONE;
266 switch (result) {
267 case INET_E_SECURITY_PROBLEM: // 0x800c000e
268 // This is returned if the download was blocked due to security
269 // restrictions. E.g. if the source URL was in the Restricted Sites zone
270 // and downloads are blocked on that zone, then the download would be
271 // deleted and this error code is returned.
272 return DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED;
274 case E_FAIL: // 0x80004005
275 // Returned if an anti-virus product reports an infection in the
276 // downloaded file during IAE::Save().
277 return DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED;
279 default:
280 // Any other error that occurs during IAttachmentExecute::Save() likely
281 // indicates a problem with the security check, but not necessarily the
282 // download. See http://crbug.com/153212.
283 return DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED;
287 } // namespace
289 // Renames a file using the SHFileOperation API to ensure that the target file
290 // gets the correct default security descriptor in the new path.
291 // Returns a network error, or net::OK for success.
292 DownloadInterruptReason BaseFile::MoveFileAndAdjustPermissions(
293 const base::FilePath& new_path) {
294 base::ThreadRestrictions::AssertIOAllowed();
296 // The parameters to SHFileOperation must be terminated with 2 NULL chars.
297 base::FilePath::StringType source = full_path_.value();
298 base::FilePath::StringType target = new_path.value();
300 source.append(1, L'\0');
301 target.append(1, L'\0');
303 SHFILEOPSTRUCT move_info = {0};
304 move_info.wFunc = FO_MOVE;
305 move_info.pFrom = source.c_str();
306 move_info.pTo = target.c_str();
307 move_info.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI |
308 FOF_NOCONFIRMMKDIR | FOF_NOCOPYSECURITYATTRIBS;
310 int result = SHFileOperation(&move_info);
311 DownloadInterruptReason interrupt_reason = DOWNLOAD_INTERRUPT_REASON_NONE;
313 if (result == 0 && move_info.fAnyOperationsAborted)
314 interrupt_reason = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
315 else if (result != 0)
316 interrupt_reason = MapShFileOperationCodes(result);
318 if (interrupt_reason != DOWNLOAD_INTERRUPT_REASON_NONE)
319 return LogInterruptReason("SHFileOperation", result, interrupt_reason);
320 return interrupt_reason;
323 DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() {
324 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
325 DCHECK(!detached_);
327 bound_net_log_.BeginEvent(net::NetLog::TYPE_DOWNLOAD_FILE_ANNOTATED);
328 DownloadInterruptReason result = DOWNLOAD_INTERRUPT_REASON_NONE;
329 HRESULT hr = ScanAndSaveDownloadedFile(full_path_, source_url_);
331 // If the download file is missing after the call, then treat this as an
332 // interrupted download.
334 // If the ScanAndSaveDownloadedFile() call failed, but the downloaded file is
335 // still around, then don't interrupt the download. Attachment Execution
336 // Services deletes the submitted file if the downloaded file is blocked by
337 // policy or if it was found to be infected.
339 // If the file is still there, then the error could be due to AES not being
340 // available or some other error during the AES invocation. In either case,
341 // we don't surface the error to the user.
342 if (!file_util::PathExists(full_path_)) {
343 DCHECK(FAILED(hr));
344 result = MapScanAndSaveErrorCodeToInterruptReason(hr);
345 if (result == DOWNLOAD_INTERRUPT_REASON_NONE) {
346 RecordDownloadCount(FILE_MISSING_AFTER_SUCCESSFUL_SCAN_COUNT);
347 result = DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED;
349 LogInterruptReason("ScanAndSaveDownloadedFile", hr, result);
351 bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_ANNOTATED);
352 return result;
355 } // namespace content