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 368