Add ICU message format support
[chromium-blink-merge.git] / content / browser / download / base_file.cc
blobf9b80155018d4a00aa2a6df90bcb32b3666c2672
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"
7 #include "base/bind.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"
23 namespace content {
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,
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 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);
60 if (detached_)
61 Close();
62 else
63 Cancel(); // Will delete the file.
66 DownloadInterruptReason BaseFile::Initialize(
67 const base::FilePath& default_directory) {
68 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
69 DCHECK(!detached_);
71 if (full_path_.empty()) {
72 base::FilePath initial_directory(default_directory);
73 base::FilePath temp_file;
74 if (initial_directory.empty()) {
75 initial_directory =
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;
89 return Open();
92 DownloadInterruptReason BaseFile::AppendDataToFile(const char* data,
93 size_t data_len) {
94 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
95 DCHECK(!detached_);
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.
99 if (detached_)
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.
107 if (data_len == 0)
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;
114 while (len > 0) {
115 write_count++;
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());
123 // Update status.
124 size_t write_size = static_cast<size_t>(write_result);
125 DCHECK_LE(write_size, len);
126 len -= write_size;
127 current_data += write_size;
128 bytes_so_far_ += write_size;
131 RecordDownloadWriteSize(data_len);
132 RecordDownloadWriteLoopCount(write_count);
134 if (calculate_hash_)
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));
156 Close();
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
167 // reason.
168 DownloadInterruptReason open_result = DOWNLOAD_INTERRUPT_REASON_NONE;
169 if (was_in_progress)
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
174 : rename_result;
177 void BaseFile::Detach() {
178 detached_ = true;
179 bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DETACHED);
182 void BaseFile::Cancel() {
183 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
184 DCHECK(!detached_);
186 bound_net_log_.AddEvent(net::NetLog::TYPE_CANCELLED);
188 Close();
190 if (!full_path_.empty()) {
191 bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DELETED);
192 base::DeleteFile(full_path_, false);
195 Detach();
198 void BaseFile::Finish() {
199 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
201 if (calculate_hash_)
202 secure_hash_->Finish(sha256_hash_, crypto::kSHA256Length);
204 Close();
207 void BaseFile::SetClientGuid(const std::string& guid) {
208 client_guid_ = 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;
216 #endif
218 bool BaseFile::GetHash(std::string* hash) {
219 DCHECK(!detached_);
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()),
234 hash_state.size());
237 // static
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
247 " detached_ = %c }",
248 source_url_.spec().c_str(),
249 full_path_.value().c_str(),
250 bytes_so_far_,
251 detached_ ? 'T' : 'F');
254 DownloadInterruptReason BaseFile::Open() {
255 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
256 DCHECK(!detached_);
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()) {
265 file_.Initialize(
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);
276 if (file_size < 0) {
277 logging::SystemErrorCode error = logging::GetLastSystemErrorCode();
278 ClearFile();
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();
287 ClearFile();
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.
292 ClearFile();
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.
308 file_.Flush();
309 ClearFile();
313 void BaseFile::ClearFile() {
314 // This should only be called when we have a stream.
315 DCHECK(file_.IsValid());
316 file_.Close();
317 bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_OPENED);
320 DownloadInterruptReason BaseFile::LogNetError(
321 const char* operation,
322 net::Error error) {
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(
335 operation, os_error,
336 ConvertFileErrorToInterruptReason(file_error));
339 DownloadInterruptReason BaseFile::LogInterruptReason(
340 const char* operation,
341 int os_error,
342 DownloadInterruptReason reason) {
343 bound_net_log_.AddEvent(
344 net::NetLog::TYPE_DOWNLOAD_FILE_ERROR,
345 base::Bind(&FileInterruptedNetLogCallback, operation, os_error, reason));
346 return reason;
349 } // namespace content