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 base::Pickle
hash_state(hash_state_bytes
.c_str(),
51 hash_state_bytes
.size());
52 base::PickleIterator
data_iterator(hash_state
);
53 secure_hash_
->Deserialize(&data_iterator
);
58 BaseFile::~BaseFile() {
59 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
63 Cancel(); // Will delete the file.
66 DownloadInterruptReason
BaseFile::Initialize(
67 const base::FilePath
& default_directory
) {
68 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
71 if (full_path_
.empty()) {
72 base::FilePath
initial_directory(default_directory
);
73 base::FilePath temp_file
;
74 if (initial_directory
.empty()) {
76 GetContentClient()->browser()->GetDefaultDownloadDirectory();
78 // |initial_directory| can still be empty if ContentBrowserClient returned
79 // an empty path for the downloads directory.
80 if ((initial_directory
.empty() ||
81 !base::CreateTemporaryFileInDir(initial_directory
, &temp_file
)) &&
82 !base::CreateTemporaryFile(&temp_file
)) {
83 return LogInterruptReason("Unable to create", 0,
84 DOWNLOAD_INTERRUPT_REASON_FILE_FAILED
);
86 full_path_
= temp_file
;
92 DownloadInterruptReason
BaseFile::AppendDataToFile(const char* data
,
94 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
97 // NOTE(benwells): The above DCHECK won't be present in release builds,
98 // so we log any occurences to see how common this error is in the wild.
100 RecordDownloadCount(APPEND_TO_DETACHED_FILE_COUNT
);
102 if (!file_
.IsValid())
103 return LogInterruptReason("No file stream on append", 0,
104 DOWNLOAD_INTERRUPT_REASON_FILE_FAILED
);
106 // TODO(phajdan.jr): get rid of this check.
108 return DOWNLOAD_INTERRUPT_REASON_NONE
;
110 // The Write call below is not guaranteed to write all the data.
111 size_t write_count
= 0;
112 size_t len
= data_len
;
113 const char* current_data
= data
;
116 int write_result
= file_
.WriteAtCurrentPos(current_data
, len
);
117 DCHECK_NE(0, write_result
);
119 // Report errors on file writes.
120 if (write_result
< 0)
121 return LogSystemError("Write", logging::GetLastSystemErrorCode());
124 size_t write_size
= static_cast<size_t>(write_result
);
125 DCHECK_LE(write_size
, len
);
127 current_data
+= write_size
;
128 bytes_so_far_
+= write_size
;
131 RecordDownloadWriteSize(data_len
);
132 RecordDownloadWriteLoopCount(write_count
);
135 secure_hash_
->Update(data
, data_len
);
137 return DOWNLOAD_INTERRUPT_REASON_NONE
;
140 DownloadInterruptReason
BaseFile::Rename(const base::FilePath
& new_path
) {
141 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
142 DownloadInterruptReason rename_result
= DOWNLOAD_INTERRUPT_REASON_NONE
;
144 // If the new path is same as the old one, there is no need to perform the
145 // following renaming logic.
146 if (new_path
== full_path_
)
147 return DOWNLOAD_INTERRUPT_REASON_NONE
;
149 // Save the information whether the download is in progress because
150 // it will be overwritten by closing the file.
151 bool was_in_progress
= in_progress();
153 bound_net_log_
.BeginEvent(
154 net::NetLog::TYPE_DOWNLOAD_FILE_RENAMED
,
155 base::Bind(&FileRenamedNetLogCallback
, &full_path_
, &new_path
));
157 base::CreateDirectory(new_path
.DirName());
159 // A simple rename wouldn't work here since we want the file to have
160 // permissions / security descriptors that makes sense in the new directory.
161 rename_result
= MoveFileAndAdjustPermissions(new_path
);
163 if (rename_result
== DOWNLOAD_INTERRUPT_REASON_NONE
)
164 full_path_
= new_path
;
166 // Re-open the file if we were still using it regardless of the interrupt
168 DownloadInterruptReason open_result
= DOWNLOAD_INTERRUPT_REASON_NONE
;
170 open_result
= Open();
172 bound_net_log_
.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_RENAMED
);
173 return rename_result
== DOWNLOAD_INTERRUPT_REASON_NONE
? open_result
177 void BaseFile::Detach() {
179 bound_net_log_
.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DETACHED
);
182 void BaseFile::Cancel() {
183 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
186 bound_net_log_
.AddEvent(net::NetLog::TYPE_CANCELLED
);
190 if (!full_path_
.empty()) {
191 bound_net_log_
.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DELETED
);
192 base::DeleteFile(full_path_
, false);
198 void BaseFile::Finish() {
199 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
202 secure_hash_
->Finish(sha256_hash_
, crypto::kSHA256Length
);
207 void BaseFile::SetClientGuid(const std::string
& guid
) {
211 // OS_WIN, OS_MACOSX and OS_LINUX have specialized implementations.
212 #if !defined(OS_WIN) && !defined(OS_MACOSX) && !defined(OS_LINUX)
213 DownloadInterruptReason
BaseFile::AnnotateWithSourceInformation() {
214 return DOWNLOAD_INTERRUPT_REASON_NONE
;
218 bool BaseFile::GetHash(std::string
* hash
) {
220 hash
->assign(reinterpret_cast<const char*>(sha256_hash_
),
221 sizeof(sha256_hash_
));
222 return (calculate_hash_
&& !in_progress());
225 std::string
BaseFile::GetHashState() {
226 if (!calculate_hash_
)
227 return std::string();
229 base::Pickle hash_state
;
230 if (!secure_hash_
->Serialize(&hash_state
))
231 return std::string();
233 return std::string(reinterpret_cast<const char*>(hash_state
.data()),
238 bool BaseFile::IsEmptyHash(const std::string
& hash
) {
239 return (hash
.size() == crypto::kSHA256Length
&&
240 0 == memcmp(hash
.data(), kEmptySha256Hash
, crypto::kSHA256Length
));
243 std::string
BaseFile::DebugString() const {
244 return base::StringPrintf("{ source_url_ = \"%s\""
245 " full_path_ = \"%" PRFilePath
"\""
246 " bytes_so_far_ = %" PRId64
248 source_url_
.spec().c_str(),
249 full_path_
.value().c_str(),
251 detached_
? 'T' : 'F');
254 DownloadInterruptReason
BaseFile::Open() {
255 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
257 DCHECK(!full_path_
.empty());
259 bound_net_log_
.BeginEvent(
260 net::NetLog::TYPE_DOWNLOAD_FILE_OPENED
,
261 base::Bind(&FileOpenedNetLogCallback
, &full_path_
, bytes_so_far_
));
263 // Create a new file if it is not provided.
264 if (!file_
.IsValid()) {
266 full_path_
, base::File::FLAG_OPEN_ALWAYS
| base::File::FLAG_WRITE
);
267 if (!file_
.IsValid()) {
268 return LogNetError("Open",
269 net::FileErrorToNetError(file_
.error_details()));
273 // We may be re-opening the file after rename. Always make sure we're
274 // writing at the end of the file.
275 int64 file_size
= file_
.Seek(base::File::FROM_END
, 0);
277 logging::SystemErrorCode error
= logging::GetLastSystemErrorCode();
279 return LogSystemError("Seek", error
);
280 } else if (file_size
> bytes_so_far_
) {
281 // The file is larger than we expected.
282 // This is OK, as long as we don't use the extra.
283 // Truncate the file.
284 if (!file_
.SetLength(bytes_so_far_
) ||
285 file_
.Seek(base::File::FROM_BEGIN
, bytes_so_far_
) != bytes_so_far_
) {
286 logging::SystemErrorCode error
= logging::GetLastSystemErrorCode();
288 return LogSystemError("Truncate", error
);
290 } else if (file_size
< bytes_so_far_
) {
291 // The file is shorter than we expected. Our hashes won't be valid.
293 return LogInterruptReason("Unable to seek to last written point", 0,
294 DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT
);
297 return DOWNLOAD_INTERRUPT_REASON_NONE
;
300 void BaseFile::Close() {
301 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
303 bound_net_log_
.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_CLOSED
);
305 if (file_
.IsValid()) {
306 // Currently we don't really care about the return value, since if it fails
307 // theres not much we can do. But we might in the future.
313 void BaseFile::ClearFile() {
314 // This should only be called when we have a stream.
315 DCHECK(file_
.IsValid());
317 bound_net_log_
.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_OPENED
);
320 DownloadInterruptReason
BaseFile::LogNetError(
321 const char* operation
,
323 bound_net_log_
.AddEvent(
324 net::NetLog::TYPE_DOWNLOAD_FILE_ERROR
,
325 base::Bind(&FileErrorNetLogCallback
, operation
, error
));
326 return ConvertNetErrorToInterruptReason(error
, DOWNLOAD_INTERRUPT_FROM_DISK
);
329 DownloadInterruptReason
BaseFile::LogSystemError(
330 const char* operation
,
331 logging::SystemErrorCode os_error
) {
332 // There's no direct conversion from a system error to an interrupt reason.
333 base::File::Error file_error
= base::File::OSErrorToFileError(os_error
);
334 return LogInterruptReason(
336 ConvertFileErrorToInterruptReason(file_error
));
339 DownloadInterruptReason
BaseFile::LogInterruptReason(
340 const char* operation
,
342 DownloadInterruptReason reason
) {
343 bound_net_log_
.AddEvent(
344 net::NetLog::TYPE_DOWNLOAD_FILE_ERROR
,
345 base::Bind(&FileInterruptedNetLogCallback
, operation
, os_error
, reason
));
349 } // namespace content