base_file_win.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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 <shellapi.h>
9
10#include "base/file_util.h"
11#include "base/threading/thread_restrictions.h"
12#include "base/utf_string_conversions.h"
13#include "content/browser/download/download_interrupt_reasons_impl.h"
14#include "content/browser/download/download_stats.h"
15#include "content/browser/safe_util_win.h"
16#include "content/public/browser/browser_thread.h"
17
18namespace content {
19namespace {
20
21// Maps the result of a call to |SHFileOperation()| onto a
22// |DownloadInterruptReason|.
23//
24// These return codes are *old* (as in, DOS era), and specific to
25// |SHFileOperation()|.
26// They do not appear in any windows header.
27//
28// See http://msdn.microsoft.com/en-us/library/bb762164(VS.85).aspx.
29DownloadInterruptReason MapShFileOperationCodes(int code) {
30  // Check these pre-Win32 error codes first, then check for matches
31  // in Winerror.h.
32
33  switch (code) {
34    // The source and destination files are the same file.
35    // DE_SAMEFILE == 0x71
36    case 0x71: return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
37
38    // The operation was canceled by the user, or silently canceled if the
39    // appropriate flags were supplied to SHFileOperation.
40    // DE_OPCANCELLED == 0x75
41    case 0x75: return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
42
43    // Security settings denied access to the source.
44    // DE_ACCESSDENIEDSRC == 0x78
45    case 0x78: return DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
46
47    // The source or destination path exceeded or would exceed MAX_PATH.
48    // DE_PATHTOODEEP == 0x79
49    case 0x79: return DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
50
51    // The path in the source or destination or both was invalid.
52    // DE_INVALIDFILES == 0x7C
53    case 0x7C: return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
54
55    // The destination path is an existing file.
56    // DE_FLDDESTISFILE == 0x7E
57    case 0x7E: return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
58
59    // The destination path is an existing folder.
60    // DE_FILEDESTISFLD == 0x80
61    case 0x80: return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
62
63    // The name of the file exceeds MAX_PATH.
64    // DE_FILENAMETOOLONG == 0x81
65    case 0x81: return DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
66
67    // The destination is a read-only CD-ROM, possibly unformatted.
68    // DE_DEST_IS_CDROM == 0x82
69    case 0x82: return DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
70
71    // The destination is a read-only DVD, possibly unformatted.
72    // DE_DEST_IS_DVD == 0x83
73    case 0x83: return DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
74
75    // The destination is a writable CD-ROM, possibly unformatted.
76    // DE_DEST_IS_CDRECORD == 0x84
77    case 0x84: return DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
78
79    // The file involved in the operation is too large for the destination
80    // media or file system.
81    // DE_FILE_TOO_LARGE == 0x85
82    case 0x85: return DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE;
83
84    // The source is a read-only CD-ROM, possibly unformatted.
85    // DE_SRC_IS_CDROM == 0x86
86    case 0x86: return DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
87
88    // The source is a read-only DVD, possibly unformatted.
89    // DE_SRC_IS_DVD == 0x87
90    case 0x87: return DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
91
92    // The source is a writable CD-ROM, possibly unformatted.
93    // DE_SRC_IS_CDRECORD == 0x88
94    case 0x88: return DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
95
96    // MAX_PATH was exceeded during the operation.
97    // DE_ERROR_MAX == 0xB7
98    case 0xB7: return DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
99
100    // An unspecified error occurred on the destination.
101    // XE_ERRORONDEST == 0x10000
102    case 0x10000: return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
103
104    // Multiple file paths were specified in the source buffer, but only one
105    // destination file path.
106    // DE_MANYSRC1DEST == 0x72
107    case 0x72: return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
108
109    // Rename operation was specified but the destination path is
110    // a different directory. Use the move operation instead.
111    // DE_DIFFDIR == 0x73
112    case 0x73: return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
113
114    // The source is a root directory, which cannot be moved or renamed.
115    // DE_ROOTDIR == 0x74
116    case 0x74: return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
117
118    // The destination is a subtree of the source.
119    // DE_DESTSUBTREE == 0x76
120    case 0x76: return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
121
122    // The operation involved multiple destination paths,
123    // which can fail in the case of a move operation.
124    // DE_MANYDEST == 0x7A
125    case 0x7A: return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
126
127    // The source and destination have the same parent folder.
128    // DE_DESTSAMETREE == 0x7D
129    case 0x7D: return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
130
131    // An unknown error occurred.  This is typically due to an invalid path in
132    // the source or destination.  This error does not occur on Windows Vista
133    // and later.
134    // DE_UNKNOWN_ERROR == 0x402
135    case 0x402: return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
136
137    // Destination is a root directory and cannot be renamed.
138    // DE_ROOTDIR | ERRORONDEST == 0x10074
139    case 0x10074: return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
140  }
141
142  // If not one of the above codes, it should be a standard Windows error code.
143  return ConvertNetErrorToInterruptReason(
144      net::MapSystemError(code), DOWNLOAD_INTERRUPT_FROM_DISK);
145}
146
147// Maps a return code from ScanAndSaveDownloadedFile() to a
148// DownloadInterruptReason. The return code in |result| is usually from the
149// final IAttachmentExecute::Save() call.
150DownloadInterruptReason MapScanAndSaveErrorCodeToInterruptReason(
151    HRESULT result) {
152  if (SUCCEEDED(result))
153    return DOWNLOAD_INTERRUPT_REASON_NONE;
154
155  switch (result) {
156    case INET_E_SECURITY_PROBLEM:       // 0x800c000e
157      // This is returned if the download was blocked due to security
158      // restrictions. E.g. if the source URL was in the Restricted Sites zone
159      // and downloads are blocked on that zone, then the download would be
160      // deleted and this error code is returned.
161      return DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED;
162
163    case E_FAIL:                        // 0x80004005
164      // Returned if an anti-virus product reports an infection in the
165      // downloaded file during IAE::Save().
166      return DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED;
167
168    default:
169      // Any other error that occurs during IAttachmentExecute::Save() likely
170      // indicates a problem with the security check, but not necessarily the
171      // download. See http://crbug.com/153212.
172      return DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED;
173  }
174}
175
176} // namespace
177
178// Renames a file using the SHFileOperation API to ensure that the target file
179// gets the correct default security descriptor in the new path.
180// Returns a network error, or net::OK for success.
181DownloadInterruptReason BaseFile::MoveFileAndAdjustPermissions(
182    const FilePath& new_path) {
183  base::ThreadRestrictions::AssertIOAllowed();
184
185  // The parameters to SHFileOperation must be terminated with 2 NULL chars.
186  FilePath::StringType source = full_path_.value();
187  FilePath::StringType target = new_path.value();
188
189  source.append(1, L'\0');
190  target.append(1, L'\0');
191
192  SHFILEOPSTRUCT move_info = {0};
193  move_info.wFunc = FO_MOVE;
194  move_info.pFrom = source.c_str();
195  move_info.pTo = target.c_str();
196  move_info.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI |
197      FOF_NOCONFIRMMKDIR | FOF_NOCOPYSECURITYATTRIBS;
198
199  int result = SHFileOperation(&move_info);
200  DownloadInterruptReason interrupt_reason = DOWNLOAD_INTERRUPT_REASON_NONE;
201
202  if (result == 0 && move_info.fAnyOperationsAborted)
203    interrupt_reason = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
204  else if (result != 0)
205    interrupt_reason = MapShFileOperationCodes(result);
206
207  if (interrupt_reason != DOWNLOAD_INTERRUPT_REASON_NONE)
208    return LogInterruptReason("SHFileOperation", result, interrupt_reason);
209  return interrupt_reason;
210}
211
212DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() {
213  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
214  DCHECK(!detached_);
215
216  bound_net_log_.BeginEvent(net::NetLog::TYPE_DOWNLOAD_FILE_ANNOTATED);
217  DownloadInterruptReason result = DOWNLOAD_INTERRUPT_REASON_NONE;
218  HRESULT hr = ScanAndSaveDownloadedFile(full_path_, source_url_);
219
220  // If the download file is missing after the call, then treat this as an
221  // interrupted download.
222  //
223  // If the ScanAndSaveDownloadedFile() call failed, but the downloaded file is
224  // still around, then don't interrupt the download. Attachment Execution
225  // Services deletes the submitted file if the downloaded file is blocked by
226  // policy or if it was found to be infected.
227  //
228  // If the file is still there, then the error could be due to AES not being
229  // available or some other error during the AES invocation. In either case,
230  // we don't surface the error to the user.
231  if (!file_util::PathExists(full_path_)) {
232    DCHECK(FAILED(hr));
233    result = MapScanAndSaveErrorCodeToInterruptReason(hr);
234    if (result == DOWNLOAD_INTERRUPT_REASON_NONE) {
235      RecordDownloadCount(FILE_MISSING_AFTER_SUCCESSFUL_SCAN_COUNT);
236      result = DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED;
237    }
238    LogInterruptReason("ScanAndSaveDownloadedFile", hr, result);
239  }
240  bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_ANNOTATED);
241  return result;
242}
243
244}  // namespace content
245