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
BaseFile(const FilePath & full_path,const GURL & source_url,const GURL & referrer_url,int64 received_bytes,const linked_ptr<net::FileStream> & file_stream)23 BaseFile::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
~BaseFile()40 BaseFile::~BaseFile() {
41 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
42 if (detached_)
43 Close();
44 else
45 Cancel(); // Will delete the file.
46 }
47
Initialize(bool calculate_hash)48 bool 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
AppendDataToFile(const char * data,size_t data_len)63 bool 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
Rename(const FilePath & new_path)87 bool 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
Detach()148 void BaseFile::Detach() {
149 detached_ = true;
150 }
151
Cancel()152 void 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
Finish()162 void 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
GetSha256Hash(std::string * hash)171 bool 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
AnnotateWithSourceInformation()180 void 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
Open()196 bool 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
Close()225 void 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
DebugString() const238 std::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