Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / content / browser / download / base_file.cc
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
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
57 BaseFile::~BaseFile() {
58   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
59   if (detached_)
60     Close();
61   else
62     Cancel();  // Will delete the file.
63 }
64
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
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
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
173 void BaseFile::Detach() {
174   detached_ = true;
175   bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DETACHED);
176 }
177
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
189     base::DeleteFile(full_path_, false);
190   }
191 }
192
193 void BaseFile::Finish() {
194   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
195
196   if (calculate_hash_)
197     secure_hash_->Finish(sha256_hash_, crypto::kSHA256Length);
198
199   Close();
200 }
201
202 void BaseFile::SetClientGuid(const std::string& guid) {
203   client_guid_ = guid;
204 }
205
206 // OS_WIN, OS_MACOSX and OS_LINUX have specialized implementations.
207 #if !defined(OS_WIN) && !defined(OS_MACOSX) && !defined(OS_LINUX)
208 DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() {
209   return DOWNLOAD_INTERRUPT_REASON_NONE;
210 }
211 #endif
212
213 bool BaseFile::GetHash(std::string* hash) {
214   DCHECK(!detached_);
215   hash->assign(reinterpret_cast<const char*>(sha256_hash_),
216                sizeof(sha256_hash_));
217   return (calculate_hash_ && !in_progress());
218 }
219
220 std::string BaseFile::GetHashState() {
221   if (!calculate_hash_)
222     return std::string();
223
224   Pickle hash_state;
225   if (!secure_hash_->Serialize(&hash_state))
226     return std::string();
227
228   return std::string(reinterpret_cast<const char*>(hash_state.data()),
229                      hash_state.size());
230 }
231
232 // static
233 bool BaseFile::IsEmptyHash(const std::string& hash) {
234   return (hash.size() == crypto::kSHA256Length &&
235           0 == memcmp(hash.data(), kEmptySha256Hash, crypto::kSHA256Length));
236 }
237
238 std::string BaseFile::DebugString() const {
239   return base::StringPrintf("{ source_url_ = \"%s\""
240                             " full_path_ = \"%" PRFilePath "\""
241                             " bytes_so_far_ = %" PRId64
242                             " detached_ = %c }",
243                             source_url_.spec().c_str(),
244                             full_path_.value().c_str(),
245                             bytes_so_far_,
246                             detached_ ? 'T' : 'F');
247 }
248
249 DownloadInterruptReason BaseFile::Open() {
250   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
251   DCHECK(!detached_);
252   DCHECK(!full_path_.empty());
253
254   bound_net_log_.BeginEvent(
255       net::NetLog::TYPE_DOWNLOAD_FILE_OPENED,
256       base::Bind(&FileOpenedNetLogCallback, &full_path_, bytes_so_far_));
257
258   // Create a new file if it is not provided.
259   if (!file_.IsValid()) {
260     file_.Initialize(
261         full_path_, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_WRITE);
262     if (!file_.IsValid()) {
263       return LogNetError("Open",
264                          net::FileErrorToNetError(file_.error_details()));
265     }
266   }
267
268   // We may be re-opening the file after rename. Always make sure we're
269   // writing at the end of the file.
270   int64 file_size = file_.Seek(base::File::FROM_END, 0);
271   if (file_size < 0) {
272     logging::SystemErrorCode error = logging::GetLastSystemErrorCode();
273     ClearFile();
274     return LogSystemError("Seek", error);
275   } else if (file_size > bytes_so_far_) {
276     // The file is larger than we expected.
277     // This is OK, as long as we don't use the extra.
278     // Truncate the file.
279     if (!file_.SetLength(bytes_so_far_) ||
280         file_.Seek(base::File::FROM_BEGIN, bytes_so_far_) != bytes_so_far_) {
281       logging::SystemErrorCode error = logging::GetLastSystemErrorCode();
282       ClearFile();
283       return LogSystemError("Truncate",  error);
284     }
285   } else if (file_size < bytes_so_far_) {
286     // The file is shorter than we expected.  Our hashes won't be valid.
287     ClearFile();
288     return LogInterruptReason("Unable to seek to last written point", 0,
289                               DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT);
290   }
291
292   return DOWNLOAD_INTERRUPT_REASON_NONE;
293 }
294
295 void BaseFile::Close() {
296   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
297
298   bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_CLOSED);
299
300   if (file_.IsValid()) {
301     // Currently we don't really care about the return value, since if it fails
302     // theres not much we can do.  But we might in the future.
303     file_.Flush();
304     ClearFile();
305   }
306 }
307
308 void BaseFile::ClearFile() {
309   // This should only be called when we have a stream.
310   DCHECK(file_.IsValid());
311   file_.Close();
312   bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_OPENED);
313 }
314
315 DownloadInterruptReason BaseFile::LogNetError(
316     const char* operation,
317     net::Error error) {
318   bound_net_log_.AddEvent(
319       net::NetLog::TYPE_DOWNLOAD_FILE_ERROR,
320       base::Bind(&FileErrorNetLogCallback, operation, error));
321   return ConvertNetErrorToInterruptReason(error, DOWNLOAD_INTERRUPT_FROM_DISK);
322 }
323
324 DownloadInterruptReason BaseFile::LogSystemError(
325     const char* operation,
326     logging::SystemErrorCode os_error) {
327   // There's no direct conversion from a system error to an interrupt reason.
328   net::Error net_error = net::MapSystemError(os_error);
329   return LogInterruptReason(
330       operation, os_error,
331       ConvertNetErrorToInterruptReason(
332           net_error, DOWNLOAD_INTERRUPT_FROM_DISK));
333 }
334
335 DownloadInterruptReason BaseFile::LogInterruptReason(
336     const char* operation,
337     int os_error,
338     DownloadInterruptReason reason) {
339   bound_net_log_.AddEvent(
340       net::NetLog::TYPE_DOWNLOAD_FILE_ERROR,
341       base::Bind(&FileInterruptedNetLogCallback, operation, os_error, reason));
342   return reason;
343 }
344
345 }  // namespace content