blob: a5e065ec5bba51a518ffeb0d2f3b60f126506fe7 [file] [log] [blame]
// 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 "components/arc/intent_helper/arc_intent_helper_bridge.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/memory/ptr_util.h"
#include "base/optional.h"
#include "components/arc/intent_helper/open_url_delegate.h"
#include "components/arc/mojom/intent_helper.mojom.h"
#include "components/arc/session/arc_bridge_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace arc {
namespace {
constexpr char kPackageName[] = "default.package.name";
IntentFilter GetIntentFilter(const std::string& host,
const std::string& pkg_name) {
std::vector<IntentFilter::AuthorityEntry> authorities;
authorities.emplace_back(host, /*port=*/-1);
return IntentFilter(pkg_name, /*actions=*/std::vector<std::string>(),
std::move(authorities),
std::vector<IntentFilter::PatternMatcher>(),
/*schemes=*/std::vector<std::string>(),
/*mime_types=*/std::vector<std::string>());
}
} // namespace
class ArcIntentHelperTest : public testing::Test {
protected:
ArcIntentHelperTest() = default;
ArcIntentHelperTest(const ArcIntentHelperTest&) = delete;
ArcIntentHelperTest& operator=(const ArcIntentHelperTest&) = delete;
class TestOpenUrlDelegate : public OpenUrlDelegate {
public:
~TestOpenUrlDelegate() override = default;
// OpenUrlDelegate:
void OpenUrlFromArc(const GURL& url) override { last_opened_url_ = url; }
void OpenWebAppFromArc(const GURL& url) override { last_opened_url_ = url; }
void OpenArcCustomTab(
const GURL& url,
int32_t task_id,
mojom::IntentHelperHost::OnOpenCustomTabCallback callback) override {
std::move(callback).Run(mojo::NullRemote());
}
void OpenChromePageFromArc(mojom::ChromePage chrome_page) override {}
GURL TakeLastOpenedUrl() {
GURL result = std::move(last_opened_url_);
last_opened_url_ = GURL();
return result;
}
private:
GURL last_opened_url_;
};
std::unique_ptr<ArcBridgeService> arc_bridge_service_;
std::unique_ptr<TestOpenUrlDelegate> test_open_url_delegate_;
std::unique_ptr<ArcIntentHelperBridge> instance_;
private:
void SetUp() override {
arc_bridge_service_ = std::make_unique<ArcBridgeService>();
test_open_url_delegate_ = std::make_unique<TestOpenUrlDelegate>();
instance_ = std::make_unique<ArcIntentHelperBridge>(
nullptr /* context */, arc_bridge_service_.get());
ArcIntentHelperBridge::SetOpenUrlDelegate(test_open_url_delegate_.get());
}
void TearDown() override {
ArcIntentHelperBridge::SetOpenUrlDelegate(nullptr);
instance_.reset();
test_open_url_delegate_.reset();
arc_bridge_service_.reset();
}
};
// Tests if IsIntentHelperPackage works as expected. Probably too trivial
// to test but just in case.
TEST_F(ArcIntentHelperTest, TestIsIntentHelperPackage) {
EXPECT_FALSE(ArcIntentHelperBridge::IsIntentHelperPackage(""));
EXPECT_FALSE(ArcIntentHelperBridge::IsIntentHelperPackage(
ArcIntentHelperBridge::kArcIntentHelperPackageName + std::string("a")));
EXPECT_FALSE(ArcIntentHelperBridge::IsIntentHelperPackage(
ArcIntentHelperBridge::kArcIntentHelperPackageName +
std::string("/.ArcIntentHelperActivity")));
EXPECT_TRUE(ArcIntentHelperBridge::IsIntentHelperPackage(
ArcIntentHelperBridge::kArcIntentHelperPackageName));
}
// Tests if FilterOutIntentHelper removes handlers as expected.
TEST_F(ArcIntentHelperTest, TestFilterOutIntentHelper) {
{
std::vector<mojom::IntentHandlerInfoPtr> orig;
std::vector<mojom::IntentHandlerInfoPtr> filtered =
ArcIntentHelperBridge::FilterOutIntentHelper(std::move(orig));
EXPECT_EQ(0U, filtered.size());
}
{
std::vector<mojom::IntentHandlerInfoPtr> orig;
orig.push_back(mojom::IntentHandlerInfo::New());
orig[0]->name = "0";
orig[0]->package_name = "package_name0";
orig.push_back(mojom::IntentHandlerInfo::New());
orig[1]->name = "1";
orig[1]->package_name = "package_name1";
// FilterOutIntentHelper is no-op in this case.
std::vector<mojom::IntentHandlerInfoPtr> filtered =
ArcIntentHelperBridge::FilterOutIntentHelper(std::move(orig));
EXPECT_EQ(2U, filtered.size());
}
{
std::vector<mojom::IntentHandlerInfoPtr> orig;
orig.push_back(mojom::IntentHandlerInfo::New());
orig[0]->name = "0";
orig[0]->package_name = ArcIntentHelperBridge::kArcIntentHelperPackageName;
orig.push_back(mojom::IntentHandlerInfo::New());
orig[1]->name = "1";
orig[1]->package_name = "package_name1";
// FilterOutIntentHelper should remove the first element.
std::vector<mojom::IntentHandlerInfoPtr> filtered =
ArcIntentHelperBridge::FilterOutIntentHelper(std::move(orig));
ASSERT_EQ(1U, filtered.size());
EXPECT_EQ("1", filtered[0]->name);
EXPECT_EQ("package_name1", filtered[0]->package_name);
}
{
std::vector<mojom::IntentHandlerInfoPtr> orig;
orig.push_back(mojom::IntentHandlerInfo::New());
orig[0]->name = "0";
orig[0]->package_name = ArcIntentHelperBridge::kArcIntentHelperPackageName;
orig.push_back(mojom::IntentHandlerInfo::New());
orig[1]->name = "1";
orig[1]->package_name = "package_name1";
orig.push_back(mojom::IntentHandlerInfo::New());
orig[2]->name = "2";
orig[2]->package_name = ArcIntentHelperBridge::kArcIntentHelperPackageName;
// FilterOutIntentHelper should remove two elements.
std::vector<mojom::IntentHandlerInfoPtr> filtered =
ArcIntentHelperBridge::FilterOutIntentHelper(std::move(orig));
ASSERT_EQ(1U, filtered.size());
EXPECT_EQ("1", filtered[0]->name);
EXPECT_EQ("package_name1", filtered[0]->package_name);
}
{
std::vector<mojom::IntentHandlerInfoPtr> orig;
orig.push_back(mojom::IntentHandlerInfo::New());
orig[0]->name = "0";
orig[0]->package_name = ArcIntentHelperBridge::kArcIntentHelperPackageName;
orig.push_back(mojom::IntentHandlerInfo::New());
orig[1]->name = "1";
orig[1]->package_name = ArcIntentHelperBridge::kArcIntentHelperPackageName;
// FilterOutIntentHelper should remove all elements.
std::vector<mojom::IntentHandlerInfoPtr> filtered =
ArcIntentHelperBridge::FilterOutIntentHelper(std::move(orig));
EXPECT_EQ(0U, filtered.size());
}
}
// Tests if observer works as expected.
TEST_F(ArcIntentHelperTest, TestObserver) {
class MockObserver : public ArcIntentHelperObserver {
public:
MOCK_METHOD(void,
OnArcDownloadAdded,
(const base::FilePath& relative_path,
const std::string& owner_package_name),
(override));
MOCK_METHOD(void,
OnIntentFiltersUpdated,
(const base::Optional<std::string>& package_name),
(override));
MOCK_METHOD(void, OnPreferredAppsChanged, (), (override));
};
// Create and add observer.
testing::StrictMock<MockObserver> observer;
instance_->AddObserver(&observer);
{
// Observer should be called when a download is added.
std::string relative_path("Download/foo/bar.pdf");
std::string owner_package_name("owner_package_name");
EXPECT_CALL(observer,
OnArcDownloadAdded(testing::Eq(base::FilePath(relative_path)),
testing::Ref(owner_package_name)));
instance_->OnDownloadAdded(relative_path, owner_package_name);
testing::Mock::VerifyAndClearExpectations(&observer);
}
{
// Observer should *not* be called when a download is added outside of the
// Download/ folder. This would be an unexpected event coming from ARC but
// we protect against it because ARC is treated as an untrusted source.
instance_->OnDownloadAdded(/*relative_path=*/"Download/../foo/bar.pdf",
/*owner_package_name=*/"owner_package_name");
testing::Mock::VerifyAndClearExpectations(&observer);
}
{
// Observer should be called when an intent filter is updated.
EXPECT_CALL(observer, OnIntentFiltersUpdated(testing::Eq(base::nullopt)));
instance_->OnIntentFiltersUpdated(/*filters=*/std::vector<IntentFilter>());
testing::Mock::VerifyAndClearExpectations(&observer);
}
{
// Observer should be called when preferred apps change.
EXPECT_CALL(observer, OnPreferredAppsChanged);
instance_->OnPreferredAppsChanged(/*added=*/{}, /*deleted=*/{});
testing::Mock::VerifyAndClearExpectations(&observer);
}
// Observer should not be called after it's removed.
instance_->RemoveObserver(&observer);
instance_->OnDownloadAdded(/*relative_path=*/"Download/foo/bar.pdf",
/*owner_package_name=*/"owner_package_name");
instance_->OnIntentFiltersUpdated(/*filters=*/{});
instance_->OnPreferredAppsChanged(/*added=*/{}, /*removed=*/{});
}
// Tests that ShouldChromeHandleUrl returns true by default.
TEST_F(ArcIntentHelperTest, TestDefault) {
EXPECT_TRUE(instance_->ShouldChromeHandleUrl(GURL("http://www.google.com")));
EXPECT_TRUE(instance_->ShouldChromeHandleUrl(GURL("https://www.google.com")));
EXPECT_TRUE(instance_->ShouldChromeHandleUrl(GURL("file:///etc/password")));
EXPECT_TRUE(instance_->ShouldChromeHandleUrl(GURL("chrome://help")));
EXPECT_TRUE(instance_->ShouldChromeHandleUrl(GURL("about://chrome")));
}
// Tests that ShouldChromeHandleUrl returns false when there's a match.
TEST_F(ArcIntentHelperTest, TestSingleFilter) {
std::vector<IntentFilter> array;
array.emplace_back(GetIntentFilter("www.google.com", kPackageName));
instance_->OnIntentFiltersUpdated(std::move(array));
EXPECT_FALSE(instance_->ShouldChromeHandleUrl(GURL("http://www.google.com")));
EXPECT_FALSE(
instance_->ShouldChromeHandleUrl(GURL("https://www.google.com")));
EXPECT_TRUE(
instance_->ShouldChromeHandleUrl(GURL("https://www.google.co.uk")));
}
// Tests the same with multiple filters.
TEST_F(ArcIntentHelperTest, TestMultipleFilters) {
std::vector<IntentFilter> array;
array.emplace_back(GetIntentFilter("www.google.com", kPackageName));
array.emplace_back(GetIntentFilter("www.google.co.uk", kPackageName));
array.emplace_back(GetIntentFilter("dev.chromium.org", kPackageName));
instance_->OnIntentFiltersUpdated(std::move(array));
EXPECT_FALSE(instance_->ShouldChromeHandleUrl(GURL("http://www.google.com")));
EXPECT_FALSE(
instance_->ShouldChromeHandleUrl(GURL("https://www.google.com")));
EXPECT_FALSE(
instance_->ShouldChromeHandleUrl(GURL("http://www.google.co.uk")));
EXPECT_FALSE(
instance_->ShouldChromeHandleUrl(GURL("https://www.google.co.uk")));
EXPECT_FALSE(
instance_->ShouldChromeHandleUrl(GURL("http://dev.chromium.org")));
EXPECT_FALSE(
instance_->ShouldChromeHandleUrl(GURL("https://dev.chromium.org")));
EXPECT_TRUE(instance_->ShouldChromeHandleUrl(GURL("http://www.android.com")));
}
// Tests that ShouldChromeHandleUrl returns true for non http(s) URLs.
TEST_F(ArcIntentHelperTest, TestNonHttp) {
std::vector<IntentFilter> array;
array.emplace_back(GetIntentFilter("www.google.com", kPackageName));
instance_->OnIntentFiltersUpdated(std::move(array));
EXPECT_TRUE(
instance_->ShouldChromeHandleUrl(GURL("chrome://www.google.com")));
EXPECT_TRUE(
instance_->ShouldChromeHandleUrl(GURL("custom://www.google.com")));
}
// Tests that ShouldChromeHandleUrl discards the previous filters when
// UpdateIntentFilters is called with new ones.
TEST_F(ArcIntentHelperTest, TestMultipleUpdate) {
std::vector<IntentFilter> array;
array.emplace_back(GetIntentFilter("www.google.com", kPackageName));
array.emplace_back(GetIntentFilter("dev.chromium.org", kPackageName));
instance_->OnIntentFiltersUpdated(std::move(array));
std::vector<IntentFilter> array2;
array2.emplace_back(GetIntentFilter("www.google.co.uk", kPackageName));
array2.emplace_back(GetIntentFilter("dev.chromium.org", kPackageName));
array2.emplace_back(GetIntentFilter("www.android.com", kPackageName));
instance_->OnIntentFiltersUpdated(std::move(array2));
EXPECT_TRUE(instance_->ShouldChromeHandleUrl(GURL("http://www.google.com")));
EXPECT_TRUE(instance_->ShouldChromeHandleUrl(GURL("https://www.google.com")));
EXPECT_FALSE(
instance_->ShouldChromeHandleUrl(GURL("http://www.google.co.uk")));
EXPECT_FALSE(
instance_->ShouldChromeHandleUrl(GURL("https://www.google.co.uk")));
EXPECT_FALSE(
instance_->ShouldChromeHandleUrl(GURL("http://dev.chromium.org")));
EXPECT_FALSE(
instance_->ShouldChromeHandleUrl(GURL("https://dev.chromium.org")));
EXPECT_FALSE(
instance_->ShouldChromeHandleUrl(GURL("http://www.android.com")));
EXPECT_FALSE(
instance_->ShouldChromeHandleUrl(GURL("https://www.android.com")));
}
// Tests that intent helper app (on ARC) is not taken as an app candidate, other
// suitable app candidates should still match if possible.
TEST_F(ArcIntentHelperTest, TestIntentHelperAppIsNotAValidCandidate) {
std::vector<IntentFilter> array;
array.emplace_back(GetIntentFilter(
"www.google.com", ArcIntentHelperBridge::kArcIntentHelperPackageName));
array.emplace_back(GetIntentFilter(
"www.android.com", ArcIntentHelperBridge::kArcIntentHelperPackageName));
// Let the package name start with "z" to ensure the intent helper package
// is not always the last package checked in the ShouldChromeHandleUrl
// filter matching logic. This is to ensure this unit test tests the package
// name checking logic properly.
array.emplace_back(GetIntentFilter("dev.chromium.org", "z.package.name"));
instance_->OnIntentFiltersUpdated(std::move(array));
EXPECT_TRUE(instance_->ShouldChromeHandleUrl(GURL("http://www.google.com")));
EXPECT_TRUE(instance_->ShouldChromeHandleUrl(GURL("https://www.google.com")));
EXPECT_TRUE(instance_->ShouldChromeHandleUrl(GURL("http://www.android.com")));
EXPECT_TRUE(
instance_->ShouldChromeHandleUrl(GURL("https://www.android.com")));
EXPECT_FALSE(
instance_->ShouldChromeHandleUrl(GURL("http://dev.chromium.org")));
EXPECT_FALSE(
instance_->ShouldChromeHandleUrl(GURL("https://dev.chromium.org")));
}
// Tests that OnOpenUrl opens the URL in Chrome browser.
TEST_F(ArcIntentHelperTest, TestOnOpenUrl) {
instance_->OnOpenUrl("http://google.com");
EXPECT_EQ(GURL("http://google.com"),
test_open_url_delegate_->TakeLastOpenedUrl());
instance_->OnOpenUrl("https://google.com");
EXPECT_EQ(GURL("https://google.com"),
test_open_url_delegate_->TakeLastOpenedUrl());
}
// Tests that OnOpenWebApp opens only HTTPS URLs.
TEST_F(ArcIntentHelperTest, TestOnOpenWebApp) {
instance_->OnOpenWebApp("http://google.com");
EXPECT_EQ(GURL(), test_open_url_delegate_->TakeLastOpenedUrl());
instance_->OnOpenWebApp("https://google.com");
EXPECT_EQ(GURL("https://google.com"),
test_open_url_delegate_->TakeLastOpenedUrl());
}
// Tests that OnOpenUrl does not open URLs with the 'chrome://' and equivalent
// schemes like 'about:'.
TEST_F(ArcIntentHelperTest, TestOnOpenUrl_ChromeScheme) {
instance_->OnOpenUrl("chrome://www.google.com");
EXPECT_FALSE(test_open_url_delegate_->TakeLastOpenedUrl().is_valid());
instance_->OnOpenUrl("chrome://settings");
EXPECT_FALSE(test_open_url_delegate_->TakeLastOpenedUrl().is_valid());
instance_->OnOpenUrl("about:");
EXPECT_FALSE(test_open_url_delegate_->TakeLastOpenedUrl().is_valid());
instance_->OnOpenUrl("about:settings");
EXPECT_FALSE(test_open_url_delegate_->TakeLastOpenedUrl().is_valid());
instance_->OnOpenUrl("about:blank");
EXPECT_FALSE(test_open_url_delegate_->TakeLastOpenedUrl().is_valid());
}
// Tests that AppendStringToIntentHelperPackageName works.
TEST_F(ArcIntentHelperTest, TestAppendStringToIntentHelperPackageName) {
std::string package_name = ArcIntentHelperBridge::kArcIntentHelperPackageName;
std::string fake_activity = "this_is_a_fake_activity";
EXPECT_EQ(ArcIntentHelperBridge::AppendStringToIntentHelperPackageName(
fake_activity),
package_name + "." + fake_activity);
const std::string empty_string;
EXPECT_EQ(ArcIntentHelperBridge::AppendStringToIntentHelperPackageName(
empty_string),
package_name + ".");
}
} // namespace arc