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.
5 #include "content/browser/download/base_file.h"
8 #include "base/files/file.h"
9 #include "base/files/file_util.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"
25 // This will initialize the entire array to zero.
26 const unsigned char BaseFile::kEmptySha256Hash
[] = { 0 };
28 BaseFile::BaseFile(const base::FilePath
& full_path
,
29 const GURL
& source_url
,
30 const GURL
& referrer_url
,
33 const std::string
& hash_state_bytes
,
35 const net::BoundNetLog
& bound_net_log
)
36 : full_path_(full_path
),
37 source_url_(source_url
),
38 referrer_url_(referrer_url
),
40 bytes_so_far_(received_bytes
),
41 start_tick_(base::TimeTicks::Now()),
42 calculate_hash_(calculate_hash
),
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
);
57 BaseFile::~BaseFile() {
58 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
62 Cancel(); // Will delete the file.
65 DownloadInterruptReason
BaseFile::Initialize(
66 const base::FilePath
& default_directory
) {
67 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
70 if (full_path_
.empty()) {
71 base::FilePath
initial_directory(default_directory
);
72 base::FilePath temp_file
;
73 if (initial_directory
.empty()) {
75 GetContentClient()->browser()->GetDefaultDownloadDirectory();
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
);
85 full_path_
= temp_file
;
91 DownloadInterruptReason
BaseFile::AppendDataToFile(const char* data
,
93 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
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.
99 RecordDownloadCount(APPEND_TO_DETACHED_FILE_COUNT
);
101 if (!file_
.IsValid())
102 return LogInterruptReason("No file stream on append", 0,
103 DOWNLOAD_INTERRUPT_REASON_FILE_FAILED
);
105 // TODO(phajdan.jr): get rid of this check.
107 return DOWNLOAD_INTERRUPT_REASON_NONE
;
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
;
115 int write_result
= file_
.WriteAtCurrentPos(current_data
, len
);
116 DCHECK_NE(0, write_result
);
118 // Report errors on file writes.
119 if (write_result
< 0)
120 return LogSystemError("Write", logging::GetLastSystemErrorCode());
123 size_t write_size
= static_cast<size_t>(write_result
);
124 DCHECK_LE(write_size
, len
);
126 current_data
+= write_size
;
127 bytes_so_far_
+= write_size
;
130 RecordDownloadWriteSize(data_len
);
131 RecordDownloadWriteLoopCount(write_count
);
134 secure_hash_
->Update(data
, data_len
);
136 return DOWNLOAD_INTERRUPT_REASON_NONE
;
139 DownloadInterruptReason
BaseFile::Rename(const base::FilePath
& new_path
) {
140 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
141 DownloadInterruptReason rename_result
= DOWNLOAD_INTERRUPT_REASON_NONE
;
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
;
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();
152 bound_net_log_
.BeginEvent(
153 net::NetLog::TYPE_DOWNLOAD_FILE_RENAMED
,
154 base::Bind(&FileRenamedNetLogCallback
, &full_path_
, &new_path
));
156 base::CreateDirectory(new_path
.DirName());
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
);
162 if (rename_result
== DOWNLOAD_INTERRUPT_REASON_NONE
)
163 full_path_
= new_path
;
165 // Re-open the file if we were still using it regardless of the interrupt
167 DownloadInterruptReason open_result
= DOWNLOAD_INTERRUPT_REASON_NONE
;
169 open_result
= Open();
171 bound_net_log_
.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_RENAMED
);
172 return rename_result
== DOWNLOAD_INTERRUPT_REASON_NONE
? open_result
176 void BaseFile::Detach() {
178 bound_net_log_
.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DETACHED
);
181 void BaseFile::Cancel() {
182 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
185 bound_net_log_
.AddEvent(net::NetLog::TYPE_CANCELLED
);
189 if (!full_path_
.empty()) {
190 bound_net_log_
.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DELETED
);
191 base::DeleteFile(full_path_
, false);
197 void BaseFile::Finish() {
198 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
201 secure_hash_
->Finish(sha256_hash_
, crypto::kSHA256Length
);
206 void BaseFile::SetClientGuid(const std::string
& guid
) {
210 // OS_WIN, OS_MACOSX and OS_LINUX have specialized implementations.
211 #if !defined(OS_WIN) && !defined(OS_MACOSX) && !defined(OS_LINUX)
212 DownloadInterruptReason
BaseFile::AnnotateWithSourceInformation() {
213 return DOWNLOAD_INTERRUPT_REASON_NONE
;
217 bool BaseFile::GetHash(std::string
* hash
) {
219 hash
->assign(reinterpret_cast<const char*>(sha256_hash_
),
220 sizeof(sha256_hash_
));
221 return (calculate_hash_
&& !in_progress());
224 std::string
BaseFile::GetHashState() {
225 if (!calculate_hash_
)
226 return std::string();
229 if (!secure_hash_
->Serialize(&hash_state
))
230 return std::string();
232 return std::string(reinterpret_cast<const char*>(hash_state
.data()),
237 bool BaseFile::IsEmptyHash(const std::string
& hash
) {
238 return (hash
.size() == crypto::kSHA256Length
&&
239 0 == memcmp(hash
.data(), kEmptySha256Hash
, crypto::kSHA256Length
));
242 std::string
BaseFile::DebugString() const {
243 return base::StringPrintf("{ source_url_ = \"%s\""
244 " full_path_ = \"%" PRFilePath
"\""
245 " bytes_so_far_ = %" PRId64
247 source_url_
.spec().c_str(),
248 full_path_
.value().c_str(),
250 detached_
? 'T' : 'F');
253 DownloadInterruptReason
BaseFile::Open() {
254 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
256 DCHECK(!full_path_
.empty());
258 bound_net_log_
.BeginEvent(
259 net::NetLog::TYPE_DOWNLOAD_FILE_OPENED
,
260 base::Bind(&FileOpenedNetLogCallback
, &full_path_
, bytes_so_far_
));
262 // Create a new file if it is not provided.
263 if (!file_
.IsValid()) {
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()));
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);
276 logging::SystemErrorCode error
= logging::GetLastSystemErrorCode();
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();
287 return LogSystemError("Truncate", error
);
289 } else if (file_size
< bytes_so_far_
) {
290 // The file is shorter than we expected. Our hashes won't be valid.
292 return LogInterruptReason("Unable to seek to last written point", 0,
293 DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT
);
296 return DOWNLOAD_INTERRUPT_REASON_NONE
;
299 void BaseFile::Close() {
300 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
302 bound_net_log_
.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_CLOSED
);
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.
312 void BaseFile::ClearFile() {
313 // This should only be called when we have a stream.
314 DCHECK(file_
.IsValid());
316 bound_net_log_
.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_OPENED
);
319 DownloadInterruptReason
BaseFile::LogNetError(
320 const char* operation
,
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
);
328 DownloadInterruptReason
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(
335 ConvertFileErrorToInterruptReason(file_error
));
338 DownloadInterruptReason
BaseFile::LogInterruptReason(
339 const char* operation
,
341 DownloadInterruptReason reason
) {
342 bound_net_log_
.AddEvent(
343 net::NetLog::TYPE_DOWNLOAD_FILE_ERROR
,
344 base::Bind(&FileInterruptedNetLogCallback
, operation
, os_error
, reason
));
348 } // namespace content