| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/files/file_util.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/run_loop.h" |
| #include "base/stl_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/browsing_data/browsing_data_media_license_helper.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/test/test_browser_thread.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "ppapi/shared_impl/ppapi_constants.h" |
| #include "storage/browser/fileapi/async_file_util.h" |
| #include "storage/browser/fileapi/file_system_context.h" |
| #include "storage/browser/fileapi/file_system_operation_context.h" |
| #include "storage/browser/fileapi/file_system_url.h" |
| #include "storage/browser/fileapi/isolated_context.h" |
| #include "storage/browser/quota/quota_manager.h" |
| #include "storage/common/fileapi/file_system_types.h" |
| #include "storage/common/fileapi/file_system_util.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using content::BrowserContext; |
| using content::BrowserThread; |
| |
| namespace { |
| |
| // We'll use these three distinct origins for testing, both as strings and as |
| // GURLs in appropriate contexts. |
| const char kTestOrigin1[] = "http://host1:1/"; |
| const char kTestOrigin2[] = "http://host2:2/"; |
| const char kTestOrigin3[] = "http://host3:1/"; |
| |
| const GURL kOrigin1(kTestOrigin1); |
| const GURL kOrigin2(kTestOrigin2); |
| const GURL kOrigin3(kTestOrigin3); |
| |
| const char kWidevineCdmPluginId[] = "application_x-ppapi-widevine-cdm"; |
| const char kClearKeyCdmPluginId[] = "application_x-ppapi-clearkey-cdm"; |
| |
| class AwaitCompletionHelper { |
| public: |
| AwaitCompletionHelper() : start_(false), already_quit_(false) {} |
| virtual ~AwaitCompletionHelper() {} |
| |
| void BlockUntilNotified() { |
| if (!already_quit_) { |
| DCHECK(!start_); |
| start_ = true; |
| base::RunLoop().Run(); |
| } else { |
| DCHECK(!start_); |
| already_quit_ = false; |
| } |
| } |
| |
| base::Closure NotifyClosure() { |
| return base::Bind(&AwaitCompletionHelper::Notify, base::Unretained(this)); |
| } |
| |
| private: |
| void Notify() { |
| if (start_) { |
| DCHECK(!already_quit_); |
| base::RunLoop::QuitCurrentWhenIdleDeprecated(); |
| start_ = false; |
| } else { |
| DCHECK(!already_quit_); |
| already_quit_ = true; |
| } |
| } |
| |
| // Helps prevent from running message_loop, if the callback invoked |
| // immediately. |
| bool start_; |
| bool already_quit_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AwaitCompletionHelper); |
| }; |
| |
| // The FileSystem APIs are all asynchronous; this testing class wraps up the |
| // boilerplate code necessary to deal with waiting for responses. In a nutshell, |
| // any async call whose response we want to test ought to be followed by a call |
| // to BlockUntilNotified(), which will block until Notify() is called. |
| class BrowsingDataMediaLicenseHelperTest : public testing::Test { |
| public: |
| BrowsingDataMediaLicenseHelperTest() { |
| now_ = base::Time::Now(); |
| profile_.reset(new TestingProfile()); |
| filesystem_context_ = |
| BrowserContext::GetDefaultStoragePartition(profile_.get()) |
| ->GetFileSystemContext(); |
| helper_ = BrowsingDataMediaLicenseHelper::Create(filesystem_context_); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| ~BrowsingDataMediaLicenseHelperTest() override { |
| // Avoid memory leaks. |
| profile_.reset(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // Calls StartFetching() on the test's BrowsingDataMediaLicenseHelper |
| // object, then blocks until the callback is executed. |
| void FetchMediaLicenses() { |
| AwaitCompletionHelper await_completion; |
| helper_->StartFetching( |
| base::Bind(&BrowsingDataMediaLicenseHelperTest::OnFetchMediaLicenses, |
| base::Unretained(this), await_completion.NotifyClosure())); |
| await_completion.BlockUntilNotified(); |
| } |
| |
| // Callback that should be executed in response to StartFetching(), and stores |
| // found file systems locally so that they are available via GetFileSystems(). |
| void OnFetchMediaLicenses( |
| const base::Closure& done_cb, |
| const std::list<BrowsingDataMediaLicenseHelper::MediaLicenseInfo>& |
| media_license_info_list) { |
| media_license_info_list_.reset( |
| new std::list<BrowsingDataMediaLicenseHelper::MediaLicenseInfo>( |
| media_license_info_list)); |
| done_cb.Run(); |
| } |
| |
| // Add some files to the PluginPrivateFileSystem. They are created as follows: |
| // kOrigin1 - ClearKey - 1 file - timestamp 10 days ago |
| // kOrigin2 - Widevine - 2 files - timestamps now and 60 days ago |
| // kOrigin3 - Widevine - 2 files - timestamps 20 and 30 days ago |
| virtual void PopulateTestMediaLicenseData() { |
| const base::Time ten_days_ago = now_ - base::TimeDelta::FromDays(10); |
| const base::Time twenty_days_ago = now_ - base::TimeDelta::FromDays(20); |
| const base::Time thirty_days_ago = now_ - base::TimeDelta::FromDays(30); |
| const base::Time sixty_days_ago = now_ - base::TimeDelta::FromDays(60); |
| |
| std::string clearkey_fsid = |
| CreateFileSystem(kClearKeyCdmPluginId, kOrigin1); |
| storage::FileSystemURL clearkey_file = |
| CreateFile(kOrigin1, clearkey_fsid, "foo"); |
| SetFileTimestamp(clearkey_file, ten_days_ago); |
| |
| std::string widevine_fsid = |
| CreateFileSystem(kWidevineCdmPluginId, kOrigin2); |
| storage::FileSystemURL widevine_file1 = |
| CreateFile(kOrigin2, widevine_fsid, "bar1"); |
| storage::FileSystemURL widevine_file2 = |
| CreateFile(kOrigin2, widevine_fsid, "bar2"); |
| SetFileTimestamp(widevine_file1, now_); |
| SetFileTimestamp(widevine_file2, sixty_days_ago); |
| |
| std::string widevine_fsid2 = |
| CreateFileSystem(kWidevineCdmPluginId, kOrigin3); |
| storage::FileSystemURL widevine_file3 = |
| CreateFile(kOrigin3, widevine_fsid2, "test1"); |
| storage::FileSystemURL widevine_file4 = |
| CreateFile(kOrigin3, widevine_fsid2, "test2"); |
| SetFileTimestamp(widevine_file3, twenty_days_ago); |
| SetFileTimestamp(widevine_file4, thirty_days_ago); |
| } |
| |
| const base::Time Now() { return now_; } |
| |
| void DeleteMediaLicenseOrigin(const GURL& origin) { |
| helper_->DeleteMediaLicenseOrigin(origin); |
| } |
| |
| std::list<BrowsingDataMediaLicenseHelper::MediaLicenseInfo>* |
| ReturnedMediaLicenseInfo() const { |
| return media_license_info_list_.get(); |
| } |
| |
| private: |
| // Creates a PluginPrivateFileSystem for the |plugin_name| and |origin| |
| // provided. Returns the file system ID for the created |
| // PluginPrivateFileSystem. |
| std::string CreateFileSystem(const std::string& plugin_name, |
| const GURL& origin) { |
| AwaitCompletionHelper await_completion; |
| std::string fsid = storage::IsolatedContext::GetInstance() |
| ->RegisterFileSystemForVirtualPath( |
| storage::kFileSystemTypePluginPrivate, |
| ppapi::kPluginPrivateRootName, base::FilePath()); |
| EXPECT_TRUE(storage::ValidateIsolatedFileSystemId(fsid)); |
| filesystem_context_->OpenPluginPrivateFileSystem( |
| origin, storage::kFileSystemTypePluginPrivate, fsid, plugin_name, |
| storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, |
| base::Bind(&BrowsingDataMediaLicenseHelperTest::OnFileSystemOpened, |
| base::Unretained(this), await_completion.NotifyClosure())); |
| await_completion.BlockUntilNotified(); |
| return fsid; |
| } |
| |
| void OnFileSystemOpened(const base::Closure& done_cb, |
| base::File::Error result) { |
| EXPECT_EQ(base::File::FILE_OK, result) << base::File::ErrorToString(result); |
| done_cb.Run(); |
| } |
| |
| // Creates a file named |file_name| in the PluginPrivateFileSystem identified |
| // by |origin| and |fsid|. The file is empty (so size = 0). Returns the URL |
| // for the created file. The file must not already exist or the test will |
| // fail. |
| storage::FileSystemURL CreateFile(const GURL& origin, |
| const std::string& fsid, |
| const std::string& file_name) { |
| AwaitCompletionHelper await_completion; |
| std::string root = storage::GetIsolatedFileSystemRootURIString( |
| origin, fsid, ppapi::kPluginPrivateRootName); |
| storage::FileSystemURL file_url = |
| filesystem_context_->CrackURL(GURL(root + file_name)); |
| storage::AsyncFileUtil* file_util = filesystem_context_->GetAsyncFileUtil( |
| storage::kFileSystemTypePluginPrivate); |
| std::unique_ptr<storage::FileSystemOperationContext> operation_context = |
| base::WrapUnique( |
| new storage::FileSystemOperationContext(filesystem_context_)); |
| operation_context->set_allowed_bytes_growth( |
| storage::QuotaManager::kNoLimit); |
| file_util->EnsureFileExists( |
| std::move(operation_context), file_url, |
| base::Bind(&BrowsingDataMediaLicenseHelperTest::OnFileCreated, |
| base::Unretained(this), await_completion.NotifyClosure())); |
| await_completion.BlockUntilNotified(); |
| return file_url; |
| } |
| |
| void OnFileCreated(const base::Closure& done_cb, |
| base::File::Error result, |
| bool created) { |
| EXPECT_EQ(base::File::FILE_OK, result) << base::File::ErrorToString(result); |
| EXPECT_TRUE(created); |
| done_cb.Run(); |
| } |
| |
| // Sets the last_access_time and last_modified_time to |time_stamp| on the |
| // file specified by |file_url|. The file must already exist. |
| void SetFileTimestamp(const storage::FileSystemURL& file_url, |
| const base::Time& time_stamp) { |
| AwaitCompletionHelper await_completion; |
| storage::AsyncFileUtil* file_util = filesystem_context_->GetAsyncFileUtil( |
| storage::kFileSystemTypePluginPrivate); |
| std::unique_ptr<storage::FileSystemOperationContext> operation_context = |
| base::WrapUnique( |
| new storage::FileSystemOperationContext(filesystem_context_)); |
| file_util->Touch( |
| std::move(operation_context), file_url, time_stamp, time_stamp, |
| base::Bind(&BrowsingDataMediaLicenseHelperTest::OnFileTouched, |
| base::Unretained(this), await_completion.NotifyClosure())); |
| await_completion.BlockUntilNotified(); |
| } |
| |
| void OnFileTouched(const base::Closure& done_cb, base::File::Error result) { |
| EXPECT_EQ(base::File::FILE_OK, result) << base::File::ErrorToString(result); |
| done_cb.Run(); |
| } |
| |
| content::TestBrowserThreadBundle thread_bundle_; |
| std::unique_ptr<TestingProfile> profile_; |
| scoped_refptr<BrowsingDataMediaLicenseHelper> helper_; |
| |
| // Keep a fixed "now" so that we can compare timestamps. |
| base::Time now_; |
| |
| // We don't own this pointer. |
| storage::FileSystemContext* filesystem_context_; |
| |
| // Storage to pass information back from callbacks. |
| std::unique_ptr<std::list<BrowsingDataMediaLicenseHelper::MediaLicenseInfo>> |
| media_license_info_list_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BrowsingDataMediaLicenseHelperTest); |
| }; |
| |
| // Verifies that the BrowsingDataMediaLicenseHelper correctly handles an empty |
| // filesystem. |
| TEST_F(BrowsingDataMediaLicenseHelperTest, Empty) { |
| FetchMediaLicenses(); |
| EXPECT_EQ(0u, ReturnedMediaLicenseInfo()->size()); |
| } |
| |
| // Verifies that the BrowsingDataMediaLicenseHelper correctly finds the test |
| // data, and that each media license returned contains the expected data. |
| TEST_F(BrowsingDataMediaLicenseHelperTest, FetchData) { |
| PopulateTestMediaLicenseData(); |
| |
| FetchMediaLicenses(); |
| EXPECT_EQ(3u, ReturnedMediaLicenseInfo()->size()); |
| |
| // Order is arbitrary, verify both origins. |
| bool test_hosts_found[] = {false, false, false}; |
| for (const auto& info : *ReturnedMediaLicenseInfo()) { |
| if (info.origin == kOrigin1) { |
| EXPECT_FALSE(test_hosts_found[0]); |
| test_hosts_found[0] = true; |
| EXPECT_EQ(0u, info.size); |
| // Single file for origin1 should be 10 days ago. |
| EXPECT_EQ(10, (Now() - info.last_modified_time).InDays()); |
| } else if (info.origin == kOrigin2) { |
| EXPECT_FALSE(test_hosts_found[1]); |
| test_hosts_found[1] = true; |
| EXPECT_EQ(0u, info.size); |
| // Files for origin2 are now and 60 days ago, so it should report now. |
| EXPECT_EQ(0, (Now() - info.last_modified_time).InDays()); |
| } else if (info.origin == kOrigin3) { |
| EXPECT_FALSE(test_hosts_found[2]); |
| test_hosts_found[2] = true; |
| EXPECT_EQ(0u, info.size); |
| // Files for origin3 are 20 and 30 days ago, so it should report 20. |
| EXPECT_EQ(20, (Now() - info.last_modified_time).InDays()); |
| } else { |
| ADD_FAILURE() << info.origin.spec() << " isn't an origin we added."; |
| } |
| } |
| for (size_t i = 0; i < base::size(test_hosts_found); i++) { |
| EXPECT_TRUE(test_hosts_found[i]); |
| } |
| } |
| |
| // Verifies that the BrowsingDataMediaLicenseHelper correctly deletes media |
| // licenses via DeleteMediaLicenseOrigin(). |
| TEST_F(BrowsingDataMediaLicenseHelperTest, DeleteData) { |
| PopulateTestMediaLicenseData(); |
| |
| DeleteMediaLicenseOrigin(kOrigin1); |
| DeleteMediaLicenseOrigin(kOrigin2); |
| |
| FetchMediaLicenses(); |
| EXPECT_EQ(1u, ReturnedMediaLicenseInfo()->size()); |
| |
| BrowsingDataMediaLicenseHelper::MediaLicenseInfo info = |
| *(ReturnedMediaLicenseInfo()->begin()); |
| EXPECT_EQ(kOrigin3, info.origin); |
| EXPECT_EQ(0u, info.size); |
| EXPECT_EQ(20, (Now() - info.last_modified_time).InDays()); |
| } |
| |
| } // namespace |