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