• 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/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"
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 
165   // Re-open the file if we were still using it regardless of the interrupt
166   // reason.
167   DownloadInterruptReason open_result = DOWNLOAD_INTERRUPT_REASON_NONE;
168   if (was_in_progress)
169     open_result = Open();
170 
171   bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_RENAMED);
172   return rename_result == DOWNLOAD_INTERRUPT_REASON_NONE ? open_result
173                                                          : rename_result;
174 }
175 
Detach()176 void BaseFile::Detach() {
177   detached_ = true;
178   bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DETACHED);
179 }
180 
Cancel()181 void BaseFile::Cancel() {
182   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
183   DCHECK(!detached_);
184 
185   bound_net_log_.AddEvent(net::NetLog::TYPE_CANCELLED);
186 
187   Close();
188 
189   if (!full_path_.empty()) {
190     bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DELETED);
191     base::DeleteFile(full_path_, false);
192   }
193 
194   Detach();
195 }
196 
Finish()197 void BaseFile::Finish() {
198   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
199 
200   if (calculate_hash_)
201     secure_hash_->Finish(sha256_hash_, crypto::kSHA256Length);
202 
203   Close();
204 }
205 
SetClientGuid(const std::string & guid)206 void BaseFile::SetClientGuid(const std::string& guid) {
207   client_guid_ = guid;
208 }
209 
210 // OS_WIN, OS_MACOSX and OS_LINUX have specialized implementations.
211 #if !defined(OS_WIN) && !defined(OS_MACOSX) && !defined(OS_LINUX)
AnnotateWithSourceInformation()212 DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() {
213   return DOWNLOAD_INTERRUPT_REASON_NONE;
214 }
215 #endif
216 
GetHash(std::string * hash)217 bool BaseFile::GetHash(std::string* hash) {
218   DCHECK(!detached_);
219   hash->assign(reinterpret_cast<const char*>(sha256_hash_),
220                sizeof(sha256_hash_));
221   return (calculate_hash_ && !in_progress());
222 }
223 
GetHashState()224 std::string BaseFile::GetHashState() {
225   if (!calculate_hash_)
226     return std::string();
227 
228   Pickle hash_state;
229   if (!secure_hash_->Serialize(&hash_state))
230     return std::string();
231 
232   return std::string(reinterpret_cast<const char*>(hash_state.data()),
233                      hash_state.size());
234 }
235 
236 // static
IsEmptyHash(const std::string & hash)237 bool BaseFile::IsEmptyHash(const std::string& hash) {
238   return (hash.size() == crypto::kSHA256Length &&
239           0 == memcmp(hash.data(), kEmptySha256Hash, crypto::kSHA256Length));
240 }
241 
DebugString() const242 std::string BaseFile::DebugString() const {
243   return base::StringPrintf("{ source_url_ = \"%s\""
244                             " full_path_ = \"%" PRFilePath "\""
245                             " bytes_so_far_ = %" PRId64
246                             " detached_ = %c }",
247                             source_url_.spec().c_str(),
248                             full_path_.value().c_str(),
249                             bytes_so_far_,
250                             detached_ ? 'T' : 'F');
251 }
252 
Open()253 DownloadInterruptReason BaseFile::Open() {
254   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
255   DCHECK(!detached_);
256   DCHECK(!full_path_.empty());
257 
258   bound_net_log_.BeginEvent(
259       net::NetLog::TYPE_DOWNLOAD_FILE_OPENED,
260       base::Bind(&FileOpenedNetLogCallback, &full_path_, bytes_so_far_));
261 
262   // Create a new file if it is not provided.
263   if (!file_.IsValid()) {
264     file_.Initialize(
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()));
269     }
270   }
271 
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);
275   if (file_size < 0) {
276     logging::SystemErrorCode error = logging::GetLastSystemErrorCode();
277     ClearFile();
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();
286       ClearFile();
287       return LogSystemError("Truncate",  error);
288     }
289   } else if (file_size < bytes_so_far_) {
290     // The file is shorter than we expected.  Our hashes won't be valid.
291     ClearFile();
292     return LogInterruptReason("Unable to seek to last written point", 0,
293                               DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT);
294   }
295 
296   return DOWNLOAD_INTERRUPT_REASON_NONE;
297 }
298 
Close()299 void BaseFile::Close() {
300   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
301 
302   bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_CLOSED);
303 
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.
307     file_.Flush();
308     ClearFile();
309   }
310 }
311 
ClearFile()312 void BaseFile::ClearFile() {
313   // This should only be called when we have a stream.
314   DCHECK(file_.IsValid());
315   file_.Close();
316   bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_OPENED);
317 }
318 
LogNetError(const char * operation,net::Error error)319 DownloadInterruptReason BaseFile::LogNetError(
320     const char* operation,
321     net::Error error) {
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);
326 }
327 
LogSystemError(const char * operation,logging::SystemErrorCode os_error)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(
334       operation, os_error,
335       ConvertFileErrorToInterruptReason(file_error));
336 }
337 
LogInterruptReason(const char * operation,int os_error,DownloadInterruptReason reason)338 DownloadInterruptReason BaseFile::LogInterruptReason(
339     const char* operation,
340     int os_error,
341     DownloadInterruptReason reason) {
342   bound_net_log_.AddEvent(
343       net::NetLog::TYPE_DOWNLOAD_FILE_ERROR,
344       base::Bind(&FileInterruptedNetLogCallback, operation, os_error, reason));
345   return reason;
346 }
347 
348 }  // namespace content
349