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