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
23namespace content {
24
25// This will initialize the entire array to zero.
26const unsigned char BaseFile::kEmptySha256Hash[] = { 0 };
27
28BaseFile::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
57BaseFile::~BaseFile() {
58  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
59  if (detached_)
60    Close();
61  else
62    Cancel();  // Will delete the file.
63}
64
65DownloadInterruptReason 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
96DownloadInterruptReason 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
151DownloadInterruptReason 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
185void BaseFile::Detach() {
186  detached_ = true;
187  bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DETACHED);
188}
189
190void 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
205void 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
214void 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)
220DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() {
221  return DOWNLOAD_INTERRUPT_REASON_NONE;
222}
223#endif
224
225bool 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
232std::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
245bool BaseFile::IsEmptyHash(const std::string& hash) {
246  return (hash.size() == kSha256HashLen &&
247          0 == memcmp(hash.data(), kEmptySha256Hash, sizeof(kSha256HashLen)));
248}
249
250std::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
261void BaseFile::CreateFileStream() {
262  file_stream_.reset(new net::FileStream(bound_net_log_.net_log()));
263  file_stream_->SetBoundNetLogSource(bound_net_log_);
264}
265
266DownloadInterruptReason 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
319void 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
334void 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
341DownloadInterruptReason 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
350DownloadInterruptReason 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
361DownloadInterruptReason 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