blob: e72c9475003afc7c40598f8e7c1a6f5ebbf04c5a [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/values_test_util.h"
#include "components/version_info/channel.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/features/feature_channel.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/protocol_handler_info.h"
#include "extensions/common/manifest_test.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
namespace errors = manifest_errors;
class ManifestProtocolHandlersTest : public ManifestTest {
public:
ManifestProtocolHandlersTest() {
feature_list_.InitAndEnableFeature(
extensions_features::kExtensionProtocolHandlers);
}
protected:
ManifestData GetManifestData(const char* manifest_part) {
static constexpr char kManifestStub[] =
R"({
"name": "Test",
"version": "0.0.1",
"manifest_version": 3,
"protocol_handlers": %s
})";
base::Value manifest_value =
base::test::ParseJson(base::StringPrintf(kManifestStub, manifest_part));
EXPECT_EQ(base::Value::Type::DICT, manifest_value.type());
return ManifestData(std::move(manifest_value).TakeDict());
}
private:
base::test::ScopedFeatureList feature_list_;
ScopedCurrentChannel current_channel_{version_info::Channel::DEV};
};
TEST_F(ManifestProtocolHandlersTest, GeneralSuccess) {
struct {
const char* title;
const char* protocol_handler;
const size_t expected_handlers_count;
} test_cases[] = {
{"Minimum required entry.",
R"([
{
"protocol": "web+testingScheme",
"name": "Testing handler",
"uriTemplate": "https:/example.com/%s"
}
])",
1},
{"Multiple protocols.",
R"([
{
"protocol": "web+testingScheme",
"name": "Testing handler 1",
"uriTemplate": "https:/example1.com/%s"
},
{
"protocol": "ipfs",
"name": "Testing handler 2",
"uriTemplate": "https:/example2.com/%s"
}
])",
2},
};
for (const auto& test_case : test_cases) {
SCOPED_TRACE(test_case.title);
scoped_refptr<Extension> extension(LoadAndExpectSuccess(
std::move(GetManifestData(test_case.protocol_handler))));
ASSERT_TRUE(extension);
const ProtocolHandlersInfo* handlers =
ProtocolHandlers::GetProtocolHandlers(*extension);
ASSERT_TRUE(handlers);
ASSERT_EQ(test_case.expected_handlers_count, handlers->size());
}
}
TEST_F(ManifestProtocolHandlersTest, InvalidManifestVersion) {
static constexpr char kManifestStub[] =
R"({
"name": "Test",
"version": "0.0.1",
"manifest_version": 2,
"protocol_handlers": [
{
"protocol": "testingScheme",
"name": "Testing handler",
"uriTemplate": "testingURI"
}
]
})";
base::Value manifest_value = base::test::ParseJson(kManifestStub);
auto manifest_data = ManifestData(std::move(manifest_value).TakeDict());
LoadAndExpectWarning(
manifest_data,
"'protocol_handlers' requires manifest version of at least 3.");
}
TEST_F(ManifestProtocolHandlersTest, InvalidManifestSyntax) {
struct {
const char* title;
const char* protocol_handler;
std::vector<std::string> expected_warnings;
} test_cases[] = {
{
"Invalid declaration: empty list.",
R"([])",
{errors::kInvalidProtocolHandlersEmpty},
},
// protocol invalid cases.
{
"Missing 'protocol' key: not declared.",
R"([
{
"name": "Testing handler",
"uriTemplate": "testingURI"
}
])",
{"Error at key 'protocol_handlers'. Parsing array failed at index 0: "
"'protocol' is required"},
},
{
"Missing 'name' key: not declared.",
R"([
{
"protocol": "web+testingScheme",
"uriTemplate": "testingURI"
}
])",
{"Error at key 'protocol_handlers'. Parsing array failed at index 0: "
"'name' is required"},
},
{
"Missing protocol key: wrong key.",
R"([
{
"protocol": "testingScheme",
"name": "Testing handler 1",
"uriTemplate": "testingURI"
},
{
"scheme": "testingScheme",
"name": "Testing handler 2",
"uriTemplate": "testingURI"
}
])",
{"Error at key 'protocol_handlers'. Parsing array failed at index 1: "
"'protocol' is required"},
},
{
"Missing uriTemplate key: not declared.",
R"([
{
"name": "Testing handler 1",
"protocol": "testingScheme",
}
])",
{"Error at key 'protocol_handlers'. Parsing array failed at index 0: "
"'uriTemplate' is required"},
},
{
"Missing uriTemplate key: wrong key.",
R"([
{
"protocol": "testingURI",
"name": "Testing handler 1",
"uriTemplate": "testingScheme"
},
{
"protocol": "testingScheme",
"name": "Testing handler 1",
"url": "testingURI"
}
])",
{"Error at key 'protocol_handlers'. Parsing array failed at index 1: "
"'uriTemplate' is required"},
},
};
for (const auto& test_case : test_cases) {
SCOPED_TRACE(test_case.title);
LoadAndExpectWarnings(GetManifestData(test_case.protocol_handler),
test_case.expected_warnings);
}
}
TEST_F(ManifestProtocolHandlersTest, InvalidProtocolHandler) {
struct {
const char* title;
const char* protocol_handler;
std::vector<std::string> expected_warnings;
} test_cases[] = {
{
"Registering handler for an unprefix scheme.",
R"([
{
"protocol": "glsearch",
"name": "Testing handler",
"uriTemplate": "https://www.google.com/search?q=%s"
}
])",
{errors::kProtocolHandlerSchemeNotInSafeList},
},
{
"Protocol handlers with unprefix scheme and an empty name.",
R"([
{
"protocol": "glsearch",
"name": "",
"uriTemplate": "https://www.google.com/search?q=%s"
}
])",
{errors::kProtocolHandlerEmptyName,
errors::kProtocolHandlerSchemeNotInSafeList},
},
{
"Registering handler for a reserved scheme.",
R"([
{
"protocol": "https",
"name": "Testing handler",
"uriTemplate": "https://www.google.com/search?q=%s"
}
])",
{errors::kProtocolHandlerSchemeNotInSafeList},
},
{
"Custom handler URL without '%s' token.",
R"([
{
"protocol": "ssh",
"name": "Testing handler",
"uriTemplate": "https://www.google.com/search"
}
])",
{errors::kProtocolHandlerUrlTokenMissing},
},
{
"Custom handler URL with an invalid syntax.",
R"([
{
"protocol": "web+glsearch",
"name": "Testing handler",
"uriTemplate": "https:/example.com:abc/path"
}
])",
{errors::kProtocolHandlerUrlInvalidSyntax},
},
{
"Custom handler URL with an untrustworthy scheme.",
R"([
{
"protocol": "web+glsearch",
"name": "Testing handler",
"uriTemplate": "http:/example.com/%s"
}
])",
{errors::kProtocolHandlerUntrustworthyScheme},
},
{
"Custom handler URL with an untrustworthy scheme and an Opaque "
"Origin.",
R"([
{
"protocol": "web+glsearch",
"name": "Testing handler",
"uriTemplate": "data:/%s"
}
])",
{errors::kProtocolHandlerUntrustworthyScheme,
errors::kProtocolHandlerOpaqueOrigin},
},
{
"Custom handler URL with an invalid syntax and untrustworthy scheme.",
R"([
{
"protocol": "web+glsearch",
"name": "Testing handler",
"uriTemplate": "http://[v8.:::]//url=%s"
}
])",
{errors::kProtocolHandlerUrlInvalidSyntax,
errors::kProtocolHandlerUntrustworthyScheme},
},
};
for (const auto& test_case : test_cases) {
SCOPED_TRACE(test_case.title);
LoadAndExpectWarnings(GetManifestData(test_case.protocol_handler),
test_case.expected_warnings);
}
}
} // namespace extensions