- add sources.
[platform/framework/web/crosswalk.git] / src / content / browser / download / base_file_win.cc
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
22 namespace content {
23 namespace {
24
25 const 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.
63 DownloadInterruptReason 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.
264 DownloadInterruptReason 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.
295 DownloadInterruptReason 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
326 DownloadInterruptReason 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