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 23 namespace content { 24 namespace { 25 26 const 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. 66 DownloadInterruptReason 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. 292 DownloadInterruptReason 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. 323 DownloadInterruptReason 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 355 DownloadInterruptReason 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