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/metrics/histogram.h"
16 #include "base/sequenced_task_runner.h"
17 #include "base/threading/sequenced_worker_pool.h"
18 #include "chrome/common/safe_browsing/csd.pb.h"
19 #include "content/public/browser/browser_context.h"
20 #include "content/public/browser/download_item.h"
22 namespace safe_browsing
{
26 // Histogram bucket values for metadata read operations. Do not reorder.
27 enum MetadataReadResult
{
39 // Histogram bucket values for metadata write operations. Do not reorder.
40 enum MetadataWriteResult
{
42 SERIALIZATION_FAILURE
= 1,
47 // The name of the metadata file in the profile directory.
48 const base::FilePath::CharType kDownloadMetadataBasename
[] =
49 FILE_PATH_LITERAL("DownloadMetadata");
51 // Returns the path to the metadata file for |browser_context|.
52 base::FilePath
GetMetadataPath(content::BrowserContext
* browser_context
) {
53 return browser_context
->GetPath().Append(kDownloadMetadataBasename
);
56 // Returns true if |metadata| appears to be valid.
57 bool MetadataIsValid(const DownloadMetadata
& metadata
) {
58 return (metadata
.has_download_id() &&
59 metadata
.has_download() &&
60 metadata
.download().has_download() &&
61 metadata
.download().download().has_url());
64 // Reads and parses a DownloadMetadata message from |metadata_path| into
66 void ReadMetadataOnWorkerPool(const base::FilePath
& metadata_path
,
67 DownloadMetadata
* metadata
) {
70 MetadataReadResult result
= NUM_READ_RESULTS
;
71 File
metadata_file(metadata_path
, File::FLAG_OPEN
| File::FLAG_READ
);
72 if (metadata_file
.IsValid()) {
73 base::File::Info info
;
74 if (metadata_file
.GetInfo(&info
)) {
75 if (info
.size
<= INT_MAX
) {
76 const int size
= static_cast<int>(info
.size
);
77 scoped_ptr
<char[]> file_data(new char[info
.size
]);
78 if (metadata_file
.Read(0, file_data
.get(), size
)) {
79 if (!metadata
->ParseFromArray(file_data
.get(), size
))
80 result
= PARSE_FAILURE
;
81 else if (!MetadataIsValid(*metadata
))
82 result
= MALFORMED_DATA
;
84 result
= READ_SUCCESS
;
86 result
= READ_FAILURE
;
89 result
= FILE_TOO_BIG
;
92 result
= GET_INFO_FAILURE
;
94 } else if (metadata_file
.error_details() != File::FILE_ERROR_NOT_FOUND
) {
95 result
= OPEN_FAILURE
;
99 if (result
!= READ_SUCCESS
)
101 UMA_HISTOGRAM_ENUMERATION(
102 "SBIRS.DownloadMetadata.ReadResult", result
, NUM_READ_RESULTS
);
105 // Writes |download_metadata| to |metadata_path|.
106 void WriteMetadataOnWorkerPool(const base::FilePath
& metadata_path
,
107 DownloadMetadata
* download_metadata
) {
108 MetadataWriteResult result
= NUM_WRITE_RESULTS
;
109 std::string file_data
;
110 if (download_metadata
->SerializeToString(&file_data
)) {
112 base::ImportantFileWriter::WriteFileAtomically(metadata_path
, file_data
)
116 result
= SERIALIZATION_FAILURE
;
118 UMA_HISTOGRAM_ENUMERATION(
119 "SBIRS.DownloadMetadata.WriteResult", result
, NUM_WRITE_RESULTS
);
122 // Deletes |metadata_path|.
123 void DeleteMetadataOnWorkerPool(const base::FilePath
& metadata_path
) {
124 bool success
= base::DeleteFile(metadata_path
, false /* not recursive */);
125 UMA_HISTOGRAM_BOOLEAN("SBIRS.DownloadMetadata.DeleteSuccess", success
);
128 // Runs |callback| with the DownloadDetails in |download_metadata|.
130 const DownloadMetadataManager::GetDownloadDetailsCallback
& callback
,
131 scoped_ptr
<DownloadMetadata
> download_metadata
) {
132 if (!download_metadata
->has_download_id())
133 callback
.Run(scoped_ptr
<ClientIncidentReport_DownloadDetails
>());
135 callback
.Run(make_scoped_ptr(download_metadata
->release_download()).Pass());
140 // Applies operations to the profile's persistent DownloadMetadata as they occur
141 // on its corresponding download item. An instance can be in one of three
142 // states: waiting for metatada load, waiting for metadata to load after its
143 // corresponding DownloadManager has gone down, and not waiting for metadata to
144 // load. While it is waiting for metadata to load, it observes all download
145 // items and records operations on them. Once the metadata is ready, recorded
146 // operations are applied and observers are removed from all items but the one
147 // corresponding to the metadata (if it exists). The instance continues to
148 // observe its related item, and applies operations on it accordingly. While
149 // waiting for metadata to load, an instance also tracks callbacks to be run to
150 // provide consumers with persisted details of a download.
151 class DownloadMetadataManager::ManagerContext
152 : public content::DownloadItem::Observer
{
154 ManagerContext(const scoped_refptr
<base::SequencedTaskRunner
>& read_runner
,
155 const scoped_refptr
<base::SequencedTaskRunner
>& write_runner
,
156 content::DownloadManager
* download_manager
);
158 // Detaches this context from its owner. The owner must not access the context
159 // following this call. The context will be deleted immediately if it is not
160 // waiting for a metadata load with either recorded operations or pending
164 // Notifies the context that |download| has been added to its manager.
165 void OnDownloadCreated(content::DownloadItem
* download
);
167 // Sets |request| as the relevant metadata to persist for |download|.
168 void SetRequest(content::DownloadItem
* download
,
169 const ClientDownloadRequest
& request
);
171 // Removes metadata for the context from memory and posts a task in the worker
172 // pool to delete it on disk.
173 void RemoveMetadata();
175 // Gets the persisted DownloadDetails. |callback| will be run immediately if
176 // the data is available. Otherwise, it will be run later on the caller's
178 void GetDownloadDetails(const GetDownloadDetailsCallback
& callback
);
181 // content::DownloadItem::Observer methods.
182 void OnDownloadOpened(content::DownloadItem
* download
) override
;
183 void OnDownloadRemoved(content::DownloadItem
* download
) override
;
184 void OnDownloadDestroyed(content::DownloadItem
* download
) override
;
188 // The context is waiting for the metadata file to be loaded.
191 // The context is waiting for the metadata file to be loaded and its
192 // corresponding DownloadManager has gone away.
195 // The context has loaded the metadata file.
200 ItemData() : item(), removed() {}
201 // null if the download has been destroyed. If non-null, the item is being
203 content::DownloadItem
* item
;
204 base::Time last_opened_time
;
208 // A mapping of download IDs to their corresponding data.
209 typedef std::map
<uint32_t, ItemData
> ItemDataMap
;
211 ~ManagerContext() override
;
213 // Clears the |pending_items_| mapping, removing observers in the process.
214 void ClearPendingItems();
216 // Runs all |get_details_callbacks_| with the current metadata.
219 // Returns the id of the download corresponding to the loaded metadata, or
220 // kInvalidId if metadata has not finished loading or is not present.
221 uint32_t GetDownloadId() const;
223 // Posts a task in the worker pool to read the metadata from disk.
226 // A callback run on the main thread with the results from reading the
227 // metadata file from disk.
228 void OnMetadataReady(scoped_ptr
<DownloadMetadata
> download_metadata
);
230 // Updates the last opened time in the metadata and writes it to disk.
231 void UpdateLastOpenedTime(const base::Time
& last_opened_time
);
233 // Posts a task in the worker pool to write the metadata to disk.
234 void WriteMetadata();
236 // A task runner to which read tasks are posted.
237 scoped_refptr
<base::SequencedTaskRunner
> read_runner_
;
239 // A task runner to which write tasks are posted.
240 scoped_refptr
<base::SequencedTaskRunner
> write_runner_
;
242 // The path to the metadata file for this context.
243 base::FilePath metadata_path_
;
245 // When not LOAD_COMPLETE, the context is waiting for a pending read operation
246 // to complete. While this is the case, all added download items are observed
247 // and events are temporarily recorded in |pending_items_|. Once the read
248 // completes, pending operations for the item correponding to the metadata
249 // file are applied to the file and all other recorded data are dropped (and
250 // corresponding observers are removed). Queued GetDownloadDetailsCallbacks
251 // are run upon read completion as well. The context is moved to the
252 // DETACHED_WAIT state if the corresponding DownloadManager goes away while a
253 // read operation is outstanding. When the read subsequently completes, the
254 // context is destroyed after the processing described above is performed.
257 // The current metadata for the context. May be supplied either by reading
258 // from the file or by having been set via |SetRequest|.
259 scoped_ptr
<DownloadMetadata
> download_metadata_
;
261 // The operation data that accumulates for added download items while the
262 // metadata file is being read.
263 ItemDataMap pending_items_
;
265 // The download item corresponding to the download_metadata_. When non-null,
266 // the context observes events on this item only.
267 content::DownloadItem
* item_
;
269 // Pending callbacks in response to GetDownloadDetails. The callbacks are run
270 // in order when a pending read operation completes.
271 std::list
<GetDownloadDetailsCallback
> get_details_callbacks_
;
273 base::WeakPtrFactory
<ManagerContext
> weak_factory_
;
275 DISALLOW_COPY_AND_ASSIGN(ManagerContext
);
278 DownloadMetadataManager::DownloadMetadataManager(
279 const scoped_refptr
<base::SequencedWorkerPool
>& worker_pool
) {
280 base::SequencedWorkerPool::SequenceToken
token(
281 worker_pool
->GetSequenceToken());
282 // Do not block shutdown on reads since nothing will come of it.
283 read_runner_
= worker_pool
->GetSequencedTaskRunnerWithShutdownBehavior(
284 token
, base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN
);
285 // Block shutdown on writes only if they've already begun.
286 write_runner_
= worker_pool
->GetSequencedTaskRunnerWithShutdownBehavior(
287 token
, base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
290 DownloadMetadataManager::DownloadMetadataManager(
291 const scoped_refptr
<base::SequencedTaskRunner
>& task_runner
)
292 : read_runner_(task_runner
), write_runner_(task_runner
) {
295 DownloadMetadataManager::~DownloadMetadataManager() {
296 // Destruction may have taken place before managers have gone down.
297 for (const auto& value
: contexts_
) {
298 value
.first
->RemoveObserver(this);
299 value
.second
->Detach();
304 void DownloadMetadataManager::AddDownloadManager(
305 content::DownloadManager
* download_manager
) {
306 DCHECK_EQ(contexts_
.count(download_manager
), 0U);
307 download_manager
->AddObserver(this);
308 contexts_
[download_manager
] =
309 new ManagerContext(read_runner_
, write_runner_
, download_manager
);
312 void DownloadMetadataManager::SetRequest(content::DownloadItem
* item
,
313 const ClientDownloadRequest
* request
) {
314 content::DownloadManager
* download_manager
=
315 GetDownloadManagerForBrowserContext(item
->GetBrowserContext());
316 DCHECK_EQ(contexts_
.count(download_manager
), 1U);
318 contexts_
[download_manager
]->SetRequest(item
, *request
);
320 contexts_
[download_manager
]->RemoveMetadata();
323 void DownloadMetadataManager::GetDownloadDetails(
324 content::BrowserContext
* browser_context
,
325 const GetDownloadDetailsCallback
& callback
) {
326 DCHECK(browser_context
);
327 // The DownloadManager for |browser_context| may not have been created yet. In
328 // this case, asking for it would cause history to load in the background and
329 // wouldn't really help much. Instead, scan the contexts to see if one belongs
330 // to |browser_context|. If one is not found, read the metadata and return it.
331 scoped_ptr
<ClientIncidentReport_DownloadDetails
> download_details
;
332 for (const auto& value
: contexts_
) {
333 if (value
.first
->GetBrowserContext() == browser_context
) {
334 value
.second
->GetDownloadDetails(callback
);
339 // Fire off a task to load the details and return them to the caller.
340 DownloadMetadata
* metadata
= new DownloadMetadata();
341 read_runner_
->PostTaskAndReply(
343 base::Bind(&ReadMetadataOnWorkerPool
,
344 GetMetadataPath(browser_context
),
347 &ReturnResults
, callback
, base::Passed(make_scoped_ptr(metadata
))));
350 content::DownloadManager
*
351 DownloadMetadataManager::GetDownloadManagerForBrowserContext(
352 content::BrowserContext
* context
) {
353 return content::BrowserContext::GetDownloadManager(context
);
356 void DownloadMetadataManager::OnDownloadCreated(
357 content::DownloadManager
* download_manager
,
358 content::DownloadItem
* item
) {
359 DCHECK_EQ(contexts_
.count(download_manager
), 1U);
360 contexts_
[download_manager
]->OnDownloadCreated(item
);
363 void DownloadMetadataManager::ManagerGoingDown(
364 content::DownloadManager
* download_manager
) {
365 DCHECK_EQ(contexts_
.count(download_manager
), 1U);
366 auto iter
= contexts_
.find(download_manager
);
367 iter
->second
->Detach();
368 contexts_
.erase(iter
);
371 DownloadMetadataManager::ManagerContext::ManagerContext(
372 const scoped_refptr
<base::SequencedTaskRunner
>& read_runner
,
373 const scoped_refptr
<base::SequencedTaskRunner
>& write_runner
,
374 content::DownloadManager
* download_manager
)
375 : read_runner_(read_runner
),
376 write_runner_(write_runner
),
377 metadata_path_(GetMetadataPath(download_manager
->GetBrowserContext())),
378 state_(WAITING_FOR_LOAD
),
380 weak_factory_(this) {
381 // Start the asynchronous task to read the persistent metadata.
385 void DownloadMetadataManager::ManagerContext::Detach() {
386 // Delete the instance immediately if there's no work to process after a
387 // pending read completes.
388 if (get_details_callbacks_
.empty() && pending_items_
.empty()) {
391 // delete the instance in OnMetadataReady.
392 state_
= DETACHED_WAIT
;
396 void DownloadMetadataManager::ManagerContext::OnDownloadCreated(
397 content::DownloadItem
* download
) {
398 const uint32_t id
= download
->GetId();
399 if (state_
!= LOAD_COMPLETE
) {
400 // Observe all downloads and record operations while waiting for metadata to
402 download
->AddObserver(this);
403 pending_items_
[id
].item
= download
;
404 } else if (id
== GetDownloadId()) {
405 // Observe this one item if it is the important one.
406 DCHECK_EQ(item_
, static_cast<content::DownloadItem
*>(nullptr));
408 download
->AddObserver(this);
412 void DownloadMetadataManager::ManagerContext::SetRequest(
413 content::DownloadItem
* download
,
414 const ClientDownloadRequest
& request
) {
415 if (state_
!= LOAD_COMPLETE
) {
416 // Abandon the read task since |download| is the new top dog.
417 weak_factory_
.InvalidateWeakPtrs();
418 state_
= LOAD_COMPLETE
;
419 // Stop observing all items and drop any recorded operations.
422 // Observe this item from here on out.
423 if (item_
!= download
) {
425 item_
->RemoveObserver(this);
426 download
->AddObserver(this);
430 download_metadata_
.reset(new DownloadMetadata
);
431 download_metadata_
->set_download_id(download
->GetId());
432 download_metadata_
->mutable_download()->mutable_download()->CopyFrom(request
);
433 base::Time end_time
= download
->GetEndTime();
434 // The item may have yet to be marked as complete, so consider the current
435 // time as being its download time.
436 if (end_time
.is_null())
437 end_time
= base::Time::Now();
438 download_metadata_
->mutable_download()->set_download_time_msec(
439 end_time
.ToJavaTime());
442 // Run callbacks (only present in case of a transition to LOAD_COMPLETE).
446 void DownloadMetadataManager::ManagerContext::RemoveMetadata() {
447 if (state_
!= LOAD_COMPLETE
) {
448 // Abandon the read task since the file is to be removed.
449 weak_factory_
.InvalidateWeakPtrs();
450 state_
= LOAD_COMPLETE
;
451 // Stop observing all items and drop any recorded operations.
454 // Remove any metadata.
455 download_metadata_
.reset();
456 // Stop observing this item.
458 item_
->RemoveObserver(this);
461 write_runner_
->PostTask(
462 FROM_HERE
, base::Bind(&DeleteMetadataOnWorkerPool
, metadata_path_
));
463 // Run callbacks (only present in case of a transition to LOAD_COMPLETE).
467 void DownloadMetadataManager::ManagerContext::GetDownloadDetails(
468 const GetDownloadDetailsCallback
& callback
) {
469 if (state_
!= LOAD_COMPLETE
) {
470 get_details_callbacks_
.push_back(callback
);
472 if (download_metadata_
) {
473 callback
.Run(make_scoped_ptr(new ClientIncidentReport_DownloadDetails(
474 download_metadata_
->download())).Pass());
476 callback
.Run(scoped_ptr
<ClientIncidentReport_DownloadDetails
>());
481 void DownloadMetadataManager::ManagerContext::OnDownloadOpened(
482 content::DownloadItem
* download
) {
483 const base::Time now
= base::Time::Now();
484 if (state_
!= LOAD_COMPLETE
)
485 pending_items_
[download
->GetId()].last_opened_time
= now
;
487 UpdateLastOpenedTime(now
);
490 void DownloadMetadataManager::ManagerContext::OnDownloadRemoved(
491 content::DownloadItem
* download
) {
492 if (state_
!= LOAD_COMPLETE
)
493 pending_items_
[download
->GetId()].removed
= true;
498 void DownloadMetadataManager::ManagerContext::OnDownloadDestroyed(
499 content::DownloadItem
* download
) {
500 if (state_
!= LOAD_COMPLETE
) {
501 // Erase the data for this item if nothing of import happened. Otherwise
502 // clear its item pointer since it is now invalid.
503 auto iter
= pending_items_
.find(download
->GetId());
504 DCHECK(iter
!= pending_items_
.end());
505 if (!iter
->second
.removed
&& iter
->second
.last_opened_time
.is_null())
506 pending_items_
.erase(iter
);
508 iter
->second
.item
= nullptr;
510 // This item is no longer being observed.
511 DCHECK_EQ(item_
, download
);
516 DownloadMetadataManager::ManagerContext::~ManagerContext() {
517 // A context should not be deleted while waiting for a load to complete.
518 DCHECK(pending_items_
.empty());
519 DCHECK(get_details_callbacks_
.empty());
521 // The context may have detached while still observing the item of interest
522 // since a DownloadManager announces that it's going down before it destroyes
525 item_
->RemoveObserver(this);
530 void DownloadMetadataManager::ManagerContext::ClearPendingItems() {
531 for (const auto& value
: pending_items_
) {
532 if (value
.second
.item
)
533 value
.second
.item
->RemoveObserver(this);
535 pending_items_
.clear();
538 void DownloadMetadataManager::ManagerContext::RunCallbacks() {
539 while (!get_details_callbacks_
.empty()) {
540 const auto& callback
= get_details_callbacks_
.front();
541 if (download_metadata_
) {
542 callback
.Run(make_scoped_ptr(new ClientIncidentReport_DownloadDetails(
543 download_metadata_
->download())).Pass());
545 callback
.Run(scoped_ptr
<ClientIncidentReport_DownloadDetails
>());
547 get_details_callbacks_
.pop_front();
551 uint32_t DownloadMetadataManager::ManagerContext::GetDownloadId() const {
552 if (state_
!= LOAD_COMPLETE
|| !download_metadata_
)
553 return content::DownloadItem::kInvalidId
;
554 return download_metadata_
->download_id();
557 void DownloadMetadataManager::ManagerContext::ReadMetadata() {
558 DCHECK_NE(state_
, LOAD_COMPLETE
);
560 DownloadMetadata
* metadata
= new DownloadMetadata();
561 // Do not block shutdown on this read since nothing will come of it.
562 read_runner_
->PostTaskAndReply(
564 base::Bind(&ReadMetadataOnWorkerPool
, metadata_path_
, metadata
),
565 base::Bind(&DownloadMetadataManager::ManagerContext::OnMetadataReady
,
566 weak_factory_
.GetWeakPtr(),
567 base::Passed(make_scoped_ptr(metadata
))));
570 void DownloadMetadataManager::ManagerContext::OnMetadataReady(
571 scoped_ptr
<DownloadMetadata
> download_metadata
) {
572 DCHECK_NE(state_
, LOAD_COMPLETE
);
574 const bool is_detached
= (state_
== DETACHED_WAIT
);
576 // Note that any available data has been read.
577 state_
= LOAD_COMPLETE
;
578 if (download_metadata
->has_download_id())
579 download_metadata_
= download_metadata
.Pass();
581 download_metadata_
.reset();
583 // Process all operations that had been held while waiting for the metadata.
584 content::DownloadItem
* download
= nullptr;
586 const auto& iter
= pending_items_
.find(GetDownloadId());
587 if (iter
!= pending_items_
.end()) {
588 const ItemData
& item_data
= iter
->second
;
589 download
= item_data
.item
; // non-null if not destroyed.
590 if (item_data
.removed
) {
593 } else if (!item_data
.last_opened_time
.is_null()) {
594 UpdateLastOpenedTime(item_data
.last_opened_time
);
599 // Stop observing all items.
602 // If the download was known and not removed, observe it from here on out.
604 download
->AddObserver(this);
611 // Delete the context now if it has been detached.
616 void DownloadMetadataManager::ManagerContext::UpdateLastOpenedTime(
617 const base::Time
& last_opened_time
) {
618 download_metadata_
->mutable_download()->set_open_time_msec(
619 last_opened_time
.ToJavaTime());
623 void DownloadMetadataManager::ManagerContext::WriteMetadata() {
624 write_runner_
->PostTask(
626 base::Bind(&WriteMetadataOnWorkerPool
,
628 base::Owned(new DownloadMetadata(*download_metadata_
))));
631 } // namespace safe_browsing