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