1f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson// Use of this source code is governed by a BSD-style license that can be
3f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson// found in the LICENSE file.
4f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson
5f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson#include "content/browser/download/base_file.h"
6f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson
7f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson#include "base/bind.h"
8f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson#include "base/files/file.h"
9f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson#include "base/files/file_util.h"
10f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson#include "base/format_macros.h"
11f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson#include "base/logging.h"
12f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson#include "base/pickle.h"
13f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson#include "base/strings/stringprintf.h"
14f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson#include "base/threading/thread_restrictions.h"
15f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson#include "content/browser/download/download_interrupt_reasons_impl.h"
16f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson#include "content/browser/download/download_net_log_parameters.h"
17f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson#include "content/browser/download/download_stats.h"
18f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson#include "content/public/browser/browser_thread.h"
19f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson#include "content/public/browser/content_browser_client.h"
20f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson#include "crypto/secure_hash.h"
21f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson#include "net/base/net_errors.h"
22f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson
23f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodsonnamespace content {
24f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson
25f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson// This will initialize the entire array to zero.
26f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodsonconst unsigned char BaseFile::kEmptySha256Hash[] = { 0 };
27f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson
28f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian HodsonBaseFile::BaseFile(const base::FilePath& full_path,
29f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson                   const GURL& source_url,
30f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson                   const GURL& referrer_url,
31f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson                   int64 received_bytes,
32f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson                   bool calculate_hash,
33f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson                   const std::string& hash_state_bytes,
34f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson                   base::File file,
35f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson                   const net::BoundNetLog& bound_net_log)
36dfd8b8327b93660601d016cdc6f29f433b45a8d8Alexander Gutkin    : full_path_(full_path),
37f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson      source_url_(source_url),
38f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson      referrer_url_(referrer_url),
39f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson      file_(file.Pass()),
40f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson      bytes_so_far_(received_bytes),
41f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson      start_tick_(base::TimeTicks::Now()),
42f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson      calculate_hash_(calculate_hash),
43f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson      detached_(false),
44f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson      bound_net_log_(bound_net_log) {
45f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson  memcpy(sha256_hash_, kEmptySha256Hash, crypto::kSHA256Length);
46f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson  if (calculate_hash_) {
47f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson    secure_hash_.reset(crypto::SecureHash::Create(crypto::SecureHash::SHA256));
48f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson    if ((bytes_so_far_ > 0) &&  // Not starting at the beginning.
49f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson        (!IsEmptyHash(hash_state_bytes))) {
50f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson      Pickle hash_state(hash_state_bytes.c_str(), hash_state_bytes.size());
51f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson      PickleIterator data_iterator(hash_state);
52f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson      secure_hash_->Deserialize(&data_iterator);
53f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson    }
54f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson  }
55f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson}
56f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson
57f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian HodsonBaseFile::~BaseFile() {
58f4c12fce1ee58e670f9c3fce46c40296ba9ee8a2Ian Hodson  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 (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
91DownloadInterruptReason 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
139DownloadInterruptReason 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
176void BaseFile::Detach() {
177  detached_ = true;
178  bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DETACHED);
179}
180
181void 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
197void 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
206void 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)
212DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() {
213  return DOWNLOAD_INTERRUPT_REASON_NONE;
214}
215#endif
216
217bool 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
224std::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
237bool BaseFile::IsEmptyHash(const std::string& hash) {
238  return (hash.size() == crypto::kSHA256Length &&
239          0 == memcmp(hash.data(), kEmptySha256Hash, crypto::kSHA256Length));
240}
241
242std::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
253DownloadInterruptReason 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
299void 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
312void 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
319DownloadInterruptReason 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
328DownloadInterruptReason 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
338DownloadInterruptReason 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