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/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