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_path.h"
12 #include "base/files/file_util.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/message_loop/message_loop.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "chrome/common/safe_browsing/csd.pb.h"
17 #include "chrome/test/base/testing_profile.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/browser/download_manager.h"
20 #include "content/public/test/mock_download_item.h"
21 #include "content/public/test/mock_download_manager.h"
22 #include "content/public/test/test_browser_thread_bundle.h"
23 #include "testing/gmock/include/gmock/gmock.h"
24 #include "testing/gtest/include/gtest/gtest.h"
26 using ::testing::AllOf
;
28 using ::testing::IsNull
;
30 using ::testing::NiceMock
;
31 using ::testing::NotNull
;
32 using ::testing::ResultOf
;
33 using ::testing::Return
;
34 using ::testing::SaveArg
;
35 using ::testing::StrEq
;
37 namespace safe_browsing
{
41 const uint32_t kTestDownloadId
= 47;
42 const uint32_t kOtherDownloadId
= 48;
43 const uint32_t kCrazyDowloadId
= 655;
44 const int64 kTestDownloadTimeMsec
= 84;
45 const char kTestUrl
[] = "http://test.test/foo";
46 const uint64_t kTestDownloadLength
= 1000;
47 const double kTestDownloadEndTimeMs
= 1413514824057;
49 // A utility class suitable for mocking that exposes a
50 // GetDownloadDetailsCallback.
51 class DownloadDetailsGetter
{
53 virtual ~DownloadDetailsGetter() {}
54 virtual void OnDownloadDetails(
55 ClientIncidentReport_DownloadDetails
* details
) = 0;
56 DownloadMetadataManager::GetDownloadDetailsCallback
GetCallback() {
57 return base::Bind(&DownloadDetailsGetter::DownloadDetailsCallback
,
58 base::Unretained(this));
62 void DownloadDetailsCallback(
63 scoped_ptr
<ClientIncidentReport_DownloadDetails
> details
) {
64 OnDownloadDetails(details
.get());
68 // A mock DownloadDetailsGetter.
69 class MockDownloadDetailsGetter
: public DownloadDetailsGetter
{
71 MOCK_METHOD1(OnDownloadDetails
, void(ClientIncidentReport_DownloadDetails
*));
74 // A mock DownloadMetadataManager that can be used to map a BrowserContext to
76 class MockDownloadMetadataManager
: public DownloadMetadataManager
{
78 MockDownloadMetadataManager(
79 const scoped_refptr
<base::SequencedTaskRunner
>& task_runner
)
80 : DownloadMetadataManager(task_runner
) {}
82 MOCK_METHOD1(GetDownloadManagerForBrowserContext
,
83 content::DownloadManager
*(content::BrowserContext
*));
86 // A helper function that returns the download URL from a DownloadDetails.
87 const std::string
& GetDetailsDownloadUrl(
88 const ClientIncidentReport_DownloadDetails
* details
) {
89 return details
->download().url();
92 // A helper function that returns the open time from a DownloadDetails.
93 int64_t GetDetailsOpenTime(
94 const ClientIncidentReport_DownloadDetails
* details
) {
95 return details
->open_time_msec();
100 // The basis upon which unit tests of the DownloadMetadataManager are built.
101 class DownloadMetadataManagerTestBase
: public ::testing::Test
{
103 // Sets up a DownloadMetadataManager that will run tasks on the main test
105 DownloadMetadataManagerTestBase()
106 : manager_(scoped_refptr
<base::SequencedTaskRunner
>(
107 base::ThreadTaskRunnerHandle::Get())),
111 // Returns the path to the test profile's DownloadMetadata file.
112 base::FilePath
GetMetadataPath() const {
113 return profile_
.GetPath().Append(FILE_PATH_LITERAL("DownloadMetadata"));
116 // Returns a new ClientDownloadRequest for the given download URL.
117 static scoped_ptr
<ClientDownloadRequest
> MakeTestRequest(const char* url
) {
118 scoped_ptr
<ClientDownloadRequest
> request(new ClientDownloadRequest());
119 request
->set_url(url
);
120 request
->mutable_digests();
121 request
->set_length(kTestDownloadLength
);
122 return request
.Pass();
125 // Returns a new DownloadMetdata for the given download id.
126 static scoped_ptr
<DownloadMetadata
> GetTestMetadata(uint32_t download_id
) {
127 scoped_ptr
<DownloadMetadata
> metadata(new DownloadMetadata());
128 metadata
->set_download_id(download_id
);
129 ClientIncidentReport_DownloadDetails
* details
=
130 metadata
->mutable_download();
131 details
->set_download_time_msec(kTestDownloadTimeMsec
);
132 details
->set_allocated_download(MakeTestRequest(kTestUrl
).release());
133 return metadata
.Pass();
136 // Writes a test DownloadMetadata file for the given download id to the
137 // test profile directory.
138 void WriteTestMetadataFileForItem(uint32_t download_id
) {
140 ASSERT_TRUE(GetTestMetadata(download_id
)->SerializeToString(&data
));
141 ASSERT_TRUE(base::WriteFile(GetMetadataPath(), data
.data(), data
.size()));
144 // Writes a test DownloadMetadata file for kTestDownloadId to the test profile
146 void WriteTestMetadataFile() {
147 WriteTestMetadataFileForItem(kTestDownloadId
);
150 // Returns the DownloadMetadata read from the test profile's directory.
151 scoped_ptr
<DownloadMetadata
> ReadTestMetadataFile() const {
153 if (!base::ReadFileToString(GetMetadataPath(), &data
))
154 return scoped_ptr
<DownloadMetadata
>();
155 scoped_ptr
<DownloadMetadata
> result(new DownloadMetadata
);
156 EXPECT_TRUE(result
->ParseFromString(data
));
157 return result
.Pass();
160 // Runs all tasks posted to the test thread's message loop.
161 void RunAllTasks() { base::MessageLoop::current()->RunUntilIdle(); }
163 // Adds a DownloadManager for the test profile. The DownloadMetadataManager's
164 // observer is stashed for later use. Only call once per call to
165 // ShutdownDownloadManager.
166 void AddDownloadManager() {
167 ASSERT_EQ(nullptr, dm_observer_
);
168 // Shove the manager into the browser context.
169 ON_CALL(download_manager_
, GetBrowserContext())
170 .WillByDefault(Return(&profile_
));
171 ON_CALL(manager_
, GetDownloadManagerForBrowserContext(Eq(&profile_
)))
172 .WillByDefault(Return(&download_manager_
));
173 // Capture the metadata manager's observer on the download manager.
174 EXPECT_CALL(download_manager_
, AddObserver(&manager_
))
175 .WillOnce(SaveArg
<0>(&dm_observer_
));
176 manager_
.AddDownloadManager(&download_manager_
);
179 // Shuts down the DownloadManager. Safe to call any number of times.
180 void ShutdownDownloadManager() {
182 dm_observer_
->ManagerGoingDown(&download_manager_
);
183 // Note: these calls may result in "Uninteresting mock function call"
184 // warnings as a result of MockDownloadItem invoking observers in its
185 // dtor. This happens after the NiceMock wrapper has removed its niceness
186 // hook. These can safely be ignored, as they are entirely expected. The
187 // values specified by ON_CALL invocations in AddDownloadItems are
188 // returned as desired.
191 dm_observer_
= nullptr;
195 // Adds two test DownloadItems to the DownloadManager.
196 void AddDownloadItems() {
197 ASSERT_NE(nullptr, dm_observer_
);
198 // Add the item under test.
199 test_item_
.reset(new NiceMock
<content::MockDownloadItem
>);
200 ON_CALL(*test_item_
, GetId())
201 .WillByDefault(Return(kTestDownloadId
));
202 ON_CALL(*test_item_
, GetBrowserContext())
203 .WillByDefault(Return(&profile_
));
204 ON_CALL(*test_item_
, GetEndTime())
205 .WillByDefault(Return(base::Time::FromJsTime(kTestDownloadEndTimeMs
)));
206 dm_observer_
->OnDownloadCreated(&download_manager_
, test_item_
.get());
209 other_item_
.reset(new NiceMock
<content::MockDownloadItem
>);
210 ON_CALL(*other_item_
, GetId())
211 .WillByDefault(Return(kOtherDownloadId
));
212 ON_CALL(*other_item_
, GetBrowserContext())
213 .WillByDefault(Return(&profile_
));
214 ON_CALL(*test_item_
, GetEndTime())
215 .WillByDefault(Return(base::Time::FromJsTime(kTestDownloadEndTimeMs
)));
216 dm_observer_
->OnDownloadCreated(&download_manager_
, other_item_
.get());
219 content::TestBrowserThreadBundle thread_bundle_
;
220 NiceMock
<MockDownloadMetadataManager
> manager_
;
221 TestingProfile profile_
;
222 NiceMock
<content::MockDownloadManager
> download_manager_
;
223 scoped_ptr
<content::MockDownloadItem
> test_item_
;
224 scoped_ptr
<content::MockDownloadItem
> other_item_
;
225 content::DownloadManager::Observer
* dm_observer_
;
228 // A parameterized test that exercises GetDownloadDetails. The parameters
229 // dictate the exact state of affairs leading up to the call as follows:
230 // 0: if "present", the profile has a pre-existing DownloadMetadata file.
231 // 1: if "managed", the profile's DownloadManager has been created.
232 // 2: the state of the DownloadItem prior to the call:
233 // "not_created": the DownloadItem has not been created.
234 // "created": the DownloadItem has been created.
235 // "opened": the DownloadItem has been opened.
236 // "removed": the DownloadItem has been removed.
237 // 3: if "loaded", the task to load the DownloadMetadata file is allowed to
239 // 4: if "early_shutdown", the DownloadManager is shut down before the callback
240 // is allowed to complete.
242 : public DownloadMetadataManagerTestBase
,
243 public ::testing::WithParamInterface
<testing::tuple
<const char*,
249 enum DownloadItemAction
{
256 : metadata_file_present_(),
258 item_action_(NOT_CREATED
),
262 void SetUp() override
{
263 DownloadMetadataManagerTestBase::SetUp();
264 metadata_file_present_
=
265 (std::string(testing::get
<0>(GetParam())) == "present");
266 manager_added_
= (std::string(testing::get
<1>(GetParam())) == "managed");
267 const std::string
item_action(testing::get
<2>(GetParam()));
268 item_action_
= (item_action
== "not_created" ? NOT_CREATED
:
269 (item_action
== "created" ? CREATED
:
270 (item_action
== "opened" ? OPENED
: REMOVED
)));
271 details_loaded_
= (std::string(testing::get
<3>(GetParam())) == "loaded");
273 (std::string(testing::get
<4>(GetParam())) == "early_shutdown");
275 // Fixup combinations that don't make sense.
277 item_action_
= NOT_CREATED
;
280 bool metadata_file_present_
;
282 DownloadItemAction item_action_
;
283 bool details_loaded_
;
284 bool early_shutdown_
;
287 // Tests that DownloadMetadataManager::GetDownloadDetails works for all
288 // combinations of states.
289 TEST_P(GetDetailsTest
, GetDownloadDetails
) {
290 // Optionally put a metadata file in the profile directory.
291 if (metadata_file_present_
)
292 WriteTestMetadataFile();
294 // Optionally add a download manager for the profile.
296 AddDownloadManager();
298 // Optionally create download items and perform actions on the one under test.
299 if (item_action_
!= NOT_CREATED
)
301 if (item_action_
== OPENED
)
302 test_item_
->NotifyObserversDownloadOpened();
303 else if (item_action_
== REMOVED
)
304 test_item_
->NotifyObserversDownloadRemoved();
306 // Optionally allow the task to read the file to complete.
310 // In http://crbug.com/433928, open after removal during load caused a crash.
311 if (item_action_
== REMOVED
)
312 test_item_
->NotifyObserversDownloadOpened();
314 MockDownloadDetailsGetter details_getter
;
315 if (metadata_file_present_
&& item_action_
!= REMOVED
) {
316 // The file is present, so expect that the callback is invoked with the
317 // details of the test download data written by WriteTestMetadataFile.
318 if (item_action_
== OPENED
) {
319 EXPECT_CALL(details_getter
,
321 AllOf(ResultOf(GetDetailsDownloadUrl
, StrEq(kTestUrl
)),
322 ResultOf(GetDetailsOpenTime
, Ne(0)))));
324 EXPECT_CALL(details_getter
,
326 AllOf(ResultOf(GetDetailsDownloadUrl
, StrEq(kTestUrl
)),
327 ResultOf(GetDetailsOpenTime
, Eq(0)))));
330 // No file on disk, so expect that the callback is invoked with null.
331 EXPECT_CALL(details_getter
, OnDownloadDetails(IsNull()));
335 manager_
.GetDownloadDetails(&profile_
, details_getter
.GetCallback());
337 // Shutdown the download manager, if relevant.
339 ShutdownDownloadManager();
341 // Allow the read task and the response callback to run.
344 // Shutdown the download manager, if relevant.
345 ShutdownDownloadManager();
348 INSTANTIATE_TEST_CASE_P(
349 DownloadMetadataManager
,
352 testing::Values("absent", "present"),
353 testing::Values("not_managed", "managed"),
354 testing::Values("not_created", "created", "opened", "removed"),
355 testing::Values("waiting", "loaded"),
356 testing::Values("normal_shutdown", "early_shutdown")));
358 // A parameterized test that exercises SetRequest. The parameters dictate the
359 // exact state of affairs leading up to the call as follows:
360 // 0: the state of the DownloadMetadata file for the test profile:
361 // "absent": no file is present.
362 // "this": the file corresponds to the item being updated.
363 // "other": the file correponds to a different item.
364 // "unknown": the file corresponds to an item that has not been created.
365 // 1: if "pending", an operation is applied to the item being updated prior to
367 // 2: if "pending", an operation is applied to a different item prior to the
369 // 3: if "loaded", the task to load the DownloadMetadata file is allowed to
371 // 4: if "set", the call to SetRequest contains a new request; otherwise it
372 // does not, leading to removal of metadata.
374 : public DownloadMetadataManagerTestBase
,
375 public ::testing::WithParamInterface
<testing::tuple
<const char*,
381 enum MetadataFilePresent
{
383 PRESENT_FOR_THIS_ITEM
,
384 PRESENT_FOR_OTHER_ITEM
,
385 PRESENT_FOR_UNKNOWN_ITEM
,
388 : metadata_file_present_(ABSENT
),
394 void SetUp() override
{
395 DownloadMetadataManagerTestBase::SetUp();
396 const std::string
present(testing::get
<0>(GetParam()));
397 metadata_file_present_
= (present
== "absent" ? ABSENT
:
398 (present
== "this" ? PRESENT_FOR_THIS_ITEM
:
399 (present
== "other" ? PRESENT_FOR_OTHER_ITEM
:
400 PRESENT_FOR_UNKNOWN_ITEM
)));
401 same_ops_
= (std::string(testing::get
<1>(GetParam())) == "pending");
402 other_ops_
= (std::string(testing::get
<2>(GetParam())) == "pending");
403 details_loaded_
= (std::string(testing::get
<3>(GetParam())) == "loaded");
404 set_request_
= (std::string(testing::get
<4>(GetParam())) == "set");
407 MetadataFilePresent metadata_file_present_
;
410 bool details_loaded_
;
414 // Tests that DownloadMetadataManager::SetRequest works for all combinations of
416 TEST_P(SetRequestTest
, SetRequest
) {
417 // Optionally put a metadata file in the profile directory.
418 switch (metadata_file_present_
) {
421 case PRESENT_FOR_THIS_ITEM
:
422 WriteTestMetadataFile();
424 case PRESENT_FOR_OTHER_ITEM
:
425 WriteTestMetadataFileForItem(kOtherDownloadId
);
427 case PRESENT_FOR_UNKNOWN_ITEM
:
428 WriteTestMetadataFileForItem(kCrazyDowloadId
);
432 AddDownloadManager();
435 // Optionally allow the task to read the file to complete.
436 if (details_loaded_
) {
439 // Optionally add pending operations if the load is outstanding.
441 test_item_
->NotifyObserversDownloadOpened();
443 other_item_
->NotifyObserversDownloadOpened();
446 static const char kNewUrl
[] = "http://blorf";
447 scoped_ptr
<ClientDownloadRequest
> request
;
449 request
= MakeTestRequest(kNewUrl
).Pass();
452 manager_
.SetRequest(test_item_
.get(), request
.get());
454 // Allow the write or remove task to run.
457 MockDownloadDetailsGetter details_getter
;
459 // Expect that the callback is invoked with details for this item.
462 OnDownloadDetails(ResultOf(GetDetailsDownloadUrl
, StrEq(kNewUrl
))));
464 // Expect that the callback is invoked with null to clear stale metadata.
465 EXPECT_CALL(details_getter
, OnDownloadDetails(IsNull()));
467 manager_
.GetDownloadDetails(&profile_
, details_getter
.GetCallback());
469 // In http://crbug.com/433928, open after SetRequest(nullpr) caused a crash.
470 test_item_
->NotifyObserversDownloadOpened();
472 ShutdownDownloadManager();
474 scoped_ptr
<DownloadMetadata
> metadata(ReadTestMetadataFile());
476 // Expect that the file contains metadata for the download.
477 ASSERT_TRUE(metadata
);
478 EXPECT_EQ(kTestDownloadId
, metadata
->download_id());
479 EXPECT_STREQ(kNewUrl
, metadata
->download().download().url().c_str());
481 // Expect that the file is not present.
482 ASSERT_FALSE(metadata
);
486 INSTANTIATE_TEST_CASE_P(
487 DownloadMetadataManager
,
489 testing::Combine(testing::Values("absent", "this", "other", "unknown"),
490 testing::Values("none", "pending"),
491 testing::Values("none", "pending"),
492 testing::Values("waiting", "loaded"),
493 testing::Values("clear", "set")));
495 } // namespace safe_browsing