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 "components/download/public/common/base_file.h"
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/files/file.h"
12 #include "base/files/file_util.h"
13 #include "base/format_macros.h"
14 #include "base/logging.h"
15 #include "base/macros.h"
16 #include "base/metrics/histogram_functions.h"
17 #include "base/numerics/safe_conversions.h"
18 #include "base/pickle.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/threading/thread_restrictions.h"
21 #include "base/trace_event/trace_event.h"
22 #include "build/build_config.h"
23 #include "components/download/public/common/download_interrupt_reasons_utils.h"
24 #include "components/download/public/common/download_item.h"
25 #include "components/download/public/common/download_stats.h"
26 #include "components/download/quarantine/quarantine.h"
27 #include "crypto/secure_hash.h"
28 
29 #if defined(OS_WIN)
30 #include "components/services/quarantine/public/cpp/quarantine_features_win.h"
31 #endif  // defined(OS_WIN)
32 
33 #if defined(OS_ANDROID)
34 #include "base/android/content_uri_utils.h"
35 #include "components/download/internal/common/android/download_collection_bridge.h"
36 #endif  // defined(OS_ANDROID)
37 
38 #define CONDITIONAL_TRACE(trace)                  \
39   do {                                            \
40     if (download_id_ != DownloadItem::kInvalidId) \
41       TRACE_EVENT_##trace;                        \
42   } while (0)
43 
44 namespace download {
45 
46 namespace {
47 class FileErrorData : public base::trace_event::ConvertableToTraceFormat {
48  public:
FileErrorData(const char * operation,int os_error,DownloadInterruptReason interrupt_reason)49   FileErrorData(const char* operation,
50                 int os_error,
51                 DownloadInterruptReason interrupt_reason)
52       : operation_(operation),
53         os_error_(os_error),
54         interrupt_reason_(interrupt_reason) {}
55 
56   ~FileErrorData() override = default;
57 
AppendAsTraceFormat(std::string * out) const58   void AppendAsTraceFormat(std::string* out) const override {
59     out->append("{");
60     out->append(
61         base::StringPrintf("\"operation\":\"%s\",", operation_.c_str()));
62     out->append(base::StringPrintf("\"os_error\":\"%d\",", os_error_));
63     out->append(base::StringPrintf(
64         "\"interrupt_reason\":\"%s\",",
65         DownloadInterruptReasonToString(interrupt_reason_).c_str()));
66     out->append("}");
67   }
68 
69  private:
70   std::string operation_;
71   int os_error_;
72   DownloadInterruptReason interrupt_reason_;
73   DISALLOW_COPY_AND_ASSIGN(FileErrorData);
74 };
75 
InitializeFile(base::File * file,const base::FilePath & file_path)76 void InitializeFile(base::File* file, const base::FilePath& file_path) {
77 #if defined(OS_ANDROID)
78   if (file_path.IsContentUri()) {
79     *file = DownloadCollectionBridge::OpenIntermediateUri(file_path);
80     return;
81   }
82 #endif  // defined(OS_ANDROID)
83 
84   // Use exclusive write to prevent another process from writing the file.
85   file->Initialize(
86       file_path,
87       base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_WRITE |
88           base::File::FLAG_READ
89 #if defined(OS_WIN)
90           // Don't allow other process to write to the file while Chrome is
91           // writing to it. On posix systems, use FLAG_EXCLUSIVE_WRITE will
92           // cause file creation to fail if the file already exists.
93           | base::File::FLAG_EXCLUSIVE_WRITE
94 #endif  // defined(OS_WIN)
95   );
96 }
97 
DeleteFile(const base::FilePath & file_path)98 void DeleteFile(const base::FilePath& file_path) {
99 #if defined(OS_ANDROID)
100   if (file_path.IsContentUri()) {
101     DownloadCollectionBridge::DeleteIntermediateUri(file_path);
102     return;
103   }
104 #endif  // defined(OS_ANDROID)
105   base::DeleteFile(file_path, false);
106 }
107 
108 }  // namespace
109 
BaseFile(uint32_t download_id)110 BaseFile::BaseFile(uint32_t download_id) : download_id_(download_id) {
111   DETACH_FROM_SEQUENCE(sequence_checker_);
112 }
113 
~BaseFile()114 BaseFile::~BaseFile() {
115   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
116   if (detached_)
117     Close();
118   else
119     Cancel();  // Will delete the file.
120 }
121 
Initialize(const base::FilePath & full_path,const base::FilePath & default_directory,base::File file,int64_t bytes_so_far,const std::string & hash_so_far,std::unique_ptr<crypto::SecureHash> hash_state,bool is_sparse_file,int64_t * const bytes_wasted)122 DownloadInterruptReason BaseFile::Initialize(
123     const base::FilePath& full_path,
124     const base::FilePath& default_directory,
125     base::File file,
126     int64_t bytes_so_far,
127     const std::string& hash_so_far,
128     std::unique_ptr<crypto::SecureHash> hash_state,
129     bool is_sparse_file,
130     int64_t* const bytes_wasted) {
131   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
132   DCHECK(!detached_);
133 
134   if (full_path.empty()) {
135     base::FilePath temp_file;
136     if ((default_directory.empty() ||
137          !base::CreateTemporaryFileInDir(default_directory, &temp_file)) &&
138         !base::CreateTemporaryFile(&temp_file)) {
139       return LogInterruptReason("Unable to create", 0,
140                                 DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
141     }
142     full_path_ = temp_file;
143   } else {
144     full_path_ = full_path;
145   }
146 
147   bytes_so_far_ = bytes_so_far;
148   secure_hash_ = std::move(hash_state);
149   is_sparse_file_ = is_sparse_file;
150   // Sparse file doesn't validate hash.
151   if (is_sparse_file_)
152     secure_hash_.reset();
153   file_ = std::move(file);
154 
155   return Open(hash_so_far, bytes_wasted);
156 }
157 
AppendDataToFile(const char * data,size_t data_len)158 DownloadInterruptReason BaseFile::AppendDataToFile(const char* data,
159                                                    size_t data_len) {
160   DCHECK(!is_sparse_file_);
161   return WriteDataToFile(bytes_so_far_, data, data_len);
162 }
163 
WriteDataToFile(int64_t offset,const char * data,size_t data_len)164 DownloadInterruptReason BaseFile::WriteDataToFile(int64_t offset,
165                                                   const char* data,
166                                                   size_t data_len) {
167   // NOTE(benwells): The above DCHECK won't be present in release builds,
168   // so we log any occurences to see how common this error is in the wild.
169   if (detached_)
170     RecordDownloadCount(APPEND_TO_DETACHED_FILE_COUNT);
171 
172   if (!file_.IsValid()) {
173     return LogInterruptReason("No file stream on append", 0,
174                               DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
175   }
176 
177   // TODO(phajdan.jr): get rid of this check.
178   if (data_len == 0)
179     return DOWNLOAD_INTERRUPT_REASON_NONE;
180 
181   // Use nestable async event instead of sync event so that all the writes
182   // belong to the same download will be grouped together.
183   CONDITIONAL_TRACE(
184       NESTABLE_ASYNC_BEGIN0("download", "DownloadFileWrite", download_id_));
185 
186   if (bytes_so_far_ != offset) {
187     // A hole is created in the file.
188     is_sparse_file_ = true;
189     secure_hash_.reset();
190   }
191 
192   // Writes to the file.
193   int64_t len = base::saturated_cast<int64_t>(data_len);
194   const char* current_data = data;
195   int64_t current_offset = offset;
196   while (len > 0) {
197     // |write_result| may be less than |len|, and return an error on the next
198     // write call when the disk is unavaliable.
199     int write_result = file_.Write(current_offset, current_data, len);
200     DCHECK_NE(0, write_result);
201 
202     // Report errors on file writes.
203     if (write_result < 0)
204       return LogSystemError("Write", logging::GetLastSystemErrorCode());
205 
206     // Update status.
207     DCHECK_LE(write_result, len);
208     len -= write_result;
209     current_data += write_result;
210     current_offset += write_result;
211     bytes_so_far_ += write_result;
212   }
213 
214   CONDITIONAL_TRACE(NESTABLE_ASYNC_END1("download", "DownloadFileWrite",
215                                         download_id_, "bytes", data_len));
216 
217   if (secure_hash_)
218     secure_hash_->Update(data, data_len);
219 
220   return DOWNLOAD_INTERRUPT_REASON_NONE;
221 }
222 
ValidateDataInFile(int64_t offset,const char * data,size_t data_len)223 bool BaseFile::ValidateDataInFile(int64_t offset,
224                                   const char* data,
225                                   size_t data_len) {
226   if (!file_.IsValid())
227     return false;
228 
229   // Only validate the first chunk of the file. So |offset| cannot be
230   // larger than bytes received.
231   if (offset > bytes_so_far_)
232     return false;
233 
234   if (data_len <= 0)
235     return true;
236 
237   std::unique_ptr<char[]> buffer(new char[data_len]);
238   int bytes_read = file_.Read(offset, buffer.get(), data_len);
239   if (bytes_read < 0 || static_cast<size_t>(bytes_read) < data_len)
240     return false;
241 
242   return memcmp(data, buffer.get(), data_len) == 0;
243 }
244 
Rename(const base::FilePath & new_path)245 DownloadInterruptReason BaseFile::Rename(const base::FilePath& new_path) {
246   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
247   DownloadInterruptReason rename_result = DOWNLOAD_INTERRUPT_REASON_NONE;
248 
249   // If the new path is same as the old one, there is no need to perform the
250   // following renaming logic.
251   if (new_path == full_path_)
252     return DOWNLOAD_INTERRUPT_REASON_NONE;
253 
254   // Save the information whether the download is in progress because
255   // it will be overwritten by closing the file.
256   bool was_in_progress = in_progress();
257 
258   Close();
259 
260   CONDITIONAL_TRACE(BEGIN2("download", "DownloadFileRename", "old_filename",
261                            full_path_.AsUTF8Unsafe(), "new_filename",
262                            new_path.AsUTF8Unsafe()));
263   bool need_to_move_file = true;
264 #if defined(OS_ANDROID)
265   if (new_path.IsContentUri()) {
266     rename_result = DownloadCollectionBridge::MoveFileToIntermediateUri(
267         full_path_, new_path);
268     need_to_move_file = false;
269   }
270 #endif
271   if (need_to_move_file) {
272     base::CreateDirectory(new_path.DirName());
273 
274     // A simple rename wouldn't work here since we want the file to have
275     // permissions / security descriptors that makes sense in the new directory.
276     rename_result = MoveFileAndAdjustPermissions(new_path);
277   }
278 
279   CONDITIONAL_TRACE(END0("download", "DownloadFileRename"));
280 
281   if (rename_result == DOWNLOAD_INTERRUPT_REASON_NONE)
282     full_path_ = new_path;
283 
284   // Re-open the file if we were still using it regardless of the interrupt
285   // reason.
286   DownloadInterruptReason open_result = DOWNLOAD_INTERRUPT_REASON_NONE;
287   if (was_in_progress) {
288     int64_t bytes_wasted;  // Do not need to use bytes_wasted.
289     open_result = Open(std::string(), &bytes_wasted);
290   }
291 
292   return rename_result == DOWNLOAD_INTERRUPT_REASON_NONE ? open_result
293                                                          : rename_result;
294 }
295 
Detach()296 void BaseFile::Detach() {
297   weak_factory_.InvalidateWeakPtrs();
298   detached_ = true;
299   CONDITIONAL_TRACE(
300       INSTANT0("download", "DownloadFileDetached", TRACE_EVENT_SCOPE_THREAD));
301 }
302 
Cancel()303 void BaseFile::Cancel() {
304   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
305   DCHECK(!detached_);
306 
307   CONDITIONAL_TRACE(
308       INSTANT0("download", "DownloadCancelled", TRACE_EVENT_SCOPE_THREAD));
309 
310   Close();
311 
312   if (!full_path_.empty()) {
313     CONDITIONAL_TRACE(
314         INSTANT0("download", "DownloadFileDeleted", TRACE_EVENT_SCOPE_THREAD));
315     DeleteFile(full_path_);
316   }
317 
318   Detach();
319 }
320 
Finish()321 std::unique_ptr<crypto::SecureHash> BaseFile::Finish() {
322   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
323 
324   // TODO(qinmin): verify that all the holes have been filled.
325   if (is_sparse_file_)
326     CalculatePartialHash(std::string());
327   Close();
328   return std::move(secure_hash_);
329 }
330 
DebugString() const331 std::string BaseFile::DebugString() const {
332   return base::StringPrintf(
333       "{ "
334       " full_path_ = \"%" PRFilePath
335       "\""
336       " bytes_so_far_ = %" PRId64 " detached_ = %c }",
337       full_path_.value().c_str(), bytes_so_far_, detached_ ? 'T' : 'F');
338 }
339 
CalculatePartialHash(const std::string & hash_to_expect)340 DownloadInterruptReason BaseFile::CalculatePartialHash(
341     const std::string& hash_to_expect) {
342   secure_hash_ = crypto::SecureHash::Create(crypto::SecureHash::SHA256);
343 
344   if (bytes_so_far_ == 0)
345     return DOWNLOAD_INTERRUPT_REASON_NONE;
346 
347   if (file_.Seek(base::File::FROM_BEGIN, 0) != 0)
348     return LogSystemError("Seek partial file",
349                           logging::GetLastSystemErrorCode());
350 
351   const size_t kMinBufferSize = secure_hash_->GetHashLength();
352   const size_t kMaxBufferSize = 1024 * 512;
353   static_assert(kMaxBufferSize <= std::numeric_limits<int>::max(),
354                 "kMaxBufferSize must fit on an int");
355 
356   // The size of the buffer is:
357   // - at least kMinBufferSize so that we can use it to hold the hash as well.
358   // - at most kMaxBufferSize so that there's a reasonable bound.
359   // - not larger than |bytes_so_far_| unless bytes_so_far_ is less than the
360   //   hash size.
361   std::vector<char> buffer(std::max<int64_t>(
362       kMinBufferSize, std::min<int64_t>(kMaxBufferSize, bytes_so_far_)));
363 
364   int64_t current_position = 0;
365   while (current_position < bytes_so_far_) {
366     // While std::min needs to work with int64_t, the result is always at most
367     // kMaxBufferSize, which fits on an int.
368     int bytes_to_read =
369         std::min<int64_t>(buffer.size(), bytes_so_far_ - current_position);
370     int length = file_.ReadAtCurrentPos(&buffer.front(), bytes_to_read);
371     if (length == -1) {
372       return LogInterruptReason("Reading partial file",
373                                 logging::GetLastSystemErrorCode(),
374                                 DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT);
375     }
376 
377     if (length == 0)
378       break;
379 
380     secure_hash_->Update(&buffer.front(), length);
381     current_position += length;
382   }
383 
384   if (current_position != bytes_so_far_) {
385     return LogInterruptReason("Verifying prefix hash", 0,
386                               DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT);
387   }
388 
389   if (!hash_to_expect.empty()) {
390     DCHECK_EQ(secure_hash_->GetHashLength(), hash_to_expect.size());
391     DCHECK(buffer.size() >= secure_hash_->GetHashLength());
392     std::unique_ptr<crypto::SecureHash> partial_hash(secure_hash_->Clone());
393     partial_hash->Finish(&buffer.front(), buffer.size());
394 
395     if (memcmp(&buffer.front(), hash_to_expect.c_str(),
396                partial_hash->GetHashLength())) {
397       return LogInterruptReason("Verifying prefix hash", 0,
398                                 DOWNLOAD_INTERRUPT_REASON_FILE_HASH_MISMATCH);
399     }
400   }
401 
402   return DOWNLOAD_INTERRUPT_REASON_NONE;
403 }
404 
Open(const std::string & hash_so_far,int64_t * const bytes_wasted)405 DownloadInterruptReason BaseFile::Open(const std::string& hash_so_far,
406                                        int64_t* const bytes_wasted) {
407   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
408   DCHECK(!detached_);
409   DCHECK(!full_path_.empty());
410 
411   // Create a new file if it is not provided.
412   if (!file_.IsValid()) {
413     InitializeFile(&file_, full_path_);
414     if (!file_.IsValid()) {
415       return LogNetError("Open/Initialize File",
416                          net::FileErrorToNetError(file_.error_details()));
417     }
418   }
419 
420   CONDITIONAL_TRACE(NESTABLE_ASYNC_BEGIN2(
421       "download", "DownloadFileOpen", download_id_, "file_name",
422       full_path_.AsUTF8Unsafe(), "bytes_so_far", bytes_so_far_));
423 
424   // For sparse file, skip hash validation.
425   if (is_sparse_file_) {
426     if (file_.GetLength() < bytes_so_far_) {
427       *bytes_wasted = bytes_so_far_;
428       ClearFile();
429       return LogInterruptReason("File has fewer written bytes than expected", 0,
430                                 DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT);
431     }
432     return DOWNLOAD_INTERRUPT_REASON_NONE;
433   }
434 
435   if (!secure_hash_) {
436     DownloadInterruptReason reason = CalculatePartialHash(hash_so_far);
437     if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
438       *bytes_wasted = file_.GetLength();
439       ClearFile();
440       return reason;
441     }
442   }
443 
444   int64_t file_size = file_.Seek(base::File::FROM_END, 0);
445   if (file_size < 0) {
446     logging::SystemErrorCode error = logging::GetLastSystemErrorCode();
447     ClearFile();
448     return LogSystemError("Seeking to end", error);
449   } else if (file_size > bytes_so_far_) {
450     // The file is larger than we expected.
451     // This is OK, as long as we don't use the extra.
452     // Truncate the file.
453     *bytes_wasted = file_size - bytes_so_far_;
454     if (!file_.SetLength(bytes_so_far_) ||
455         file_.Seek(base::File::FROM_BEGIN, bytes_so_far_) != bytes_so_far_) {
456       logging::SystemErrorCode error = logging::GetLastSystemErrorCode();
457       *bytes_wasted = file_size;
458       ClearFile();
459       return LogSystemError("Truncating to last known offset", error);
460     }
461   } else if (file_size < bytes_so_far_) {
462     // The file is shorter than we expected.  Our hashes won't be valid.
463     *bytes_wasted = bytes_so_far_;
464     ClearFile();
465     return LogInterruptReason("Unable to seek to last written point", 0,
466                               DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT);
467   }
468 
469   return DOWNLOAD_INTERRUPT_REASON_NONE;
470 }
471 
Close()472 void BaseFile::Close() {
473   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
474 
475   if (file_.IsValid()) {
476     // Currently we don't really care about the return value, since if it fails
477     // theres not much we can do.  But we might in the future.
478     file_.Flush();
479     ClearFile();
480   }
481 }
482 
ClearFile()483 void BaseFile::ClearFile() {
484   // This should only be called when we have a stream.
485   DCHECK(file_.IsValid());
486   file_.Close();
487   CONDITIONAL_TRACE(
488       NESTABLE_ASYNC_END0("download", "DownloadFileOpen", download_id_));
489 }
490 
LogNetError(const char * operation,net::Error error)491 DownloadInterruptReason BaseFile::LogNetError(const char* operation,
492                                               net::Error error) {
493   CONDITIONAL_TRACE(INSTANT2("download", "DownloadFileError",
494                              TRACE_EVENT_SCOPE_THREAD, "operation", operation,
495                              "net_error", error));
496   return ConvertNetErrorToInterruptReason(error, DOWNLOAD_INTERRUPT_FROM_DISK);
497 }
498 
LogSystemError(const char * operation,logging::SystemErrorCode os_error)499 DownloadInterruptReason BaseFile::LogSystemError(
500     const char* operation,
501     logging::SystemErrorCode os_error) {
502   // There's no direct conversion from a system error to an interrupt reason.
503   base::File::Error file_error = base::File::OSErrorToFileError(os_error);
504   return LogInterruptReason(operation, os_error,
505                             ConvertFileErrorToInterruptReason(file_error));
506 }
507 
LogInterruptReason(const char * operation,int os_error,DownloadInterruptReason reason)508 DownloadInterruptReason BaseFile::LogInterruptReason(
509     const char* operation,
510     int os_error,
511     DownloadInterruptReason reason) {
512   DVLOG(1) << __func__ << "() operation:" << operation
513            << " os_error:" << os_error
514            << " reason:" << DownloadInterruptReasonToString(reason);
515   auto error_data =
516       std::make_unique<FileErrorData>(operation, os_error, reason);
517   CONDITIONAL_TRACE(INSTANT1("download", "DownloadFileError",
518                              TRACE_EVENT_SCOPE_THREAD, "file_error",
519                              std::move(error_data)));
520   return reason;
521 }
522 
523 #if defined(OS_ANDROID)
PublishDownload()524 DownloadInterruptReason BaseFile::PublishDownload() {
525   Close();
526   base::FilePath new_path =
527       DownloadCollectionBridge::PublishDownload(full_path_);
528   if (!new_path.empty()) {
529     full_path_ = new_path;
530     return DOWNLOAD_INTERRUPT_REASON_NONE;
531   }
532   return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
533 }
534 #endif  // defined(OS_ANDROID)
535 
536 namespace {
537 
QuarantineFileResultToReason(quarantine::mojom::QuarantineFileResult result)538 DownloadInterruptReason QuarantineFileResultToReason(
539     quarantine::mojom::QuarantineFileResult result) {
540   switch (result) {
541     case quarantine::mojom::QuarantineFileResult::OK:
542       return DOWNLOAD_INTERRUPT_REASON_NONE;
543     case quarantine::mojom::QuarantineFileResult::VIRUS_INFECTED:
544       return DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED;
545     case quarantine::mojom::QuarantineFileResult::SECURITY_CHECK_FAILED:
546       return DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED;
547     case quarantine::mojom::QuarantineFileResult::BLOCKED_BY_POLICY:
548       return DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED;
549     case quarantine::mojom::QuarantineFileResult::ACCESS_DENIED:
550       return DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
551 
552     case quarantine::mojom::QuarantineFileResult::FILE_MISSING:
553       // Don't have a good interrupt reason here. This return code means that
554       // the file at |full_path_| went missing before QuarantineFile got to
555       // look at it. Not expected to happen, but we've seen instances where a
556       // file goes missing immediately after BaseFile closes the handle.
557       //
558       // Intentionally using a different error message than
559       // SECURITY_CHECK_FAILED in order to distinguish the two.
560       return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
561 
562     case quarantine::mojom::QuarantineFileResult::ANNOTATION_FAILED:
563       // This means that the mark-of-the-web couldn't be applied. The file is
564       // already on the file system under its final target name.
565       //
566       // Causes of failed annotations typically aren't transient. E.g. the
567       // target file system may not support extended attributes or alternate
568       // streams. We are going to allow these downloads to progress on the
569       // assumption that failures to apply MOTW can't reliably be introduced
570       // remotely.
571       return DOWNLOAD_INTERRUPT_REASON_NONE;
572   }
573   return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
574 }
575 
576 // Given a source and a referrer, determines the "safest" URL that can be used
577 // to determine the authority of the download source. Returns an empty URL if no
578 // HTTP/S URL can be determined for the <|source_url|, |referrer_url|> pair.
GetEffectiveAuthorityURL(const GURL & source_url,const GURL & referrer_url)579 GURL GetEffectiveAuthorityURL(const GURL& source_url,
580                               const GURL& referrer_url) {
581   if (source_url.is_valid()) {
582     // http{,s} has an authority and are supported.
583     if (source_url.SchemeIsHTTPOrHTTPS())
584       return source_url;
585 
586     // If the download source is file:// ideally we should copy the MOTW from
587     // the original file, but given that Chrome/Chromium places strict
588     // restrictions on which schemes can reference file:// URLs, this code is
589     // going to assume that at this point it's okay to treat this download as
590     // being from the local system.
591     if (source_url.SchemeIsFile())
592       return source_url;
593 
594     // ftp:// has an authority.
595     if (source_url.SchemeIs(url::kFtpScheme))
596       return source_url;
597   }
598 
599   if (referrer_url.is_valid() && referrer_url.SchemeIsHTTPOrHTTPS())
600     return referrer_url;
601 
602   return GURL();
603 }
604 
605 }  // namespace
606 
607 #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_BSD)
608 
AnnotateWithSourceInformationSync(const std::string & client_guid,const GURL & source_url,const GURL & referrer_url)609 DownloadInterruptReason BaseFile::AnnotateWithSourceInformationSync(
610     const std::string& client_guid,
611     const GURL& source_url,
612     const GURL& referrer_url) {
613   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
614   DCHECK(!detached_);
615   DCHECK(!full_path_.empty());
616 
617   CONDITIONAL_TRACE(BEGIN0("download", "DownloadFileAnnotate"));
618   QuarantineFileResult result = QuarantineFile(
619       full_path_, GetEffectiveAuthorityURL(source_url, referrer_url),
620       referrer_url, client_guid);
621   CONDITIONAL_TRACE(END0("download", "DownloadFileAnnotate"));
622 
623   return QuarantineFileResultToReason(result);
624 }
625 #else  // !OS_WIN && !OS_MACOSX && !OS_LINUX
AnnotateWithSourceInformationSync(const std::string & client_guid,const GURL & source_url,const GURL & referrer_url)626 DownloadInterruptReason BaseFile::AnnotateWithSourceInformationSync(
627     const std::string& client_guid,
628     const GURL& source_url,
629     const GURL& referrer_url) {
630   return DOWNLOAD_INTERRUPT_REASON_NONE;
631 }
632 #endif
633 
OnFileQuarantined(bool connection_error,quarantine::mojom::QuarantineFileResult result)634 void BaseFile::OnFileQuarantined(
635     bool connection_error,
636     quarantine::mojom::QuarantineFileResult result) {
637   base::UmaHistogramBoolean("Download.QuarantineService.ConnectionError",
638                             connection_error);
639 
640   DCHECK(on_annotation_done_callback_);
641   quarantine_service_.reset();
642   std::move(on_annotation_done_callback_)
643       .Run(QuarantineFileResultToReason(result));
644 }
645 
OnQuarantineServiceError(const GURL & source_url,const GURL & referrer_url)646 void BaseFile::OnQuarantineServiceError(const GURL& source_url,
647                                         const GURL& referrer_url) {
648 #if defined(OS_WIN)
649   if (base::FeatureList::IsEnabled(quarantine::kOutOfProcessQuarantine)) {
650     OnFileQuarantined(/*connection_error=*/true,
651                       quarantine::SetInternetZoneIdentifierDirectly(
652                           full_path_, source_url, referrer_url));
653     return;
654   }
655 #endif  // defined(OS_WIN)
656 
657   CHECK(false) << "In-process quarantine service should not have failed.";
658 }
659 
AnnotateWithSourceInformation(const std::string & client_guid,const GURL & source_url,const GURL & referrer_url,mojo::PendingRemote<quarantine::mojom::Quarantine> remote_quarantine,OnAnnotationDoneCallback on_annotation_done_callback)660 void BaseFile::AnnotateWithSourceInformation(
661     const std::string& client_guid,
662     const GURL& source_url,
663     const GURL& referrer_url,
664     mojo::PendingRemote<quarantine::mojom::Quarantine> remote_quarantine,
665     OnAnnotationDoneCallback on_annotation_done_callback) {
666   GURL authority_url = GetEffectiveAuthorityURL(source_url, referrer_url);
667   if (!remote_quarantine) {
668 #if defined(OS_WIN)
669     QuarantineFileResult result = quarantine::SetInternetZoneIdentifierDirectly(
670         full_path_, authority_url, referrer_url);
671 #else
672     QuarantineFileResult result = QuarantineFileResult::ANNOTATION_FAILED;
673 #endif
674     std::move(on_annotation_done_callback)
675         .Run(QuarantineFileResultToReason(result));
676   } else {
677     quarantine_service_.Bind(std::move(remote_quarantine));
678 
679     on_annotation_done_callback_ = std::move(on_annotation_done_callback);
680 
681     quarantine_service_.set_disconnect_handler(base::BindOnce(
682         &BaseFile::OnQuarantineServiceError, weak_factory_.GetWeakPtr(),
683         authority_url, referrer_url));
684 
685     quarantine_service_->QuarantineFile(
686         full_path_, authority_url, referrer_url, client_guid,
687         base::BindOnce(&BaseFile::OnFileQuarantined, weak_factory_.GetWeakPtr(),
688                        false));
689   }
690 }
691 
692 }  // namespace download
693