Home | History | Annotate | Download | only in download
      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