• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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