blob: 1c31968c468a99b55c0b33d47402ae5bfedbcdf0 [file] [log] [blame]
// Copyright 2017 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 "extensions/browser/api/declarative_net_request/utils.h"
#include <memory>
#include <set>
#include <utility>
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/hash.h"
#include "base/json/json_file_value_serializer.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_restrictions.h"
#include "base/timer/elapsed_timer.h"
#include "base/values.h"
#include "components/url_pattern_index/url_pattern_index.h"
#include "extensions/browser/api/declarative_net_request/constants.h"
#include "extensions/browser/api/declarative_net_request/flat/extension_ruleset_generated.h"
#include "extensions/browser/api/declarative_net_request/flat_ruleset_indexer.h"
#include "extensions/browser/api/declarative_net_request/indexed_rule.h"
#include "extensions/browser/api/declarative_net_request/parse_info.h"
#include "extensions/common/api/declarative_net_request.h"
#include "extensions/common/api/declarative_net_request/dnr_manifest_data.h"
#include "extensions/common/api/declarative_net_request/utils.h"
#include "extensions/common/file_util.h"
#include "extensions/common/install_warning.h"
#include "extensions/common/manifest_constants.h"
#include "services/data_decoder/public/cpp/safe_json_parser.h"
#include "services/service_manager/public/cpp/identity.h"
#include "third_party/flatbuffers/src/include/flatbuffers/flatbuffers.h"
namespace extensions {
namespace declarative_net_request {
namespace {
namespace dnr_api = extensions::api::declarative_net_request;
// The ruleset format version of the flatbuffer schema. Increment this whenever
// making an incompatible change to the schema at extension_ruleset.fbs or
// url_pattern_index.fbs. Whenever an extension with an indexed ruleset format
// version different from the one currently used by Chrome is loaded, the
// extension ruleset will be reindexed.
constexpr int kIndexedRulesetFormatVersion = 1;
// This static assert is meant to catch cases where
// url_pattern_index::kUrlPatternIndexFormatVersion is incremented without
// updating kIndexedRulesetFormatVersion.
static_assert(url_pattern_index::kUrlPatternIndexFormatVersion == 1,
"kUrlPatternIndexFormatVersion has changed, make sure you've "
"also updated kIndexedRulesetFormatVersion above.");
constexpr int kInvalidIndexedRulesetFormatVersion = -1;
int g_indexed_ruleset_format_version_for_testing =
kInvalidIndexedRulesetFormatVersion;
int GetIndexedRulesetFormatVersion() {
return g_indexed_ruleset_format_version_for_testing ==
kInvalidIndexedRulesetFormatVersion
? kIndexedRulesetFormatVersion
: g_indexed_ruleset_format_version_for_testing;
}
// Returns the header to be used for indexed rulesets. This depends on the
// current ruleset format version.
std::string GetVersionHeader() {
return base::StringPrintf("---------Version=%d",
GetIndexedRulesetFormatVersion());
}
// Returns the checksum of the given serialized |data|. |data| must not include
// the version header.
int GetChecksum(base::span<const uint8_t> data) {
uint32_t hash = base::PersistentHash(data.data(), data.size());
// Strip off the sign bit since this needs to be persisted in preferences
// which don't support unsigned ints.
return static_cast<int>(hash & 0x7fffffff);
}
// Helper function to persist the indexed ruleset |data| for |extension|. The
// ruleset is composed of a version header corresponding to the current ruleset
// format version, followed by the actual ruleset data. Note: The checksum only
// corresponds to this ruleset data and does not include the version header.
bool PersistRuleset(const Extension& extension,
base::span<const uint8_t> data,
int* ruleset_checksum) {
DCHECK(ruleset_checksum);
const base::FilePath path =
file_util::GetIndexedRulesetPath(extension.path());
// Create the directory corresponding to |path| if it does not exist.
if (!base::CreateDirectory(path.DirName()))
return false;
base::File ruleset_file(
path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
if (!ruleset_file.IsValid())
return false;
// Write the version header.
std::string version_header = GetVersionHeader();
int version_header_size = static_cast<int>(version_header.size());
if (ruleset_file.WriteAtCurrentPos(
version_header.data(), version_header_size) != version_header_size) {
return false;
}
// Write the flatbuffer ruleset.
if (!base::IsValueInRangeForNumericType<int>(data.size()))
return false;
int data_size = static_cast<int>(data.size());
if (ruleset_file.WriteAtCurrentPos(reinterpret_cast<const char*>(data.data()),
data_size) != data_size) {
return false;
}
*ruleset_checksum = GetChecksum(data);
return true;
}
// Helper to retrieve the ruleset ExtensionResource for |extension|.
const ExtensionResource* GetRulesetResource(const Extension& extension) {
return declarative_net_request::DNRManifestData::GetRulesetResource(
&extension);
}
// Helper to retrieve the filename of the JSON ruleset provided by |extension|.
std::string GetJSONRulesetFilename(const Extension& extension) {
base::AssertBlockingAllowed();
return GetRulesetResource(extension)->GetFilePath().BaseName().AsUTF8Unsafe();
}
// Helper function to index |rules| and persist them to the
// |indexed_ruleset_path|.
ParseInfo IndexAndPersistRulesImpl(const base::Value& rules,
const Extension& extension,
std::vector<InstallWarning>* warnings,
int* ruleset_checksum) {
base::AssertBlockingAllowed();
DCHECK(warnings);
DCHECK(ruleset_checksum);
if (!rules.is_list())
return ParseInfo(ParseResult::ERROR_LIST_NOT_PASSED);
FlatRulesetIndexer indexer;
bool all_rules_parsed = true;
base::ElapsedTimer timer;
{
std::set<int> id_set; // Ensure all ids are distinct.
std::unique_ptr<dnr_api::Rule> parsed_rule;
const auto& rules_list = rules.GetList();
for (size_t i = 0; i < rules_list.size(); i++) {
parsed_rule = dnr_api::Rule::FromValue(rules_list[i]);
// Ignore rules which can't be successfully parsed and show an install
// warning for them.
if (!parsed_rule) {
all_rules_parsed = false;
continue;
}
bool inserted = id_set.insert(parsed_rule->id).second;
if (!inserted)
return ParseInfo(ParseResult::ERROR_DUPLICATE_IDS, i);
IndexedRule indexed_rule;
ParseResult parse_result =
IndexedRule::CreateIndexedRule(std::move(parsed_rule), &indexed_rule);
if (parse_result != ParseResult::SUCCESS)
return ParseInfo(parse_result, i);
indexer.AddUrlRule(indexed_rule);
}
}
indexer.Finish();
UMA_HISTOGRAM_TIMES(kIndexRulesTimeHistogram, timer.Elapsed());
if (!PersistRuleset(extension, indexer.GetData(), ruleset_checksum))
return ParseInfo(ParseResult::ERROR_PERSISTING_RULESET);
if (!all_rules_parsed) {
warnings->push_back(InstallWarning(
kRulesNotParsedWarning, manifest_keys::kDeclarativeNetRequestKey,
manifest_keys::kDeclarativeRuleResourcesKey));
}
UMA_HISTOGRAM_TIMES(kIndexAndPersistRulesTimeHistogram, timer.Elapsed());
UMA_HISTOGRAM_COUNTS_100000(kManifestRulesCountHistogram,
indexer.indexed_rules_count());
return ParseInfo(ParseResult::SUCCESS);
}
void OnSafeJSONParserSuccess(const Extension* extension,
IndexAndPersistRulesCallback callback,
std::unique_ptr<base::Value> root) {
DCHECK(root);
std::vector<InstallWarning> warnings;
int ruleset_checksum;
const ParseInfo info =
IndexAndPersistRulesImpl(*root, *extension, &warnings, &ruleset_checksum);
if (info.result() == ParseResult::SUCCESS) {
std::move(callback).Run(IndexAndPersistRulesResult::CreateSuccessResult(
ruleset_checksum, std::move(warnings)));
return;
}
std::string error =
info.GetErrorDescription(GetJSONRulesetFilename(*extension));
std::move(callback).Run(
IndexAndPersistRulesResult::CreateErrorResult(std::move(error)));
}
void OnSafeJSONParserError(IndexAndPersistRulesCallback callback,
const std::string& error) {
std::move(callback).Run(IndexAndPersistRulesResult::CreateErrorResult(error));
}
} // namespace
// static
IndexAndPersistRulesResult IndexAndPersistRulesResult::CreateSuccessResult(
int ruleset_checksum,
std::vector<InstallWarning> warnings) {
IndexAndPersistRulesResult result;
result.success = true;
result.ruleset_checksum = ruleset_checksum;
result.warnings = std::move(warnings);
return result;
}
// static
IndexAndPersistRulesResult IndexAndPersistRulesResult::CreateErrorResult(
std::string error) {
IndexAndPersistRulesResult result;
result.success = false;
result.error = std::move(error);
return result;
}
IndexAndPersistRulesResult::~IndexAndPersistRulesResult() = default;
IndexAndPersistRulesResult::IndexAndPersistRulesResult(
IndexAndPersistRulesResult&&) = default;
IndexAndPersistRulesResult& IndexAndPersistRulesResult::operator=(
IndexAndPersistRulesResult&&) = default;
IndexAndPersistRulesResult::IndexAndPersistRulesResult() = default;
IndexAndPersistRulesResult IndexAndPersistRulesUnsafe(
const Extension& extension) {
DCHECK(IsAPIAvailable());
base::AssertBlockingAllowed();
const ExtensionResource* resource = GetRulesetResource(extension);
DCHECK(resource);
JSONFileValueDeserializer deserializer(resource->GetFilePath());
std::string error;
std::unique_ptr<base::Value> root = deserializer.Deserialize(
nullptr /*error_code*/, &error /*error_message*/);
if (!root)
return IndexAndPersistRulesResult::CreateErrorResult(std::move(error));
std::vector<InstallWarning> warnings;
int ruleset_checksum;
const ParseInfo info =
IndexAndPersistRulesImpl(*root, extension, &warnings, &ruleset_checksum);
if (info.result() == ParseResult::SUCCESS) {
return IndexAndPersistRulesResult::CreateSuccessResult(ruleset_checksum,
std::move(warnings));
}
error = info.GetErrorDescription(GetJSONRulesetFilename(extension));
return IndexAndPersistRulesResult::CreateErrorResult(std::move(error));
}
void IndexAndPersistRules(service_manager::Connector* connector,
service_manager::Identity* identity,
const Extension& extension,
IndexAndPersistRulesCallback callback) {
DCHECK(IsAPIAvailable());
base::AssertBlockingAllowed();
const ExtensionResource* resource = GetRulesetResource(extension);
DCHECK(resource);
std::string json_contents;
if (!base::ReadFileToString(resource->GetFilePath(), &json_contents)) {
std::move(callback).Run(IndexAndPersistRulesResult::CreateErrorResult(
manifest_errors::kDeclarativeNetRequestJSONRulesFileReadError));
return;
}
// TODO(crbug.com/730593): Remove AdaptCallbackForRepeating() by updating
// the callee interface.
auto repeating_callback =
base::AdaptCallbackForRepeating(std::move(callback));
auto success_callback =
base::BindRepeating(&OnSafeJSONParserSuccess,
base::RetainedRef(&extension), repeating_callback);
auto error_callback =
base::BindRepeating(&OnSafeJSONParserError, repeating_callback);
if (identity) {
data_decoder::SafeJsonParser::ParseBatch(connector, json_contents,
success_callback, error_callback,
identity->instance());
} else {
data_decoder::SafeJsonParser::Parse(connector, json_contents,
success_callback, error_callback);
}
}
bool IsValidRulesetData(base::span<const uint8_t> data, int expected_checksum) {
flatbuffers::Verifier verifier(data.data(), data.size());
return expected_checksum == GetChecksum(data) &&
flat::VerifyExtensionIndexedRulesetBuffer(verifier);
}
std::string GetVersionHeaderForTesting() {
return GetVersionHeader();
}
int GetIndexedRulesetFormatVersionForTesting() {
return GetIndexedRulesetFormatVersion();
}
void SetIndexedRulesetFormatVersionForTesting(int version) {
DCHECK_NE(kInvalidIndexedRulesetFormatVersion, version);
g_indexed_ruleset_format_version_for_testing = version;
}
bool StripVersionHeaderAndParseVersion(std::string* ruleset_data) {
DCHECK(ruleset_data);
const std::string version_header = GetVersionHeader();
if (!base::StartsWith(*ruleset_data, version_header,
base::CompareCase::SENSITIVE)) {
return false;
}
// Strip the header from |ruleset_data|.
ruleset_data->erase(0, version_header.size());
return true;
}
} // namespace declarative_net_request
} // namespace extensions