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/files/file.h"
10 #include "base/format_macros.h"
11 #include "base/logging.h"
12 #include "base/pickle.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/threading/thread_restrictions.h"
15 #include "content/browser/download/download_interrupt_reasons_impl.h"
16 #include "content/browser/download/download_net_log_parameters.h"
17 #include "content/browser/download/download_stats.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/browser/content_browser_client.h"
20 #include "crypto/secure_hash.h"
21 #include "net/base/net_errors.h"
22
23 namespace content {
24
25 // This will initialize the entire array to zero.
26 const unsigned char BaseFile::kEmptySha256Hash[] = { 0 };
27
BaseFile(const base::FilePath & full_path,const GURL & source_url,const GURL & referrer_url,int64 received_bytes,bool calculate_hash,const std::string & hash_state_bytes,base::File file,const net::BoundNetLog & bound_net_log)28 BaseFile::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 base::File file,
35 const net::BoundNetLog& bound_net_log)
36 : full_path_(full_path),
37 source_url_(source_url),
38 referrer_url_(referrer_url),
39 file_(file.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, crypto::kSHA256Length);
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
~BaseFile()57 BaseFile::~BaseFile() {
58 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
59 if (detached_)
60 Close();
61 else
62 Cancel(); // Will delete the file.
63 }
64
Initialize(const base::FilePath & default_directory)65 DownloadInterruptReason 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
AppendDataToFile(const char * data,size_t data_len)91 DownloadInterruptReason 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
Rename(const base::FilePath & new_path)139 DownloadInterruptReason 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 // Re-open the file if we were still using it.
165 if (was_in_progress)
166 rename_result = Open();
167 }
168
169 bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_RENAMED);
170 return rename_result;
171 }
172
Detach()173 void BaseFile::Detach() {
174 detached_ = true;
175 bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DETACHED);
176 }
177
Cancel()178 void BaseFile::Cancel() {
179 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
180 DCHECK(!detached_);
181
182 bound_net_log_.AddEvent(net::NetLog::TYPE_CANCELLED);
183
184 Close();
185
186 if (!full_path_.empty()) {
187 bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DELETED);
188 base::DeleteFile(full_path_, false);
189 }
190
191 Detach();
192 }
193
Finish()194 void BaseFile::Finish() {
195 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
196
197 if (calculate_hash_)
198 secure_hash_->Finish(sha256_hash_, crypto::kSHA256Length);
199
200 Close();
201 }
202
SetClientGuid(const std::string & guid)203 void BaseFile::SetClientGuid(const std::string& guid) {
204 client_guid_ = guid;
205 }
206
207 // OS_WIN, OS_MACOSX and OS_LINUX have specialized implementations.
208 #if !defined(OS_WIN) && !defined(OS_MACOSX) && !defined(OS_LINUX)
AnnotateWithSourceInformation()209 DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() {
210 return DOWNLOAD_INTERRUPT_REASON_NONE;
211 }
212 #endif
213
GetHash(std::string * hash)214 bool BaseFile::GetHash(std::string* hash) {
215 DCHECK(!detached_);
216 hash->assign(reinterpret_cast<const char*>(sha256_hash_),
217 sizeof(sha256_hash_));
218 return (calculate_hash_ && !in_progress());
219 }
220
GetHashState()221 std::string BaseFile::GetHashState() {
222 if (!calculate_hash_)
223 return std::string();
224
225 Pickle hash_state;
226 if (!secure_hash_->Serialize(&hash_state))
227 return std::string();
228
229 return std::string(reinterpret_cast<const char*>(hash_state.data()),
230 hash_state.size());
231 }
232
233 // static
IsEmptyHash(const std::string & hash)234 bool BaseFile::IsEmptyHash(const std::string& hash) {
235 return (hash.size() == crypto::kSHA256Length &&
236 0 == memcmp(hash.data(), kEmptySha256Hash, crypto::kSHA256Length));
237 }
238
DebugString() const239 std::string BaseFile::DebugString() const {
240 return base::StringPrintf("{ source_url_ = \"%s\""
241 " full_path_ = \"%" PRFilePath "\""
242 " bytes_so_far_ = %" PRId64
243 " detached_ = %c }",
244 source_url_.spec().c_str(),
245 full_path_.value().c_str(),
246 bytes_so_far_,
247 detached_ ? 'T' : 'F');
248 }
249
Open()250 DownloadInterruptReason BaseFile::Open() {
251 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
252 DCHECK(!detached_);
253 DCHECK(!full_path_.empty());
254
255 bound_net_log_.BeginEvent(
256 net::NetLog::TYPE_DOWNLOAD_FILE_OPENED,
257 base::Bind(&FileOpenedNetLogCallback, &full_path_, bytes_so_far_));
258
259 // Create a new file if it is not provided.
260 if (!file_.IsValid()) {
261 file_.Initialize(
262 full_path_, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_WRITE);
263 if (!file_.IsValid()) {
264 return LogNetError("Open",
265 net::FileErrorToNetError(file_.error_details()));
266 }
267 }
268
269 // We may be re-opening the file after rename. Always make sure we're
270 // writing at the end of the file.
271 int64 file_size = file_.Seek(base::File::FROM_END, 0);
272 if (file_size < 0) {
273 logging::SystemErrorCode error = logging::GetLastSystemErrorCode();
274 ClearFile();
275 return LogSystemError("Seek", error);
276 } else if (file_size > bytes_so_far_) {
277 // The file is larger than we expected.
278 // This is OK, as long as we don't use the extra.
279 // Truncate the file.
280 if (!file_.SetLength(bytes_so_far_) ||
281 file_.Seek(base::File::FROM_BEGIN, bytes_so_far_) != bytes_so_far_) {
282 logging::SystemErrorCode error = logging::GetLastSystemErrorCode();
283 ClearFile();
284 return LogSystemError("Truncate", error);
285 }
286 } else if (file_size < bytes_so_far_) {
287 // The file is shorter than we expected. Our hashes won't be valid.
288 ClearFile();
289 return LogInterruptReason("Unable to seek to last written point", 0,
290 DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT);
291 }
292
293 return DOWNLOAD_INTERRUPT_REASON_NONE;
294 }
295
Close()296 void BaseFile::Close() {
297 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
298
299 bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_CLOSED);
300
301 if (file_.IsValid()) {
302 // Currently we don't really care about the return value, since if it fails
303 // theres not much we can do. But we might in the future.
304 file_.Flush();
305 ClearFile();
306 }
307 }
308
ClearFile()309 void BaseFile::ClearFile() {
310 // This should only be called when we have a stream.
311 DCHECK(file_.IsValid());
312 file_.Close();
313 bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_OPENED);
314 }
315
LogNetError(const char * operation,net::Error error)316 DownloadInterruptReason BaseFile::LogNetError(
317 const char* operation,
318 net::Error error) {
319 bound_net_log_.AddEvent(
320 net::NetLog::TYPE_DOWNLOAD_FILE_ERROR,
321 base::Bind(&FileErrorNetLogCallback, operation, error));
322 return ConvertNetErrorToInterruptReason(error, DOWNLOAD_INTERRUPT_FROM_DISK);
323 }
324
LogSystemError(const char * operation,logging::SystemErrorCode os_error)325 DownloadInterruptReason BaseFile::LogSystemError(
326 const char* operation,
327 logging::SystemErrorCode os_error) {
328 // There's no direct conversion from a system error to an interrupt reason.
329 net::Error net_error = net::MapSystemError(os_error);
330 return LogInterruptReason(
331 operation, os_error,
332 ConvertNetErrorToInterruptReason(
333 net_error, DOWNLOAD_INTERRUPT_FROM_DISK));
334 }
335
LogInterruptReason(const char * operation,int os_error,DownloadInterruptReason reason)336 DownloadInterruptReason BaseFile::LogInterruptReason(
337 const char* operation,
338 int os_error,
339 DownloadInterruptReason reason) {
340 bound_net_log_.AddEvent(
341 net::NetLog::TYPE_DOWNLOAD_FILE_ERROR,
342 base::Bind(&FileInterruptedNetLogCallback, operation, os_error, reason));
343 return reason;
344 }
345
346 } // namespace content
347