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 "base/bind.h"
      8 #include "base/files/file.h"
      9 #include "base/files/file_util.h"
     10 #include "base/format_macros.h"
     11 #include "base/logging.h"
     12 #include "base/pickle.h"
     13 #include "base/strings/stringprintf.h"
     14 #include "base/threading/thread_restrictions.h"
     15 #include "content/browser/download/download_interrupt_reasons_impl.h"
     16 #include "content/browser/download/download_net_log_parameters.h"
     17 #include "content/browser/download/download_stats.h"
     18 #include "content/public/browser/browser_thread.h"
     19 #include "content/public/browser/content_browser_client.h"
     20 #include "crypto/secure_hash.h"
     21 #include "net/base/net_errors.h"
     22 
     23 namespace content {
     24 
     25 // This will initialize the entire array to zero.
     26 const unsigned char BaseFile::kEmptySha256Hash[] = { 0 };
     27 
     28 BaseFile::BaseFile(const base::FilePath& full_path,
     29                    const GURL& source_url,
     30                    const GURL& referrer_url,
     31                    int64 received_bytes,
     32                    bool calculate_hash,
     33                    const std::string& hash_state_bytes,
     34                    base::File file,
     35                    const net::BoundNetLog& bound_net_log)
     36     : full_path_(full_path),
     37       source_url_(source_url),
     38       referrer_url_(referrer_url),
     39       file_(file.Pass()),
     40       bytes_so_far_(received_bytes),
     41       start_tick_(base::TimeTicks::Now()),
     42       calculate_hash_(calculate_hash),
     43       detached_(false),
     44       bound_net_log_(bound_net_log) {
     45   memcpy(sha256_hash_, kEmptySha256Hash, crypto::kSHA256Length);
     46   if (calculate_hash_) {
     47     secure_hash_.reset(crypto::SecureHash::Create(crypto::SecureHash::SHA256));
     48     if ((bytes_so_far_ > 0) &&  // Not starting at the beginning.
     49         (!IsEmptyHash(hash_state_bytes))) {
     50       Pickle hash_state(hash_state_bytes.c_str(), hash_state_bytes.size());
     51       PickleIterator data_iterator(hash_state);
     52       secure_hash_->Deserialize(&data_iterator);
     53     }
     54   }
     55 }
     56 
     57 BaseFile::~BaseFile() {
     58   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
     59   if (detached_)
     60     Close();
     61   else
     62     Cancel();  // Will delete the file.
     63 }
     64 
     65 DownloadInterruptReason BaseFile::Initialize(
     66     const base::FilePath& default_directory) {
     67   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
     68   DCHECK(!detached_);
     69 
     70   if (full_path_.empty()) {
     71     base::FilePath initial_directory(default_directory);
     72     base::FilePath temp_file;
     73     if (initial_directory.empty()) {
     74       initial_directory =
     75           GetContentClient()->browser()->GetDefaultDownloadDirectory();
     76     }
     77     // |initial_directory| can still be empty if ContentBrowserClient returned
     78     // an empty path for the downloads directory.
     79     if ((initial_directory.empty() ||
     80          !base::CreateTemporaryFileInDir(initial_directory, &temp_file)) &&
     81         !base::CreateTemporaryFile(&temp_file)) {
     82       return LogInterruptReason("Unable to create", 0,
     83                                 DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
     84     }
     85     full_path_ = temp_file;
     86   }
     87 
     88   return Open();
     89 }
     90 
     91 DownloadInterruptReason BaseFile::AppendDataToFile(const char* data,
     92                                                    size_t data_len) {
     93   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
     94   DCHECK(!detached_);
     95 
     96   // NOTE(benwells): The above DCHECK won't be present in release builds,
     97   // so we log any occurences to see how common this error is in the wild.
     98   if (detached_)
     99     RecordDownloadCount(APPEND_TO_DETACHED_FILE_COUNT);
    100 
    101   if (!file_.IsValid())
    102     return LogInterruptReason("No file stream on append", 0,
    103                               DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
    104 
    105   // TODO(phajdan.jr): get rid of this check.
    106   if (data_len == 0)
    107     return DOWNLOAD_INTERRUPT_REASON_NONE;
    108 
    109   // The Write call below is not guaranteed to write all the data.
    110   size_t write_count = 0;
    111   size_t len = data_len;
    112   const char* current_data = data;
    113   while (len > 0) {
    114     write_count++;
    115     int write_result = file_.WriteAtCurrentPos(current_data, len);
    116     DCHECK_NE(0, write_result);
    117 
    118     // Report errors on file writes.
    119     if (write_result < 0)
    120       return LogSystemError("Write", logging::GetLastSystemErrorCode());
    121 
    122     // Update status.
    123     size_t write_size = static_cast<size_t>(write_result);
    124     DCHECK_LE(write_size, len);
    125     len -= write_size;
    126     current_data += write_size;
    127     bytes_so_far_ += write_size;
    128   }
    129 
    130   RecordDownloadWriteSize(data_len);
    131   RecordDownloadWriteLoopCount(write_count);
    132 
    133   if (calculate_hash_)
    134     secure_hash_->Update(data, data_len);
    135 
    136   return DOWNLOAD_INTERRUPT_REASON_NONE;
    137 }
    138 
    139 DownloadInterruptReason BaseFile::Rename(const base::FilePath& new_path) {
    140   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    141   DownloadInterruptReason rename_result = DOWNLOAD_INTERRUPT_REASON_NONE;
    142 
    143   // If the new path is same as the old one, there is no need to perform the
    144   // following renaming logic.
    145   if (new_path == full_path_)
    146     return DOWNLOAD_INTERRUPT_REASON_NONE;
    147 
    148   // Save the information whether the download is in progress because
    149   // it will be overwritten by closing the file.
    150   bool was_in_progress = in_progress();
    151 
    152   bound_net_log_.BeginEvent(
    153       net::NetLog::TYPE_DOWNLOAD_FILE_RENAMED,
    154       base::Bind(&FileRenamedNetLogCallback, &full_path_, &new_path));
    155   Close();
    156   base::CreateDirectory(new_path.DirName());
    157 
    158   // A simple rename wouldn't work here since we want the file to have
    159   // permissions / security descriptors that makes sense in the new directory.
    160   rename_result = MoveFileAndAdjustPermissions(new_path);
    161 
    162   if (rename_result == DOWNLOAD_INTERRUPT_REASON_NONE)
    163     full_path_ = new_path;
    164 
    165   // Re-open the file if we were still using it regardless of the interrupt
    166   // reason.
    167   DownloadInterruptReason open_result = DOWNLOAD_INTERRUPT_REASON_NONE;
    168   if (was_in_progress)
    169     open_result = Open();
    170 
    171   bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_RENAMED);
    172   return rename_result == DOWNLOAD_INTERRUPT_REASON_NONE ? open_result
    173                                                          : rename_result;
    174 }
    175 
    176 void BaseFile::Detach() {
    177   detached_ = true;
    178   bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DETACHED);
    179 }
    180 
    181 void BaseFile::Cancel() {
    182   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    183   DCHECK(!detached_);
    184 
    185   bound_net_log_.AddEvent(net::NetLog::TYPE_CANCELLED);
    186 
    187   Close();
    188 
    189   if (!full_path_.empty()) {
    190     bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DELETED);
    191     base::DeleteFile(full_path_, false);
    192   }
    193 
    194   Detach();
    195 }
    196 
    197 void BaseFile::Finish() {
    198   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    199 
    200   if (calculate_hash_)
    201     secure_hash_->Finish(sha256_hash_, crypto::kSHA256Length);
    202 
    203   Close();
    204 }
    205 
    206 void BaseFile::SetClientGuid(const std::string& guid) {
    207   client_guid_ = guid;
    208 }
    209 
    210 // OS_WIN, OS_MACOSX and OS_LINUX have specialized implementations.
    211 #if !defined(OS_WIN) && !defined(OS_MACOSX) && !defined(OS_LINUX)
    212 DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() {
    213   return DOWNLOAD_INTERRUPT_REASON_NONE;
    214 }
    215 #endif
    216 
    217 bool BaseFile::GetHash(std::string* hash) {
    218   DCHECK(!detached_);
    219   hash->assign(reinterpret_cast<const char*>(sha256_hash_),
    220                sizeof(sha256_hash_));
    221   return (calculate_hash_ && !in_progress());
    222 }
    223 
    224 std::string BaseFile::GetHashState() {
    225   if (!calculate_hash_)
    226     return std::string();
    227 
    228   Pickle hash_state;
    229   if (!secure_hash_->Serialize(&hash_state))
    230     return std::string();
    231 
    232   return std::string(reinterpret_cast<const char*>(hash_state.data()),
    233                      hash_state.size());
    234 }
    235 
    236 // static
    237 bool BaseFile::IsEmptyHash(const std::string& hash) {
    238   return (hash.size() == crypto::kSHA256Length &&
    239           0 == memcmp(hash.data(), kEmptySha256Hash, crypto::kSHA256Length));
    240 }
    241 
    242 std::string BaseFile::DebugString() const {
    243   return base::StringPrintf("{ source_url_ = \"%s\""
    244                             " full_path_ = \"%" PRFilePath "\""
    245                             " bytes_so_far_ = %" PRId64
    246                             " detached_ = %c }",
    247                             source_url_.spec().c_str(),
    248                             full_path_.value().c_str(),
    249                             bytes_so_far_,
    250                             detached_ ? 'T' : 'F');
    251 }
    252 
    253 DownloadInterruptReason BaseFile::Open() {
    254   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    255   DCHECK(!detached_);
    256   DCHECK(!full_path_.empty());
    257 
    258   bound_net_log_.BeginEvent(
    259       net::NetLog::TYPE_DOWNLOAD_FILE_OPENED,
    260       base::Bind(&FileOpenedNetLogCallback, &full_path_, bytes_so_far_));
    261 
    262   // Create a new file if it is not provided.
    263   if (!file_.IsValid()) {
    264     file_.Initialize(
    265         full_path_, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_WRITE);
    266     if (!file_.IsValid()) {
    267       return LogNetError("Open",
    268                          net::FileErrorToNetError(file_.error_details()));
    269     }
    270   }
    271 
    272   // We may be re-opening the file after rename. Always make sure we're
    273   // writing at the end of the file.
    274   int64 file_size = file_.Seek(base::File::FROM_END, 0);
    275   if (file_size < 0) {
    276     logging::SystemErrorCode error = logging::GetLastSystemErrorCode();
    277     ClearFile();
    278     return LogSystemError("Seek", error);
    279   } else if (file_size > bytes_so_far_) {
    280     // The file is larger than we expected.
    281     // This is OK, as long as we don't use the extra.
    282     // Truncate the file.
    283     if (!file_.SetLength(bytes_so_far_) ||
    284         file_.Seek(base::File::FROM_BEGIN, bytes_so_far_) != bytes_so_far_) {
    285       logging::SystemErrorCode error = logging::GetLastSystemErrorCode();
    286       ClearFile();
    287       return LogSystemError("Truncate",  error);
    288     }
    289   } else if (file_size < bytes_so_far_) {
    290     // The file is shorter than we expected.  Our hashes won't be valid.
    291     ClearFile();
    292     return LogInterruptReason("Unable to seek to last written point", 0,
    293                               DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT);
    294   }
    295 
    296   return DOWNLOAD_INTERRUPT_REASON_NONE;
    297 }
    298 
    299 void BaseFile::Close() {
    300   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    301 
    302   bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_CLOSED);
    303 
    304   if (file_.IsValid()) {
    305     // Currently we don't really care about the return value, since if it fails
    306     // theres not much we can do.  But we might in the future.
    307     file_.Flush();
    308     ClearFile();
    309   }
    310 }
    311 
    312 void BaseFile::ClearFile() {
    313   // This should only be called when we have a stream.
    314   DCHECK(file_.IsValid());
    315   file_.Close();
    316   bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_OPENED);
    317 }
    318 
    319 DownloadInterruptReason BaseFile::LogNetError(
    320     const char* operation,
    321     net::Error error) {
    322   bound_net_log_.AddEvent(
    323       net::NetLog::TYPE_DOWNLOAD_FILE_ERROR,
    324       base::Bind(&FileErrorNetLogCallback, operation, error));
    325   return ConvertNetErrorToInterruptReason(error, DOWNLOAD_INTERRUPT_FROM_DISK);
    326 }
    327 
    328 DownloadInterruptReason BaseFile::LogSystemError(
    329     const char* operation,
    330     logging::SystemErrorCode os_error) {
    331   // There's no direct conversion from a system error to an interrupt reason.
    332   base::File::Error file_error = base::File::OSErrorToFileError(os_error);
    333   return LogInterruptReason(
    334       operation, os_error,
    335       ConvertFileErrorToInterruptReason(file_error));
    336 }
    337 
    338 DownloadInterruptReason BaseFile::LogInterruptReason(
    339     const char* operation,
    340     int os_error,
    341     DownloadInterruptReason reason) {
    342   bound_net_log_.AddEvent(
    343       net::NetLog::TYPE_DOWNLOAD_FILE_ERROR,
    344       base::Bind(&FileInterruptedNetLogCallback, operation, os_error, reason));
    345   return reason;
    346 }
    347 
    348 }  // namespace content
    349