| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extensions/common/manifest_handlers/externally_connectable.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "components/crx_file/id_util.h" |
| #include "extensions/common/api/extensions_manifest_types.h" |
| #include "extensions/common/error_utils.h" |
| #include "extensions/common/manifest_constants.h" |
| #include "extensions/common/manifest_handlers/permissions_parser.h" |
| #include "extensions/common/permissions/api_permission_set.h" |
| #include "extensions/common/url_pattern.h" |
| #include "url/gurl.h" |
| |
| namespace extensions { |
| |
| namespace externally_connectable_errors { |
| const char kErrorInvalidMatchPattern[] = "Invalid match pattern '*' (*)"; |
| const char kErrorInvalidId[] = "Invalid ID '*'"; |
| const char kErrorNothingSpecified[] = |
| "'externally_connectable' specifies neither 'matches' nor 'ids'; " |
| "nothing will be able to connect"; |
| const char kErrorUnusedAcceptsTlsChannelId[] = |
| "'externally_connectable' specifies 'accepts_tls_channel_id' but not " |
| "'matches'; 'accepts_tls_channel_id' has no efect"; |
| } // namespace externally_connectable_errors |
| |
| namespace keys = extensions::manifest_keys; |
| using api::extensions_manifest_types::ExternallyConnectable; |
| |
| namespace { |
| |
| const char kAllIds[] = "*"; |
| |
| template <typename T> |
| std::vector<T> Sorted(const std::vector<T>& in) { |
| std::vector<T> out = in; |
| std::sort(out.begin(), out.end()); |
| return out; |
| } |
| |
| } // namespace |
| |
| ExternallyConnectableHandler::ExternallyConnectableHandler() = default; |
| |
| ExternallyConnectableHandler::~ExternallyConnectableHandler() = default; |
| |
| bool ExternallyConnectableHandler::Parse(Extension* extension, |
| std::u16string* error) { |
| const base::Value* externally_connectable = |
| extension->manifest()->FindPath(keys::kExternallyConnectable); |
| CHECK(externally_connectable != nullptr); |
| |
| std::vector<InstallWarning> install_warnings; |
| std::unique_ptr<ExternallyConnectableInfo> info = |
| ExternallyConnectableInfo::FromValue(*externally_connectable, |
| &install_warnings, error); |
| if (!info) { |
| return false; |
| } |
| |
| extension->AddInstallWarnings(std::move(install_warnings)); |
| extension->SetManifestData(keys::kExternallyConnectable, std::move(info)); |
| return true; |
| } |
| |
| base::span<const char* const> ExternallyConnectableHandler::Keys() const { |
| static constexpr const char* kKeys[] = {keys::kExternallyConnectable}; |
| return kKeys; |
| } |
| |
| // static |
| ExternallyConnectableInfo* ExternallyConnectableInfo::Get( |
| const Extension* extension) { |
| return static_cast<ExternallyConnectableInfo*>( |
| extension->GetManifestData(keys::kExternallyConnectable)); |
| } |
| |
| // static |
| std::unique_ptr<ExternallyConnectableInfo> ExternallyConnectableInfo::FromValue( |
| const base::Value& value, |
| std::vector<InstallWarning>* install_warnings, |
| std::u16string* error) { |
| auto externally_connectable = ExternallyConnectable::FromValue(value); |
| if (!externally_connectable.has_value()) { |
| *error = std::move(externally_connectable).error(); |
| return nullptr; |
| } |
| |
| URLPatternSet matches; |
| |
| if (externally_connectable->matches) { |
| for (auto it = externally_connectable->matches->begin(); |
| it != externally_connectable->matches->end(); ++it) { |
| // Safe to use SCHEME_ALL here; externally_connectable gives a page -> |
| // extension communication path, not the other way. |
| URLPattern pattern(URLPattern::SCHEME_ALL); |
| auto parse_result = pattern.Parse(*it); |
| if (parse_result != URLPattern::ParseResult::kSuccess) { |
| *error = ErrorUtils::FormatErrorMessageUTF16( |
| externally_connectable_errors::kErrorInvalidMatchPattern, *it, |
| URLPattern::GetParseResultString(parse_result)); |
| return nullptr; |
| } |
| |
| matches.AddPattern(pattern); |
| } |
| } |
| |
| std::vector<ExtensionId> ids; |
| bool all_ids = false; |
| |
| if (externally_connectable->ids) { |
| for (const auto& id : *externally_connectable->ids) { |
| if (id == kAllIds) { |
| all_ids = true; |
| } else if (crx_file::id_util::IdIsValid(id)) { |
| ids.push_back(id); |
| } else { |
| *error = ErrorUtils::FormatErrorMessageUTF16( |
| externally_connectable_errors::kErrorInvalidId, id); |
| return nullptr; |
| } |
| } |
| } |
| |
| if (!all_ids && matches.is_empty() && ids.empty()) { |
| install_warnings->emplace_back( |
| externally_connectable_errors::kErrorNothingSpecified, |
| keys::kExternallyConnectable); |
| } |
| |
| bool accepts_tls_channel_id = |
| externally_connectable->accepts_tls_channel_id.value_or(false); |
| |
| if (externally_connectable->accepts_tls_channel_id && matches.is_empty()) { |
| accepts_tls_channel_id = false; |
| install_warnings->emplace_back( |
| externally_connectable_errors::kErrorUnusedAcceptsTlsChannelId, |
| keys::kExternallyConnectable); |
| } |
| |
| return base::WrapUnique(new ExternallyConnectableInfo( |
| std::move(matches), ids, all_ids, accepts_tls_channel_id)); |
| } |
| |
| ExternallyConnectableInfo::~ExternallyConnectableInfo() = default; |
| |
| ExternallyConnectableInfo::ExternallyConnectableInfo( |
| URLPatternSet matches, |
| const std::vector<ExtensionId>& ids, |
| bool all_ids, |
| bool accepts_tls_channel_id) |
| : matches(std::move(matches)), |
| ids(Sorted(ids)), |
| all_ids(all_ids), |
| accepts_tls_channel_id(accepts_tls_channel_id) {} |
| |
| bool ExternallyConnectableInfo::IdCanConnect(const ExtensionId& id) { |
| if (all_ids) { |
| return true; |
| } |
| DCHECK(std::ranges::is_sorted(ids)); |
| return std::binary_search(ids.begin(), ids.end(), id); |
| } |
| |
| } // namespace extensions |