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/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"
22
23namespace content {
24namespace {
25
26const 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,
56};
57
58// Maps the result of a call to |SHFileOperation()| onto a
59// |DownloadInterruptReason|.
60//
61// These return codes are *old* (as in, DOS era), and specific to
62// |SHFileOperation()|.
63// They do not appear in any windows header.
64//
65// See http://msdn.microsoft.com/en-us/library/bb762164(VS.85).aspx.
66DownloadInterruptReason MapShFileOperationCodes(int code) {
67  DownloadInterruptReason result = DOWNLOAD_INTERRUPT_REASON_NONE;
68
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;
81
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;
90
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;
97
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;
103
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;
110
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;
116
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;
122
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;
128
129    // The destination path is an existing file.
130    // DE_FLDDESTISFILE == 0x7E
131    case 0x7E:
132      result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
133      break;
134
135    // The destination path is an existing folder.
136    // DE_FILEDESTISFLD == 0x80
137    case 0x80:
138      result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
139      break;
140
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;
146
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;
152
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;
158
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;
164
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;
171
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;
177
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;
183
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;
189
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;
195
196    // An unspecified error occurred on the destination.
197    // XE_ERRORONDEST == 0x10000
198    case 0x10000:
199      result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
200      break;
201
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;
208
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;
215
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;
221
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;
227
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;
234
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;
240
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;
248
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;
254  }
255
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)));
263  }
264
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)));
271  }
272
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)));
279  }
280
281  if (result != DOWNLOAD_INTERRUPT_REASON_NONE)
282    return result;
283
284  // If not one of the above codes, it should be a standard Windows error code.
285  return ConvertFileErrorToInterruptReason(
286      base::File::OSErrorToFileError(code));
287}
288
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.
292DownloadInterruptReason MapScanAndSaveErrorCodeToInterruptReason(
293    HRESULT result) {
294  if (SUCCEEDED(result))
295    return DOWNLOAD_INTERRUPT_REASON_NONE;
296
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;
304
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;
309
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;
315  }
316}
317
318} // namespace
319
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.
323DownloadInterruptReason BaseFile::MoveFileAndAdjustPermissions(
324    const base::FilePath& new_path) {
325  base::ThreadRestrictions::AssertIOAllowed();
326
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();
330
331  source.append(1, L'\0');
332  target.append(1, L'\0');
333
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;
340
341  base::TimeTicks now = base::TimeTicks::Now();
342  int result = SHFileOperation(&move_info);
343  DownloadInterruptReason interrupt_reason = DOWNLOAD_INTERRUPT_REASON_NONE;
344
345  if (result == 0 && move_info.fAnyOperationsAborted)
346    interrupt_reason = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
347  else if (result != 0)
348    interrupt_reason = MapShFileOperationCodes(result);
349
350  if (interrupt_reason != DOWNLOAD_INTERRUPT_REASON_NONE)
351    return LogInterruptReason("SHFileOperation", result, interrupt_reason);
352  return interrupt_reason;
353}
354
355DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() {
356  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
357  DCHECK(!detached_);
358
359  bound_net_log_.BeginEvent(net::NetLog::TYPE_DOWNLOAD_FILE_ANNOTATED);
360  DownloadInterruptReason result = DOWNLOAD_INTERRUPT_REASON_NONE;
361  std::string braces_guid = "{" + client_guid_ + "}";
362  GUID guid = GUID_NULL;
363  if (base::IsValidGUID(client_guid_)) {
364    HRESULT hr = CLSIDFromString(
365        base::UTF8ToUTF16(braces_guid).c_str(), &guid);
366    if (FAILED(hr))
367      guid = GUID_NULL;
368  }
369
370  HRESULT hr = AVScanFile(full_path_, source_url_.spec(), guid);
371
372  // If the download file is missing after the call, then treat this as an
373  // interrupted download.
374  //
375  // If the ScanAndSaveDownloadedFile() call failed, but the downloaded file is
376  // still around, then don't interrupt the download. Attachment Execution
377  // Services deletes the submitted file if the downloaded file is blocked by
378  // policy or if it was found to be infected.
379  //
380  // If the file is still there, then the error could be due to AES not being
381  // available or some other error during the AES invocation. In either case,
382  // we don't surface the error to the user.
383  if (!base::PathExists(full_path_)) {
384    DCHECK(FAILED(hr));
385    result = MapScanAndSaveErrorCodeToInterruptReason(hr);
386    if (result == DOWNLOAD_INTERRUPT_REASON_NONE) {
387      RecordDownloadCount(FILE_MISSING_AFTER_SUCCESSFUL_SCAN_COUNT);
388      result = DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED;
389    }
390    LogInterruptReason("ScanAndSaveDownloadedFile", hr, result);
391  }
392  bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_ANNOTATED);
393  return result;
394}
395
396}  // namespace content
397