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 "components/download/public/common/base_file.h"
6 
7 #include <windows.h>
8 
9 #include <objbase.h>
10 #include <shellapi.h>
11 #include <shobjidl.h>
12 #include <wrl/client.h>
13 #include <wrl/implements.h>
14 
15 #include "base/threading/scoped_blocking_call.h"
16 #include "base/win/com_init_util.h"
17 #include "components/download/public/common/download_interrupt_reasons_utils.h"
18 #include "components/download/public/common/download_stats.h"
19 
20 namespace download {
21 namespace {
22 
23 // By and large the errors seen here are listed in sherrors.h, included from
24 // shobjidl.h.
HRESULTToDownloadInterruptReason(HRESULT hr)25 DownloadInterruptReason HRESULTToDownloadInterruptReason(HRESULT hr) {
26   // S_OK, other success values are aggregated here.
27   if (SUCCEEDED(hr) && HRESULT_FACILITY(hr) != FACILITY_SHELL)
28     return DOWNLOAD_INTERRUPT_REASON_NONE;
29 
30   DownloadInterruptReason reason = DOWNLOAD_INTERRUPT_REASON_NONE;
31   // All of the remaining HRESULTs to be considered are either from the copy
32   // engine, or are unknown; we've got handling for all the copy engine errors,
33   // and otherwise we'll just return the generic error reason.
34   switch (hr) {
35     case COPYENGINE_S_YES:
36     case COPYENGINE_S_NOT_HANDLED:
37     case COPYENGINE_S_USER_RETRY:
38     case COPYENGINE_S_MERGE:
39     case COPYENGINE_S_DONT_PROCESS_CHILDREN:
40     case COPYENGINE_S_ALREADY_DONE:
41     case COPYENGINE_S_PENDING:
42     case COPYENGINE_S_KEEP_BOTH:
43     case COPYENGINE_S_COLLISIONRESOLVED:
44     case COPYENGINE_S_PROGRESS_PAUSE:
45       return DOWNLOAD_INTERRUPT_REASON_NONE;
46 
47     case COPYENGINE_S_CLOSE_PROGRAM:
48       // Like sharing violations, another process is using the file we want to
49       // touch, so wait for it to close.
50     case COPYENGINE_E_SHARING_VIOLATION_SRC:
51     case COPYENGINE_E_SHARING_VIOLATION_DEST:
52       // Sharing violations are encountered when some other process has a file
53       // open; often it's antivirus scanning, and this error can be treated as
54       // transient, as we assume eventually the other process will close its
55       // handle.
56       reason = DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR;
57       break;
58 
59     case COPYENGINE_E_PATH_TOO_DEEP_DEST:
60     case COPYENGINE_E_PATH_TOO_DEEP_SRC:
61     case COPYENGINE_E_NEWFILE_NAME_TOO_LONG:
62     case COPYENGINE_E_NEWFOLDER_NAME_TOO_LONG:
63       // Any of these errors can be encountered if MAXPATH is hit while writing
64       // out a filename. This can happen really just about anywhere.
65       reason = DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
66       break;
67 
68     case COPYENGINE_S_USER_IGNORED:
69       // On Windows 7, inability to access a file may return "user ignored"
70       // instead of correctly reporting the failure.
71     case COPYENGINE_E_ACCESS_DENIED_DEST:
72     case COPYENGINE_E_ACCESS_DENIED_SRC:
73       // There's a security problem, or the file is otherwise inaccessible.
74     case COPYENGINE_E_DEST_IS_RO_CD:
75     case COPYENGINE_E_DEST_IS_RW_CD:
76     case COPYENGINE_E_DEST_IS_R_CD:
77     case COPYENGINE_E_DEST_IS_RO_DVD:
78     case COPYENGINE_E_DEST_IS_RW_DVD:
79     case COPYENGINE_E_DEST_IS_R_DVD:
80     case COPYENGINE_E_SRC_IS_RO_CD:
81     case COPYENGINE_E_SRC_IS_RW_CD:
82     case COPYENGINE_E_SRC_IS_R_CD:
83     case COPYENGINE_E_SRC_IS_RO_DVD:
84     case COPYENGINE_E_SRC_IS_RW_DVD:
85     case COPYENGINE_E_SRC_IS_R_DVD:
86       // When the source is actually a disk, and a Move is attempted, it can't
87       // delete the source. This is unlikely to be encountered in our scenario.
88       reason = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
89       break;
90 
91     case COPYENGINE_E_FILE_TOO_LARGE:
92     case COPYENGINE_E_DISK_FULL:
93     case COPYENGINE_E_REMOVABLE_FULL:
94     case COPYENGINE_E_DISK_FULL_CLEAN:
95       // No room for the file in the destination location.
96       reason = DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE;
97       break;
98 
99     case COPYENGINE_E_ALREADY_EXISTS_NORMAL:
100     case COPYENGINE_E_ALREADY_EXISTS_READONLY:
101     case COPYENGINE_E_ALREADY_EXISTS_SYSTEM:
102     case COPYENGINE_E_ALREADY_EXISTS_FOLDER:
103       // The destination already exists and can't be replaced.
104     case COPYENGINE_E_INVALID_FILES_SRC:
105     case COPYENGINE_E_INVALID_FILES_DEST:
106       // Either the source or destination file was invalid.
107     case COPYENGINE_E_STREAM_LOSS:
108     case COPYENGINE_E_EA_LOSS:
109     case COPYENGINE_E_PROPERTY_LOSS:
110     case COPYENGINE_E_PROPERTIES_LOSS:
111     case COPYENGINE_E_ENCRYPTION_LOSS:
112       // The destination can't support some functionality that the file needs.
113       // The interesting one here is E_STREAM_LOSS, especially with MOTW.
114     case COPYENGINE_E_FLD_IS_FILE_DEST:
115     case COPYENGINE_E_FILE_IS_FLD_DEST:
116       // There is an existing file with the same name as a new folder, and
117       // vice versa.
118     case COPYENGINE_E_ROOT_DIR_DEST:
119     case COPYENGINE_E_ROOT_DIR_SRC:
120     case COPYENGINE_E_DIFF_DIR:
121     case COPYENGINE_E_SAME_FILE:
122     case COPYENGINE_E_MANY_SRC_1_DEST:
123     case COPYENGINE_E_DEST_SUBTREE:
124     case COPYENGINE_E_DEST_SAME_TREE:
125     case COPYENGINE_E_USER_CANCELLED:
126     case COPYENGINE_E_CANCELLED:
127     case COPYENGINE_E_REQUIRES_ELEVATION:
128       reason = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
129       break;
130   }
131 
132   if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
133     RecordWinFileMoveError(HRESULT_CODE(hr));
134     return reason;
135   }
136 
137   // Copy operations may still return Win32 error codes, so handle those here.
138   if (HRESULT_FACILITY(hr) == FACILITY_WIN32) {
139     return ConvertFileErrorToInterruptReason(
140         base::File::OSErrorToFileError(HRESULT_CODE(hr)));
141   }
142 
143   return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
144 }
145 
146 class FileOperationProgressSink
147     : public Microsoft::WRL::RuntimeClass<
148           Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
149           IFileOperationProgressSink> {
150  public:
151   FileOperationProgressSink() = default;
152 
GetOperationResult()153   HRESULT GetOperationResult() { return result_; }
154 
155   // IFileOperationProgressSink:
FinishOperations(HRESULT hr)156   IFACEMETHODIMP FinishOperations(HRESULT hr) override {
157     // If a failure has already been captured, don't bother overriding it. That
158     // way, the original failure can be propagated; in the event that the new
159     // HRESULT is also a success, overwriting will not harm anything and
160     // captures the final state of the whole operation.
161     if (SUCCEEDED(result_))
162       result_ = hr;
163     return S_OK;
164   }
165 
PauseTimer()166   IFACEMETHODIMP PauseTimer() override { return S_OK; }
PostCopyItem(DWORD,IShellItem *,IShellItem *,PCWSTR,HRESULT,IShellItem *)167   IFACEMETHODIMP PostCopyItem(DWORD,
168                               IShellItem*,
169                               IShellItem*,
170                               PCWSTR,
171                               HRESULT,
172                               IShellItem*) override {
173     return E_NOTIMPL;
174   }
PostDeleteItem(DWORD,IShellItem *,HRESULT,IShellItem *)175   IFACEMETHODIMP PostDeleteItem(DWORD,
176                                 IShellItem*,
177                                 HRESULT,
178                                 IShellItem*) override {
179     return E_NOTIMPL;
180   }
PostMoveItem(DWORD,IShellItem *,IShellItem *,PCWSTR,HRESULT hr,IShellItem *)181   IFACEMETHODIMP PostMoveItem(DWORD,
182                               IShellItem*,
183                               IShellItem*,
184                               PCWSTR,
185                               HRESULT hr,
186                               IShellItem*) override {
187     // Like in FinishOperations, overwriting with a different success value
188     // does not have a negative impact, but replacing an existing failure will
189     // cause issues.
190     if (SUCCEEDED(result_))
191       result_ = hr;
192     return S_OK;
193   }
PostNewItem(DWORD,IShellItem *,PCWSTR,PCWSTR,DWORD,HRESULT,IShellItem *)194   IFACEMETHODIMP PostNewItem(DWORD,
195                              IShellItem*,
196                              PCWSTR,
197                              PCWSTR,
198                              DWORD,
199                              HRESULT,
200                              IShellItem*) override {
201     return E_NOTIMPL;
202   }
203   IFACEMETHODIMP
PostRenameItem(DWORD,IShellItem *,PCWSTR,HRESULT,IShellItem *)204   PostRenameItem(DWORD, IShellItem*, PCWSTR, HRESULT, IShellItem*) override {
205     return E_NOTIMPL;
206   }
PreCopyItem(DWORD,IShellItem *,IShellItem *,PCWSTR)207   IFACEMETHODIMP PreCopyItem(DWORD, IShellItem*, IShellItem*, PCWSTR) override {
208     return E_NOTIMPL;
209   }
PreDeleteItem(DWORD,IShellItem *)210   IFACEMETHODIMP PreDeleteItem(DWORD, IShellItem*) override {
211     return E_NOTIMPL;
212   }
PreMoveItem(DWORD,IShellItem *,IShellItem *,PCWSTR)213   IFACEMETHODIMP PreMoveItem(DWORD, IShellItem*, IShellItem*, PCWSTR) override {
214     return S_OK;
215   }
PreNewItem(DWORD,IShellItem *,PCWSTR)216   IFACEMETHODIMP PreNewItem(DWORD, IShellItem*, PCWSTR) override {
217     return E_NOTIMPL;
218   }
PreRenameItem(DWORD,IShellItem *,PCWSTR)219   IFACEMETHODIMP PreRenameItem(DWORD, IShellItem*, PCWSTR) override {
220     return E_NOTIMPL;
221   }
ResetTimer()222   IFACEMETHODIMP ResetTimer() override { return S_OK; }
ResumeTimer()223   IFACEMETHODIMP ResumeTimer() override { return S_OK; }
StartOperations()224   IFACEMETHODIMP StartOperations() override { return S_OK; }
UpdateProgress(UINT,UINT)225   IFACEMETHODIMP UpdateProgress(UINT, UINT) override { return S_OK; }
226 
227  protected:
228   ~FileOperationProgressSink() override = default;
229 
230  private:
231   HRESULT result_ = S_OK;
232 
233   DISALLOW_COPY_AND_ASSIGN(FileOperationProgressSink);
234 };
235 
236 }  // namespace
237 
238 // Renames a file using IFileOperation::MoveItem() to ensure that the target
239 // file gets the correct default security descriptor in the new path.
240 // Returns a network error, or net::OK for success.
MoveFileAndAdjustPermissions(const base::FilePath & new_path)241 DownloadInterruptReason BaseFile::MoveFileAndAdjustPermissions(
242     const base::FilePath& new_path) {
243   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
244                                                 base::BlockingType::MAY_BLOCK);
245 
246   base::win::AssertComInitialized();
247   Microsoft::WRL::ComPtr<IShellItem> original_path;
248   HRESULT hr = SHCreateItemFromParsingName(full_path_.value().c_str(), nullptr,
249                                            IID_PPV_ARGS(&original_path));
250 
251   // |new_path| can be broken down to provide the new folder, as well as the
252   // new filename. We'll start with the folder, which the caller should ensure
253   // exists.
254   Microsoft::WRL::ComPtr<IShellItem> destination_folder;
255   if (SUCCEEDED(hr)) {
256     hr =
257         SHCreateItemFromParsingName(new_path.DirName().value().c_str(), nullptr,
258                                     IID_PPV_ARGS(&destination_folder));
259   }
260 
261   Microsoft::WRL::ComPtr<IFileOperation> file_operation;
262   if (SUCCEEDED(hr)) {
263     hr = CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_INPROC_SERVER,
264                           IID_PPV_ARGS(&file_operation));
265   }
266 
267   if (SUCCEEDED(hr)) {
268     // Don't show any UI, don't migrate security attributes (use the
269     // destination's attributes), and stop on first error-retaining the original
270     // failure reason.
271     hr = file_operation->SetOperationFlags(
272         FOF_NO_UI | FOF_NOCOPYSECURITYATTRIBS | FOFX_EARLYFAILURE);
273   }
274 
275   Microsoft::WRL::ComPtr<FileOperationProgressSink> sink =
276       Microsoft::WRL::Make<FileOperationProgressSink>();
277   if (SUCCEEDED(hr)) {
278     hr = file_operation->MoveItem(original_path.Get(), destination_folder.Get(),
279                                   new_path.BaseName().value().c_str(),
280                                   sink.Get());
281   }
282 
283   if (SUCCEEDED(hr))
284     hr = file_operation->PerformOperations();
285 
286   if (SUCCEEDED(hr))
287     hr = sink->GetOperationResult();
288 
289   // Convert HRESULT to DownloadInterruptReason.
290   DownloadInterruptReason interrupt_reason =
291       HRESULTToDownloadInterruptReason(hr);
292 
293   if (interrupt_reason == DOWNLOAD_INTERRUPT_REASON_NONE) {
294     // The operation could still have been aborted; we can't get a better reason
295     // at this point, but we've got more information to go by.
296     BOOL any_operations_aborted = TRUE;
297     file_operation->GetAnyOperationsAborted(&any_operations_aborted);
298     if (any_operations_aborted)
299       interrupt_reason = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
300   } else {
301     return LogInterruptReason("IFileOperation::MoveItem", hr, interrupt_reason);
302   }
303 
304   return interrupt_reason;
305 }
306 
307 }  // namespace download
308