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