1 // Copyright 2014 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.
5 #include "chrome/browser/safe_browsing/incident_reporting/download_metadata_manager.h"
10 #include "base/callback.h"
11 #include "base/files/file.h"
12 #include "base/files/file_util.h"
13 #include "base/files/important_file_writer.h"
14 #include "base/location.h"
15 #include "base/macros.h"
16 #include "base/metrics/histogram.h"
17 #include "base/sequenced_task_runner.h"
18 #include "base/threading/sequenced_worker_pool.h"
19 #include "chrome/common/safe_browsing/csd.pb.h"
20 #include "content/public/browser/browser_context.h"
21 #include "content/public/browser/download_item.h"
23 namespace safe_browsing
{
27 // Histogram bucket values for metadata read operations. Do not reorder.
28 enum MetadataReadResult
{
40 // Histogram bucket values for metadata write operations. Do not reorder.
41 enum MetadataWriteResult
{
43 SERIALIZATION_FAILURE
= 1,
48 // The name of the metadata file in the profile directory.
49 const base::FilePath::CharType kDownloadMetadataBasename
[] =
50 FILE_PATH_LITERAL("DownloadMetadata");
53 // DownloadItemData ------------------------------------------------------------
55 // A UserData object that holds the ClientDownloadRequest for a download while
57 class DownloadItemData
: public base::SupportsUserData::Data
{
59 // Sets the ClientDownloadRequest for a given DownloadItem.
60 static void SetRequestForDownload(content::DownloadItem
* item
,
61 scoped_ptr
<ClientDownloadRequest
> request
);
63 // Returns the ClientDownloadRequest for a download or null if there is none.
64 static scoped_ptr
<ClientDownloadRequest
> TakeRequestForDownload(
65 content::DownloadItem
* item
);
68 // A unique id for associating metadata with a content::DownloadItem.
69 static const void* const kKey_
;
71 explicit DownloadItemData(scoped_ptr
<ClientDownloadRequest
> request
)
72 : request_(request
.Pass()) {}
73 ~DownloadItemData() override
{}
75 scoped_ptr
<ClientDownloadRequest
> request_
;
77 DISALLOW_COPY_AND_ASSIGN(DownloadItemData
);
80 // Make the key's value unique by setting it to its own location.
82 const void* const DownloadItemData::kKey_
= &DownloadItemData::kKey_
;
85 void DownloadItemData::SetRequestForDownload(
86 content::DownloadItem
* item
,
87 scoped_ptr
<ClientDownloadRequest
> request
) {
88 item
->SetUserData(&kKey_
, new DownloadItemData(request
.Pass()));
92 scoped_ptr
<ClientDownloadRequest
> DownloadItemData::TakeRequestForDownload(
93 content::DownloadItem
* item
) {
94 DownloadItemData
* data
=
95 static_cast<DownloadItemData
*>(item
->GetUserData(&kKey_
));
98 scoped_ptr
<ClientDownloadRequest
> request
= data
->request_
.Pass();
99 item
->RemoveUserData(&kKey_
);
100 return request
.Pass();
104 // Utility functions------------------------------------------------------------
106 // Returns the path to the metadata file for |browser_context|.
107 base::FilePath
GetMetadataPath(content::BrowserContext
* browser_context
) {
108 return browser_context
->GetPath().Append(kDownloadMetadataBasename
);
111 // Returns true if |metadata| appears to be valid.
112 bool MetadataIsValid(const DownloadMetadata
& metadata
) {
113 return (metadata
.has_download_id() &&
114 metadata
.has_download() &&
115 metadata
.download().has_download() &&
116 metadata
.download().download().has_url());
119 // Reads and parses a DownloadMetadata message from |metadata_path| into
121 void ReadMetadataOnWorkerPool(const base::FilePath
& metadata_path
,
122 DownloadMetadata
* metadata
) {
125 MetadataReadResult result
= NUM_READ_RESULTS
;
126 File
metadata_file(metadata_path
, File::FLAG_OPEN
| File::FLAG_READ
);
127 if (metadata_file
.IsValid()) {
128 base::File::Info info
;
129 if (metadata_file
.GetInfo(&info
)) {
130 if (info
.size
<= INT_MAX
) {
131 const int size
= static_cast<int>(info
.size
);
132 scoped_ptr
<char[]> file_data(new char[info
.size
]);
133 if (metadata_file
.Read(0, file_data
.get(), size
)) {
134 if (!metadata
->ParseFromArray(file_data
.get(), size
))
135 result
= PARSE_FAILURE
;
136 else if (!MetadataIsValid(*metadata
))
137 result
= MALFORMED_DATA
;
139 result
= READ_SUCCESS
;
141 result
= READ_FAILURE
;
144 result
= FILE_TOO_BIG
;
147 result
= GET_INFO_FAILURE
;
149 } else if (metadata_file
.error_details() != File::FILE_ERROR_NOT_FOUND
) {
150 result
= OPEN_FAILURE
;
154 if (result
!= READ_SUCCESS
)
156 UMA_HISTOGRAM_ENUMERATION(
157 "SBIRS.DownloadMetadata.ReadResult", result
, NUM_READ_RESULTS
);
160 // Writes |download_metadata| to |metadata_path|.
161 void WriteMetadataOnWorkerPool(const base::FilePath
& metadata_path
,
162 DownloadMetadata
* download_metadata
) {
163 MetadataWriteResult result
= NUM_WRITE_RESULTS
;
164 std::string file_data
;
165 if (download_metadata
->SerializeToString(&file_data
)) {
167 base::ImportantFileWriter::WriteFileAtomically(metadata_path
, file_data
)
171 result
= SERIALIZATION_FAILURE
;
173 UMA_HISTOGRAM_ENUMERATION(
174 "SBIRS.DownloadMetadata.WriteResult", result
, NUM_WRITE_RESULTS
);
177 // Deletes |metadata_path|.
178 void DeleteMetadataOnWorkerPool(const base::FilePath
& metadata_path
) {
179 bool success
= base::DeleteFile(metadata_path
, false /* not recursive */);
180 UMA_HISTOGRAM_BOOLEAN("SBIRS.DownloadMetadata.DeleteSuccess", success
);
183 // Runs |callback| with the DownloadDetails in |download_metadata|.
185 const DownloadMetadataManager::GetDownloadDetailsCallback
& callback
,
186 scoped_ptr
<DownloadMetadata
> download_metadata
) {
187 if (!download_metadata
->has_download_id())
188 callback
.Run(scoped_ptr
<ClientIncidentReport_DownloadDetails
>());
190 callback
.Run(make_scoped_ptr(download_metadata
->release_download()).Pass());
195 // Applies operations to the profile's persistent DownloadMetadata as they occur
196 // on its corresponding download item. An instance can be in one of three
197 // states: waiting for metatada load, waiting for metadata to load after its
198 // corresponding DownloadManager has gone down, and not waiting for metadata to
199 // load. The instance observes all download items beloing to its manager. While
200 // it is waiting for metadata to load, it records all operations on download
201 // items that must be reflected in the metadata. Once the metadata is ready,
202 // recorded operations are applied to the metadata. The instance continues to
203 // observe all download items to keep the existing metadata up to date. While
204 // waiting for metadata to load, an instance also tracks callbacks to be run to
205 // provide consumers with persisted details of a download.
206 class DownloadMetadataManager::ManagerContext
207 : public content::DownloadItem::Observer
{
209 ManagerContext(const scoped_refptr
<base::SequencedTaskRunner
>& read_runner
,
210 const scoped_refptr
<base::SequencedTaskRunner
>& write_runner
,
211 content::DownloadManager
* download_manager
);
213 // Detaches this context from its owner. The owner must not access the context
214 // following this call. The context will be deleted immediately if it is not
215 // waiting for a metadata load with either recorded operations or pending
217 void Detach(content::DownloadManager
* download_manager
);
219 // Notifies the context that |download| has been added to its manager.
220 void OnDownloadCreated(content::DownloadItem
* download
);
222 // Sets |request| as the relevant metadata to persist for |download| if or
223 // when it is complete. If |request| is null, the metadata for |download|
224 // is/will be removed.
225 void SetRequest(content::DownloadItem
* download
,
226 scoped_ptr
<ClientDownloadRequest
> request
);
228 // Gets the persisted DownloadDetails. |callback| will be run immediately if
229 // the data is available. Otherwise, it will be run later on the caller's
231 void GetDownloadDetails(const GetDownloadDetailsCallback
& callback
);
234 // content::DownloadItem::Observer methods.
235 void OnDownloadUpdated(content::DownloadItem
* download
) override
;
236 void OnDownloadOpened(content::DownloadItem
* download
) override
;
237 void OnDownloadRemoved(content::DownloadItem
* download
) override
;
241 // The context is waiting for the metadata file to be loaded.
244 // The context is waiting for the metadata file to be loaded and its
245 // corresponding DownloadManager has gone away.
248 // The context has loaded the metadata file.
253 ItemData() : removed() {}
254 base::Time last_opened_time
;
258 // A mapping of download IDs to their corresponding data.
259 typedef std::map
<uint32_t, ItemData
> ItemDataMap
;
261 ~ManagerContext() override
;
263 // Commits |request| to the DownloadDetails for |item|'s BrowserContext.
264 // Callbacks will be run immediately if the context had been waiting for a
265 // load (which will be abandoned).
266 void CommitRequest(content::DownloadItem
* item
,
267 scoped_ptr
<ClientDownloadRequest
> request
);
269 // Posts a task in the worker pool to read the metadata from disk.
272 // Posts a task in the worker pool to write the metadata to disk.
273 void WriteMetadata();
275 // Removes metadata for the context from memory and posts a task in the worker
276 // pool to delete it on disk.
277 void RemoveMetadata();
279 // Clears the |pending_items_| mapping.
280 void ClearPendingItems();
282 // Runs all |get_details_callbacks_| with the current metadata.
285 // Returns true if metadata corresponding to |item| is available.
286 bool HasMetadataFor(const content::DownloadItem
* item
) const;
288 // A callback run on the main thread with the results from reading the
289 // metadata file from disk.
290 void OnMetadataReady(scoped_ptr
<DownloadMetadata
> download_metadata
);
292 // Updates the last opened time in the metadata and writes it to disk.
293 void UpdateLastOpenedTime(const base::Time
& last_opened_time
);
295 // A task runner to which read tasks are posted.
296 scoped_refptr
<base::SequencedTaskRunner
> read_runner_
;
298 // A task runner to which write tasks are posted.
299 scoped_refptr
<base::SequencedTaskRunner
> write_runner_
;
301 // The path to the metadata file for this context.
302 base::FilePath metadata_path_
;
304 // When not LOAD_COMPLETE, the context is waiting for a pending read operation
305 // to complete. While this is the case, events are temporarily recorded in
306 // |pending_items_|. Once the read completes, pending operations for the item
307 // corresponding to the metadata file are applied to the file and all other
308 // recorded data are dropped. Queued GetDownloadDetailsCallbacks are run upon
309 // read completion as well. The context is moved to the DETACHED_WAIT state if
310 // the corresponding DownloadManager goes away while a read operation is
311 // outstanding. When the read subsequently completes, the context is destroyed
312 // after the processing described above is performed.
315 // The current metadata for the context. May be supplied either by reading
316 // from the file or by having been set via |SetRequest|.
317 scoped_ptr
<DownloadMetadata
> download_metadata_
;
319 // The operation data that accumulates for added download items while the
320 // metadata file is being read.
321 ItemDataMap pending_items_
;
323 // Pending callbacks in response to GetDownloadDetails. The callbacks are run
324 // in order when a pending read operation completes.
325 std::list
<GetDownloadDetailsCallback
> get_details_callbacks_
;
327 base::WeakPtrFactory
<ManagerContext
> weak_factory_
;
329 DISALLOW_COPY_AND_ASSIGN(ManagerContext
);
333 // DownloadMetadataManager -----------------------------------------------------
335 DownloadMetadataManager::DownloadMetadataManager(
336 const scoped_refptr
<base::SequencedWorkerPool
>& worker_pool
) {
337 base::SequencedWorkerPool::SequenceToken
token(
338 worker_pool
->GetSequenceToken());
339 // Do not block shutdown on reads since nothing will come of it.
340 read_runner_
= worker_pool
->GetSequencedTaskRunnerWithShutdownBehavior(
341 token
, base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN
);
342 // Block shutdown on writes only if they've already begun.
343 write_runner_
= worker_pool
->GetSequencedTaskRunnerWithShutdownBehavior(
344 token
, base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
347 DownloadMetadataManager::DownloadMetadataManager(
348 const scoped_refptr
<base::SequencedTaskRunner
>& task_runner
)
349 : read_runner_(task_runner
), write_runner_(task_runner
) {
352 DownloadMetadataManager::~DownloadMetadataManager() {
353 // Destruction may have taken place before managers have gone down.
354 for (const auto& manager_context_pair
: contexts_
) {
355 manager_context_pair
.first
->RemoveObserver(this);
356 manager_context_pair
.second
->Detach(manager_context_pair
.first
);
361 void DownloadMetadataManager::AddDownloadManager(
362 content::DownloadManager
* download_manager
) {
363 DCHECK_EQ(contexts_
.count(download_manager
), 0U);
364 download_manager
->AddObserver(this);
365 contexts_
[download_manager
] =
366 new ManagerContext(read_runner_
, write_runner_
, download_manager
);
369 void DownloadMetadataManager::SetRequest(content::DownloadItem
* item
,
370 const ClientDownloadRequest
* request
) {
372 content::DownloadManager
* download_manager
=
373 GetDownloadManagerForBrowserContext(item
->GetBrowserContext());
374 DCHECK_EQ(contexts_
.count(download_manager
), 1U);
375 contexts_
[download_manager
]->SetRequest(
376 item
, make_scoped_ptr(new ClientDownloadRequest(*request
)));
379 void DownloadMetadataManager::GetDownloadDetails(
380 content::BrowserContext
* browser_context
,
381 const GetDownloadDetailsCallback
& callback
) {
382 DCHECK(browser_context
);
383 // The DownloadManager for |browser_context| may not have been created yet. In
384 // this case, asking for it would cause history to load in the background and
385 // wouldn't really help much. Instead, scan the contexts to see if one belongs
386 // to |browser_context|. If one is not found, read the metadata and return it.
387 scoped_ptr
<ClientIncidentReport_DownloadDetails
> download_details
;
388 for (const auto& manager_context_pair
: contexts_
) {
389 if (manager_context_pair
.first
->GetBrowserContext() == browser_context
) {
390 manager_context_pair
.second
->GetDownloadDetails(callback
);
395 // Fire off a task to load the details and return them to the caller.
396 DownloadMetadata
* metadata
= new DownloadMetadata();
397 read_runner_
->PostTaskAndReply(
399 base::Bind(&ReadMetadataOnWorkerPool
,
400 GetMetadataPath(browser_context
),
403 &ReturnResults
, callback
, base::Passed(make_scoped_ptr(metadata
))));
406 content::DownloadManager
*
407 DownloadMetadataManager::GetDownloadManagerForBrowserContext(
408 content::BrowserContext
* context
) {
409 return content::BrowserContext::GetDownloadManager(context
);
412 void DownloadMetadataManager::OnDownloadCreated(
413 content::DownloadManager
* download_manager
,
414 content::DownloadItem
* item
) {
415 DCHECK_EQ(contexts_
.count(download_manager
), 1U);
416 contexts_
[download_manager
]->OnDownloadCreated(item
);
419 void DownloadMetadataManager::ManagerGoingDown(
420 content::DownloadManager
* download_manager
) {
421 DCHECK_EQ(contexts_
.count(download_manager
), 1U);
422 auto iter
= contexts_
.find(download_manager
);
423 iter
->first
->RemoveObserver(this);
424 iter
->second
->Detach(download_manager
);
425 contexts_
.erase(iter
);
429 // DownloadMetadataManager::ManagerContext -------------------------------------
431 DownloadMetadataManager::ManagerContext::ManagerContext(
432 const scoped_refptr
<base::SequencedTaskRunner
>& read_runner
,
433 const scoped_refptr
<base::SequencedTaskRunner
>& write_runner
,
434 content::DownloadManager
* download_manager
)
435 : read_runner_(read_runner
),
436 write_runner_(write_runner
),
437 metadata_path_(GetMetadataPath(download_manager
->GetBrowserContext())),
438 state_(WAITING_FOR_LOAD
),
439 weak_factory_(this) {
440 // Observe all pre-existing items in the manager.
441 content::DownloadManager::DownloadVector items
;
442 download_manager
->GetAllDownloads(&items
);
443 for (auto* download_item
: items
)
444 download_item
->AddObserver(this);
446 // Start the asynchronous task to read the persistent metadata.
450 void DownloadMetadataManager::ManagerContext::Detach(
451 content::DownloadManager
* download_manager
) {
452 // Stop observing all items belonging to the manager.
453 content::DownloadManager::DownloadVector items
;
454 download_manager
->GetAllDownloads(&items
);
455 for (auto* download_item
: items
)
456 download_item
->RemoveObserver(this);
458 // Delete the instance immediately if there's no work to process after a
459 // pending read completes.
460 if (get_details_callbacks_
.empty() && pending_items_
.empty()) {
463 // delete the instance in OnMetadataReady.
464 state_
= DETACHED_WAIT
;
468 void DownloadMetadataManager::ManagerContext::OnDownloadCreated(
469 content::DownloadItem
* download
) {
470 download
->AddObserver(this);
473 void DownloadMetadataManager::ManagerContext::SetRequest(
474 content::DownloadItem
* download
,
475 scoped_ptr
<ClientDownloadRequest
> request
) {
477 // Hold on to the request for completion time if the download is in progress.
478 // Otherwise, commit the request.
479 if (download
->GetState() == content::DownloadItem::IN_PROGRESS
)
480 DownloadItemData::SetRequestForDownload(download
, request
.Pass());
482 CommitRequest(download
, request
.Pass());
485 void DownloadMetadataManager::ManagerContext::GetDownloadDetails(
486 const GetDownloadDetailsCallback
& callback
) {
487 if (state_
!= LOAD_COMPLETE
) {
488 get_details_callbacks_
.push_back(callback
);
490 callback
.Run(download_metadata_
?
491 make_scoped_ptr(new ClientIncidentReport_DownloadDetails(
492 download_metadata_
->download())) :
497 void DownloadMetadataManager::ManagerContext::OnDownloadUpdated(
498 content::DownloadItem
* download
) {
499 // Persist metadata for this download if it has just completed.
500 if (download
->GetState() == content::DownloadItem::COMPLETE
) {
501 // Ignore downloads we don't have a ClientDownloadRequest for.
502 scoped_ptr
<ClientDownloadRequest
> request
=
503 DownloadItemData::TakeRequestForDownload(download
);
505 CommitRequest(download
, request
.Pass());
509 void DownloadMetadataManager::ManagerContext::OnDownloadOpened(
510 content::DownloadItem
* download
) {
511 const base::Time now
= base::Time::Now();
512 if (state_
!= LOAD_COMPLETE
)
513 pending_items_
[download
->GetId()].last_opened_time
= now
;
514 else if (HasMetadataFor(download
))
515 UpdateLastOpenedTime(now
);
518 void DownloadMetadataManager::ManagerContext::OnDownloadRemoved(
519 content::DownloadItem
* download
) {
520 if (state_
!= LOAD_COMPLETE
)
521 pending_items_
[download
->GetId()].removed
= true;
522 else if (HasMetadataFor(download
))
526 DownloadMetadataManager::ManagerContext::~ManagerContext() {
527 // A context should not be deleted while waiting for a load to complete.
528 DCHECK(pending_items_
.empty());
529 DCHECK(get_details_callbacks_
.empty());
532 void DownloadMetadataManager::ManagerContext::CommitRequest(
533 content::DownloadItem
* item
,
534 scoped_ptr
<ClientDownloadRequest
> request
) {
535 DCHECK_EQ(content::DownloadItem::COMPLETE
, item
->GetState());
536 if (state_
!= LOAD_COMPLETE
) {
537 // Abandon the read task since |item| is the new top dog.
538 weak_factory_
.InvalidateWeakPtrs();
539 state_
= LOAD_COMPLETE
;
540 // Drop any recorded operations.
544 download_metadata_
.reset(new DownloadMetadata
);
545 download_metadata_
->set_download_id(item
->GetId());
546 download_metadata_
->mutable_download()->set_allocated_download(
548 download_metadata_
->mutable_download()->set_download_time_msec(
549 item
->GetEndTime().ToJavaTime());
552 // Run callbacks (only present in case of a transition to LOAD_COMPLETE).
556 void DownloadMetadataManager::ManagerContext::ReadMetadata() {
557 DCHECK_NE(state_
, LOAD_COMPLETE
);
559 DownloadMetadata
* metadata
= new DownloadMetadata();
560 // Do not block shutdown on this read since nothing will come of it.
561 read_runner_
->PostTaskAndReply(
563 base::Bind(&ReadMetadataOnWorkerPool
, metadata_path_
, metadata
),
564 base::Bind(&DownloadMetadataManager::ManagerContext::OnMetadataReady
,
565 weak_factory_
.GetWeakPtr(),
566 base::Passed(make_scoped_ptr(metadata
))));
569 void DownloadMetadataManager::ManagerContext::WriteMetadata() {
570 write_runner_
->PostTask(
572 base::Bind(&WriteMetadataOnWorkerPool
,
574 base::Owned(new DownloadMetadata(*download_metadata_
))));
577 void DownloadMetadataManager::ManagerContext::RemoveMetadata() {
578 if (state_
!= LOAD_COMPLETE
) {
579 // Abandon the read task since the file is to be removed.
580 weak_factory_
.InvalidateWeakPtrs();
581 state_
= LOAD_COMPLETE
;
582 // Drop any recorded operations.
585 // Remove any metadata.
586 download_metadata_
.reset();
587 write_runner_
->PostTask(
588 FROM_HERE
, base::Bind(&DeleteMetadataOnWorkerPool
, metadata_path_
));
589 // Run callbacks (only present in case of a transition to LOAD_COMPLETE).
593 void DownloadMetadataManager::ManagerContext::ClearPendingItems() {
594 pending_items_
.clear();
597 void DownloadMetadataManager::ManagerContext::RunCallbacks() {
598 while (!get_details_callbacks_
.empty()) {
599 const auto& callback
= get_details_callbacks_
.front();
600 callback
.Run(download_metadata_
?
601 make_scoped_ptr(new ClientIncidentReport_DownloadDetails(
602 download_metadata_
->download())) :
604 get_details_callbacks_
.pop_front();
608 bool DownloadMetadataManager::ManagerContext::HasMetadataFor(
609 const content::DownloadItem
* item
) const {
610 // There must not be metadata if the load is not complete.
611 DCHECK(state_
== LOAD_COMPLETE
|| !download_metadata_
);
612 return (download_metadata_
&&
613 download_metadata_
->download_id() == item
->GetId());
616 void DownloadMetadataManager::ManagerContext::OnMetadataReady(
617 scoped_ptr
<DownloadMetadata
> download_metadata
) {
618 DCHECK_NE(state_
, LOAD_COMPLETE
);
620 const bool is_detached
= (state_
== DETACHED_WAIT
);
622 // Note that any available data has been read.
623 state_
= LOAD_COMPLETE
;
624 if (download_metadata
->has_download_id())
625 download_metadata_
= download_metadata
.Pass();
627 download_metadata_
.reset();
629 // Process all operations that had been held while waiting for the metadata.
630 if (download_metadata_
) {
631 const auto& iter
= pending_items_
.find(download_metadata_
->download_id());
632 if (iter
!= pending_items_
.end()) {
633 const ItemData
& item_data
= iter
->second
;
634 if (item_data
.removed
)
636 else if (!item_data
.last_opened_time
.is_null())
637 UpdateLastOpenedTime(item_data
.last_opened_time
);
641 // Drop the recorded operations.
647 // Delete the context now if it has been detached.
652 void DownloadMetadataManager::ManagerContext::UpdateLastOpenedTime(
653 const base::Time
& last_opened_time
) {
654 download_metadata_
->mutable_download()->set_open_time_msec(
655 last_opened_time
.ToJavaTime());
659 } // namespace safe_browsing