1// Copyright (c) 2011 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 "chrome/browser/download/base_file.h"
6
7#include "base/file_util.h"
8#include "base/format_macros.h"
9#include "base/logging.h"
10#include "base/stringprintf.h"
11#include "crypto/secure_hash.h"
12#include "net/base/file_stream.h"
13#include "net/base/net_errors.h"
14#include "chrome/browser/download/download_util.h"
15#include "content/browser/browser_thread.h"
16
17#if defined(OS_WIN)
18#include "chrome/common/win_safe_util.h"
19#elif defined(OS_MACOSX)
20#include "chrome/browser/cocoa/file_metadata.h"
21#endif
22
23BaseFile::BaseFile(const FilePath& full_path,
24                   const GURL& source_url,
25                   const GURL& referrer_url,
26                   int64 received_bytes,
27                   const linked_ptr<net::FileStream>& file_stream)
28    : full_path_(full_path),
29      source_url_(source_url),
30      referrer_url_(referrer_url),
31      file_stream_(file_stream),
32      bytes_so_far_(received_bytes),
33      power_save_blocker_(true),
34      calculate_hash_(false),
35      detached_(false) {
36  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
37  memset(sha256_hash_, 0, sizeof(sha256_hash_));
38}
39
40BaseFile::~BaseFile() {
41  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
42  if (detached_)
43    Close();
44  else
45    Cancel();  // Will delete the file.
46}
47
48bool BaseFile::Initialize(bool calculate_hash) {
49  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
50  DCHECK(!detached_);
51
52  calculate_hash_ = calculate_hash;
53
54  if (calculate_hash_)
55    secure_hash_.reset(crypto::SecureHash::Create(crypto::SecureHash::SHA256));
56
57  if (!full_path_.empty() ||
58      download_util::CreateTemporaryFileForDownload(&full_path_))
59    return Open();
60  return false;
61}
62
63bool BaseFile::AppendDataToFile(const char* data, size_t data_len) {
64  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
65  DCHECK(!detached_);
66
67  if (!file_stream_.get())
68    return false;
69
70  // TODO(phajdan.jr): get rid of this check.
71  if (data_len == 0)
72    return true;
73
74  bytes_so_far_ += data_len;
75
76  // TODO(phajdan.jr): handle errors on file writes. http://crbug.com/58355
77  size_t written = file_stream_->Write(data, data_len, NULL);
78  if (written != data_len)
79    return false;
80
81  if (calculate_hash_)
82    secure_hash_->Update(data, data_len);
83
84  return true;
85}
86
87bool BaseFile::Rename(const FilePath& new_path) {
88  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
89
90  // Save the information whether the download is in progress because
91  // it will be overwritten by closing the file.
92  bool saved_in_progress = in_progress();
93
94  // If the new path is same as the old one, there is no need to perform the
95  // following renaming logic.
96  if (new_path == full_path_) {
97    // Don't close the file if we're not done (finished or canceled).
98    if (!saved_in_progress)
99      Close();
100
101    return true;
102  }
103
104  Close();
105
106  file_util::CreateDirectory(new_path.DirName());
107
108#if defined(OS_WIN)
109  // We cannot rename because rename will keep the same security descriptor
110  // on the destination file. We want to recreate the security descriptor
111  // with the security that makes sense in the new path.
112  if (!file_util::RenameFileAndResetSecurityDescriptor(full_path_, new_path))
113    return false;
114#elif defined(OS_POSIX)
115  {
116    // Similarly, on Unix, we're moving a temp file created with permissions
117    // 600 to |new_path|. Here, we try to fix up the destination file with
118    // appropriate permissions.
119    struct stat st;
120    // First check the file existence and create an empty file if it doesn't
121    // exist.
122    if (!file_util::PathExists(new_path))
123      file_util::WriteFile(new_path, "", 0);
124    bool stat_succeeded = (stat(new_path.value().c_str(), &st) == 0);
125
126    // TODO(estade): Move() falls back to copying and deleting when a simple
127    // rename fails. Copying sucks for large downloads. crbug.com/8737
128    if (!file_util::Move(full_path_, new_path))
129      return false;
130
131    if (stat_succeeded)
132      chmod(new_path.value().c_str(), st.st_mode);
133  }
134#endif
135
136  full_path_ = new_path;
137
138  // We don't need to re-open the file if we're done (finished or canceled).
139  if (!saved_in_progress)
140    return true;
141
142  if (!Open())
143    return false;
144
145  return true;
146}
147
148void BaseFile::Detach() {
149  detached_ = true;
150}
151
152void BaseFile::Cancel() {
153  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
154  DCHECK(!detached_);
155
156  Close();
157
158  if (!full_path_.empty())
159    file_util::Delete(full_path_, false);
160}
161
162void BaseFile::Finish() {
163  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
164
165  if (calculate_hash_)
166    secure_hash_->Finish(sha256_hash_, kSha256HashLen);
167
168  Close();
169}
170
171bool BaseFile::GetSha256Hash(std::string* hash) {
172  DCHECK(!detached_);
173  if (!calculate_hash_ || in_progress())
174    return false;
175  hash->assign(reinterpret_cast<const char*>(sha256_hash_),
176               sizeof(sha256_hash_));
177  return true;
178}
179
180void BaseFile::AnnotateWithSourceInformation() {
181  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
182  DCHECK(!detached_);
183
184#if defined(OS_WIN)
185  // Sets the Zone to tell Windows that this file comes from the internet.
186  // We ignore the return value because a failure is not fatal.
187  win_util::SetInternetZoneIdentifier(full_path_);
188#elif defined(OS_MACOSX)
189  file_metadata::AddQuarantineMetadataToFile(full_path_, source_url_,
190                                             referrer_url_);
191  file_metadata::AddOriginMetadataToFile(full_path_, source_url_,
192                                         referrer_url_);
193#endif
194}
195
196bool BaseFile::Open() {
197  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
198  DCHECK(!detached_);
199  DCHECK(!full_path_.empty());
200
201  // Create a new file stream if it is not provided.
202  if (!file_stream_.get()) {
203    file_stream_.reset(new net::FileStream);
204    if (file_stream_->Open(full_path_,
205                           base::PLATFORM_FILE_OPEN_ALWAYS |
206                               base::PLATFORM_FILE_WRITE) != net::OK) {
207      file_stream_.reset();
208      return false;
209    }
210
211    // We may be re-opening the file after rename. Always make sure we're
212    // writing at the end of the file.
213    if (file_stream_->Seek(net::FROM_END, 0) < 0) {
214      file_stream_.reset();
215      return false;
216    }
217  }
218
219#if defined(OS_WIN)
220  AnnotateWithSourceInformation();
221#endif
222  return true;
223}
224
225void BaseFile::Close() {
226  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
227  if (file_stream_.get()) {
228#if defined(OS_CHROMEOS)
229    // Currently we don't really care about the return value, since if it fails
230    // theres not much we can do.  But we might in the future.
231    file_stream_->Flush();
232#endif
233    file_stream_->Close();
234    file_stream_.reset();
235  }
236}
237
238std::string BaseFile::DebugString() const {
239  return base::StringPrintf("{ source_url_ = \"%s\""
240                            " full_path_ = \"%" PRFilePath "\""
241                            " bytes_so_far_ = %" PRId64 " detached_ = %c }",
242                            source_url_.spec().c_str(),
243                            full_path_.value().c_str(),
244                            bytes_so_far_,
245                            detached_ ? 'T' : 'F');
246}
247