blob: a9db83fa4de6a0808a17a3840be26fe6007cbd6b [file] [log] [blame]
// Copyright 2019 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/ruleset_source.h"
#include <memory>
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/json/json_string_value_serializer.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/timer/elapsed_timer.h"
#include "base/values.h"
#include "content/public/browser/browser_context.h"
#include "extensions/browser/api/declarative_net_request/constants.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/browser/api/declarative_net_request/utils.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/error_utils.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_resource.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 "tools/json_schema_compiler/util.h"
#include "url/gurl.h"
namespace extensions {
namespace declarative_net_request {
namespace {
namespace dnr_api = extensions::api::declarative_net_request;
using Status = ReadJSONRulesResult::Status;
// Dynamic rulesets get more priority over static rulesets.
const size_t kStaticRulesetPriority = 1;
const size_t kDynamicRulesetPriority = 2;
constexpr const char kFileDoesNotExistError[] = "File does not exist.";
constexpr const char kFileReadError[] = "File read error.";
constexpr const char kDynamicRulesetDirectory[] = "DNR Extension Rules";
constexpr const char kDynamicRulesJSONFilename[] = "rules.json";
constexpr const char kDynamicIndexedRulesFilename[] = "rules.fbs";
// Helper to retrieve the filename for the given |file_path|.
std::string GetFilename(const base::FilePath& file_path) {
return file_path.BaseName().AsUTF8Unsafe();
}
std::string GetErrorWithFilename(const base::FilePath& path,
const std::string& error) {
return base::StrCat({GetFilename(path), ": ", error});
}
InstallWarning CreateInstallWarning(const std::string& message) {
return InstallWarning(message, manifest_keys::kDeclarativeNetRequestKey,
manifest_keys::kDeclarativeRuleResourcesKey);
}
ReadJSONRulesResult ParseRulesFromJSON(const base::Value& rules,
size_t rule_limit) {
ReadJSONRulesResult result;
if (!rules.is_list()) {
return ReadJSONRulesResult::CreateErrorResult(Status::kJSONIsNotList,
kErrorListNotPassed);
}
// Limit the maximum number of rule unparsed warnings to 5.
const size_t kMaxUnparsedRulesWarnings = 5;
bool unparsed_warnings_limit_exeeded = false;
size_t unparsed_warning_count = 0;
// We don't use json_schema_compiler::util::PopulateArrayFromList since it
// fails if a single Value can't be deserialized. However we want to ignore
// values which can't be parsed to maintain backwards compatibility.
const auto& rules_list = rules.GetList();
for (size_t i = 0; i < rules_list.size(); i++) {
dnr_api::Rule parsed_rule;
base::string16 parse_error;
if (dnr_api::Rule::Populate(rules_list[i], &parsed_rule, &parse_error) &&
parse_error.empty()) {
if (result.rules.size() == rule_limit) {
result.rule_parse_warnings.push_back(
CreateInstallWarning(kRuleCountExceeded));
break;
}
result.rules.push_back(std::move(parsed_rule));
continue;
}
if (unparsed_warning_count == kMaxUnparsedRulesWarnings) {
// Don't add the warning for the current rule.
unparsed_warnings_limit_exeeded = true;
continue;
}
++unparsed_warning_count;
std::string rule_location;
// If possible use the rule ID in the install warning.
if (auto* id_val =
rules_list[i].FindKeyOfType(kIDKey, base::Value::Type::INTEGER)) {
rule_location = base::StringPrintf("id %d", id_val->GetInt());
} else {
// Use one-based indices.
rule_location = base::StringPrintf("index %zu", i + 1);
}
result.rule_parse_warnings.push_back(CreateInstallWarning(
ErrorUtils::FormatErrorMessage(kRuleNotParsedWarning, rule_location,
base::UTF16ToUTF8(parse_error))));
}
DCHECK_LE(result.rules.size(), rule_limit);
if (unparsed_warnings_limit_exeeded) {
result.rule_parse_warnings.push_back(
CreateInstallWarning(ErrorUtils::FormatErrorMessage(
kTooManyParseFailuresWarning,
std::to_string(kMaxUnparsedRulesWarnings))));
}
return result;
}
void OnSafeJSONParserSuccess(
const RulesetSource& source,
RulesetSource::IndexAndPersistJSONRulesetCallback callback,
base::Value root) {
base::ElapsedTimer timer;
ReadJSONRulesResult result =
ParseRulesFromJSON(root, source.rule_count_limit());
if (result.status != Status::kSuccess) {
std::move(callback).Run(IndexAndPersistJSONRulesetResult::CreateErrorResult(
GetErrorWithFilename(source.json_path(), result.error)));
return;
}
int ruleset_checksum;
size_t rules_count = result.rules.size();
const ParseInfo info =
source.IndexAndPersistRules(std::move(result.rules), &ruleset_checksum);
if (info.result() == ParseResult::SUCCESS) {
std::move(callback).Run(
IndexAndPersistJSONRulesetResult::CreateSuccessResult(
ruleset_checksum, std::move(result.rule_parse_warnings),
rules_count, timer.Elapsed()));
return;
}
std::string error =
GetErrorWithFilename(source.json_path(), info.GetErrorDescription());
std::move(callback).Run(
IndexAndPersistJSONRulesetResult::CreateErrorResult(std::move(error)));
}
void OnSafeJSONParserError(
RulesetSource::IndexAndPersistJSONRulesetCallback callback,
const base::FilePath& json_path,
const std::string& json_parse_error) {
std::move(callback).Run(IndexAndPersistJSONRulesetResult::CreateErrorResult(
GetErrorWithFilename(json_path, json_parse_error)));
}
} // namespace
// static
IndexAndPersistJSONRulesetResult
IndexAndPersistJSONRulesetResult::CreateSuccessResult(
int ruleset_checksum,
std::vector<InstallWarning> warnings,
size_t rules_count,
base::TimeDelta index_and_persist_time) {
IndexAndPersistJSONRulesetResult result;
result.success = true;
result.ruleset_checksum = ruleset_checksum;
result.warnings = std::move(warnings);
result.rules_count = rules_count;
result.index_and_persist_time = index_and_persist_time;
return result;
}
// static
IndexAndPersistJSONRulesetResult
IndexAndPersistJSONRulesetResult::CreateErrorResult(std::string error) {
IndexAndPersistJSONRulesetResult result;
result.success = false;
result.error = std::move(error);
return result;
}
IndexAndPersistJSONRulesetResult::~IndexAndPersistJSONRulesetResult() = default;
IndexAndPersistJSONRulesetResult::IndexAndPersistJSONRulesetResult(
IndexAndPersistJSONRulesetResult&&) = default;
IndexAndPersistJSONRulesetResult& IndexAndPersistJSONRulesetResult::operator=(
IndexAndPersistJSONRulesetResult&&) = default;
IndexAndPersistJSONRulesetResult::IndexAndPersistJSONRulesetResult() = default;
// static
ReadJSONRulesResult ReadJSONRulesResult::CreateErrorResult(Status status,
std::string error) {
ReadJSONRulesResult result;
result.status = status;
result.error = std::move(error);
return result;
}
ReadJSONRulesResult::ReadJSONRulesResult() = default;
ReadJSONRulesResult::~ReadJSONRulesResult() = default;
ReadJSONRulesResult::ReadJSONRulesResult(ReadJSONRulesResult&&) = default;
ReadJSONRulesResult& ReadJSONRulesResult::operator=(ReadJSONRulesResult&&) =
default;
// static
const size_t RulesetSource::kStaticRulesetID = 1;
const size_t RulesetSource::kDynamicRulesetID = 2;
// static
RulesetSource RulesetSource::CreateStatic(const Extension& extension) {
return RulesetSource(
declarative_net_request::DNRManifestData::GetRulesetPath(extension),
file_util::GetIndexedRulesetPath(extension.path()), kStaticRulesetID,
kStaticRulesetPriority, dnr_api::MAX_NUMBER_OF_RULES, extension.id());
}
// static
RulesetSource RulesetSource::CreateDynamic(content::BrowserContext* context,
const Extension& extension) {
base::FilePath dynamic_ruleset_directory =
context->GetPath()
.AppendASCII(kDynamicRulesetDirectory)
.AppendASCII(extension.id());
return RulesetSource(
dynamic_ruleset_directory.AppendASCII(kDynamicRulesJSONFilename),
dynamic_ruleset_directory.AppendASCII(kDynamicIndexedRulesFilename),
kDynamicRulesetID, kDynamicRulesetPriority,
dnr_api::MAX_NUMBER_OF_DYNAMIC_RULES, extension.id());
}
// static
std::unique_ptr<RulesetSource> RulesetSource::CreateTemporarySource(
size_t id,
size_t priority,
size_t rule_count_limit,
ExtensionId extension_id) {
base::FilePath temporary_file_indexed;
base::FilePath temporary_file_json;
if (!base::CreateTemporaryFile(&temporary_file_indexed) ||
!base::CreateTemporaryFile(&temporary_file_json)) {
return nullptr;
}
return std::make_unique<RulesetSource>(
std::move(temporary_file_json), std::move(temporary_file_indexed), id,
priority, rule_count_limit, std::move(extension_id));
}
RulesetSource::RulesetSource(base::FilePath json_path,
base::FilePath indexed_path,
size_t id,
size_t priority,
size_t rule_count_limit,
ExtensionId extension_id)
: json_path_(std::move(json_path)),
indexed_path_(std::move(indexed_path)),
id_(id),
priority_(priority),
rule_count_limit_(rule_count_limit),
extension_id_(std::move(extension_id)) {}
RulesetSource::~RulesetSource() = default;
RulesetSource::RulesetSource(RulesetSource&&) = default;
RulesetSource& RulesetSource::operator=(RulesetSource&&) = default;
RulesetSource RulesetSource::Clone() const {
return RulesetSource(json_path_, indexed_path_, id_, priority_,
rule_count_limit_, extension_id_);
}
IndexAndPersistJSONRulesetResult
RulesetSource::IndexAndPersistJSONRulesetUnsafe() const {
DCHECK(IsAPIAvailable());
base::ElapsedTimer timer;
ReadJSONRulesResult result = ReadJSONRulesUnsafe();
if (result.status != Status::kSuccess) {
return IndexAndPersistJSONRulesetResult::CreateErrorResult(
GetErrorWithFilename(json_path_, result.error));
}
int ruleset_checksum;
size_t rules_count = result.rules.size();
const ParseInfo info =
IndexAndPersistRules(std::move(result.rules), &ruleset_checksum);
if (info.result() == ParseResult::SUCCESS) {
return IndexAndPersistJSONRulesetResult::CreateSuccessResult(
ruleset_checksum, std::move(result.rule_parse_warnings), rules_count,
timer.Elapsed());
}
std::string error =
GetErrorWithFilename(json_path_, info.GetErrorDescription());
return IndexAndPersistJSONRulesetResult::CreateErrorResult(std::move(error));
}
void RulesetSource::IndexAndPersistJSONRuleset(
service_manager::Connector* connector,
const base::Optional<base::Token>& decoder_batch_id,
IndexAndPersistJSONRulesetCallback callback) const {
DCHECK(IsAPIAvailable());
if (!base::PathExists(json_path_)) {
std::move(callback).Run(IndexAndPersistJSONRulesetResult::CreateErrorResult(
GetErrorWithFilename(json_path_, kFileDoesNotExistError)));
return;
}
std::string json_contents;
if (!base::ReadFileToString(json_path_, &json_contents)) {
std::move(callback).Run(IndexAndPersistJSONRulesetResult::CreateErrorResult(
GetErrorWithFilename(json_path_, kFileReadError)));
return;
}
// TODO(crbug.com/730593): Remove AdaptCallbackForRepeating() by updating
// the callee interface.
auto repeating_callback =
base::AdaptCallbackForRepeating(std::move(callback));
auto error_callback =
base::BindOnce(&OnSafeJSONParserError, repeating_callback, json_path_);
auto success_callback =
base::BindOnce(&OnSafeJSONParserSuccess, Clone(), repeating_callback);
if (decoder_batch_id) {
data_decoder::SafeJsonParser::ParseBatch(
connector, json_contents, std::move(success_callback),
std::move(error_callback), *decoder_batch_id);
} else {
data_decoder::SafeJsonParser::Parse(connector, json_contents,
std::move(success_callback),
std::move(error_callback));
}
}
ParseInfo RulesetSource::IndexAndPersistRules(std::vector<dnr_api::Rule> rules,
int* ruleset_checksum) const {
DCHECK_LE(rules.size(), rule_count_limit_);
DCHECK(ruleset_checksum);
DCHECK(IsAPIAvailable());
FlatRulesetIndexer indexer;
{
std::set<int> id_set; // Ensure all ids are distinct.
const GURL base_url = Extension::GetBaseURLFromExtensionId(extension_id_);
for (auto& rule : rules) {
int rule_id = rule.id;
bool inserted = id_set.insert(rule_id).second;
if (!inserted)
return ParseInfo(ParseResult::ERROR_DUPLICATE_IDS, rule_id);
IndexedRule indexed_rule;
ParseResult parse_result = IndexedRule::CreateIndexedRule(
std::move(rule), base_url, &indexed_rule);
if (parse_result != ParseResult::SUCCESS)
return ParseInfo(parse_result, rule_id);
indexer.AddUrlRule(indexed_rule);
}
}
indexer.Finish();
if (!PersistIndexedRuleset(indexed_path_, indexer.GetData(),
ruleset_checksum)) {
return ParseInfo(ParseResult::ERROR_PERSISTING_RULESET);
}
return ParseInfo(ParseResult::SUCCESS);
}
ReadJSONRulesResult RulesetSource::ReadJSONRulesUnsafe() const {
ReadJSONRulesResult result;
if (!base::PathExists(json_path_)) {
return ReadJSONRulesResult::CreateErrorResult(Status::kFileDoesNotExist,
kFileDoesNotExistError);
}
std::string json_contents;
if (!base::ReadFileToString(json_path_, &json_contents)) {
return ReadJSONRulesResult::CreateErrorResult(Status::kFileReadError,
kFileReadError);
}
base::JSONReader::ValueWithError value_with_error =
base::JSONReader::ReadAndReturnValueWithError(
json_contents, base::JSON_PARSE_RFC /* options */);
if (!value_with_error.value) {
return ReadJSONRulesResult::CreateErrorResult(
Status::kJSONParseError, std::move(value_with_error.error_message));
}
return ParseRulesFromJSON(*value_with_error.value, rule_count_limit_);
}
bool RulesetSource::WriteRulesToJSON(
const std::vector<dnr_api::Rule>& rules) const {
DCHECK_LE(rules.size(), rule_count_limit_);
std::unique_ptr<base::Value> rules_value =
json_schema_compiler::util::CreateValueFromArray(rules);
DCHECK(rules_value);
DCHECK(rules_value->is_list());
if (!base::CreateDirectory(json_path_.DirName()))
return false;
std::string json_contents;
JSONStringValueSerializer serializer(&json_contents);
serializer.set_pretty_print(false);
if (!serializer.Serialize(*rules_value))
return false;
int data_size = static_cast<int>(json_contents.size());
return base::WriteFile(json_path_, json_contents.data(), data_size) ==
data_size;
}
} // namespace declarative_net_request
} // namespace extensions