blob: d4ab5dcf07dbd0b352b90d6809be2d340108b8c6 [file] [log] [blame]
// Copyright 2018 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/autofill_assistant/browser/script_precondition.h"
#include <utility>
#include "base/bind.h"
#include "base/strings/strcat.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/autofill_assistant/browser/batch_element_checker.h"
#include "components/autofill_assistant/browser/service.pb.h"
#include "third_party/re2/src/re2/re2.h"
#include "url/gurl.h"
namespace autofill_assistant {
// Static
std::unique_ptr<ScriptPrecondition> ScriptPrecondition::FromProto(
const std::string& script_path,
const ScriptPreconditionProto& script_precondition_proto) {
std::vector<Selector> elements_exist;
for (const auto& element : script_precondition_proto.elements_exist()) {
// TODO(crbug.com/806868): Check if we shouldn't skip the script when this
// happens.
if (element.selectors_size() == 0) {
DLOG(WARNING)
<< "Empty selectors in script precondition for script path: "
<< script_path << ".";
continue;
}
Selector a_selector;
for (const auto& selector : element.selectors()) {
a_selector.selectors.emplace_back(selector);
}
elements_exist.emplace_back(std::move(a_selector));
}
std::set<std::string> domain_match;
for (const auto& domain : script_precondition_proto.domain()) {
domain_match.emplace(domain);
}
std::vector<std::unique_ptr<re2::RE2>> path_pattern;
for (const auto& pattern : script_precondition_proto.path_pattern()) {
auto re = std::make_unique<re2::RE2>(pattern);
if (re->error_code() != re2::RE2::NoError) {
DLOG(ERROR) << "Invalid regexp in script precondition '" << pattern
<< "' for script path: " << script_path << ".";
return nullptr;
}
path_pattern.emplace_back(std::move(re));
}
std::vector<ScriptParameterMatchProto> parameter_match;
for (const auto& match : script_precondition_proto.script_parameter_match()) {
parameter_match.emplace_back(match);
}
std::vector<FormValueMatchProto> form_value_match;
for (const auto& match : script_precondition_proto.form_value_match()) {
form_value_match.emplace_back(match);
}
std::vector<ScriptStatusMatchProto> status_matches;
for (const auto& status_match :
script_precondition_proto.script_status_match()) {
status_matches.push_back(status_match);
}
// TODO(crbug.com/806868): Detect unknown or unsupported conditions and
// reject them.
return std::make_unique<ScriptPrecondition>(
elements_exist, domain_match, std::move(path_pattern), parameter_match,
form_value_match, status_matches);
}
ScriptPrecondition::~ScriptPrecondition() {}
void ScriptPrecondition::Check(
const GURL& url,
BatchElementChecker* batch_checks,
const std::map<std::string, std::string>& parameters,
const std::map<std::string, ScriptStatusProto>& executed_scripts,
base::OnceCallback<void(bool)> callback) {
if (!MatchDomain(url) || !MatchPath(url) || !MatchParameters(parameters) ||
!MatchScriptStatus(executed_scripts)) {
std::move(callback).Run(false);
return;
}
pending_check_count_ = elements_exist_.size() + form_value_match_.size();
if (pending_check_count_ == 0) {
std::move(callback).Run(true);
return;
}
check_preconditions_callback_ = std::move(callback);
for (const auto& selector : elements_exist_) {
base::OnceCallback<void(bool)> callback =
base::BindOnce(&ScriptPrecondition::OnCheckElementExists,
weak_ptr_factory_.GetWeakPtr());
batch_checks->AddElementCheck(kExistenceCheck, selector,
std::move(callback));
}
for (const auto& value_match : form_value_match_) {
DCHECK(!value_match.element().selectors().empty());
Selector a_selector;
for (const auto& selector : value_match.element().selectors()) {
a_selector.selectors.emplace_back(selector);
}
batch_checks->AddFieldValueCheck(
a_selector, base::BindOnce(&ScriptPrecondition::OnGetFieldValue,
weak_ptr_factory_.GetWeakPtr()));
}
}
ScriptPrecondition::ScriptPrecondition(
const std::vector<Selector>& elements_exist,
const std::set<std::string>& domain_match,
std::vector<std::unique_ptr<re2::RE2>> path_pattern,
const std::vector<ScriptParameterMatchProto>& parameter_match,
const std::vector<FormValueMatchProto>& form_value_match,
const std::vector<ScriptStatusMatchProto>& status_match)
: elements_exist_(elements_exist),
domain_match_(domain_match),
path_pattern_(std::move(path_pattern)),
parameter_match_(parameter_match),
form_value_match_(form_value_match),
status_match_(status_match),
pending_check_count_(0),
weak_ptr_factory_(this) {}
bool ScriptPrecondition::MatchDomain(const GURL& url) const {
if (domain_match_.empty())
return true;
// We require the scheme and host parts to match.
// TODO(crbug.com/806868): Consider using Origin::IsSameOriginWith here.
std::string scheme_domain = base::StrCat({url.scheme(), "://", url.host()});
return domain_match_.find(scheme_domain) != domain_match_.end();
}
bool ScriptPrecondition::MatchPath(const GURL& url) const {
if (path_pattern_.empty()) {
return true;
}
std::string path = url.has_ref()
? base::StrCat({url.PathForRequest(), "#", url.ref()})
: url.PathForRequest();
for (auto& regexp : path_pattern_) {
if (regexp->Match(path, 0, path.size(), re2::RE2::UNANCHORED, NULL, 0)) {
return true;
}
}
return false;
}
bool ScriptPrecondition::MatchParameters(
const std::map<std::string, std::string>& parameters) const {
for (const auto& match : parameter_match_) {
auto iter = parameters.find(match.name());
if (match.exists()) {
// parameter must exist and optionally have a specific value
if (iter == parameters.end())
return false;
if (!match.value_equals().empty() && iter->second != match.value_equals())
return false;
} else {
// parameter must not exist
if (iter != parameters.end())
return false;
}
}
return true;
}
bool ScriptPrecondition::MatchScriptStatus(
const std::map<std::string, ScriptStatusProto>& executed_scripts) const {
for (const auto status_match : status_match_) {
auto status = SCRIPT_STATUS_NOT_RUN;
auto iter = executed_scripts.find(status_match.script());
if (iter != executed_scripts.end()) {
status = iter->second;
}
bool has_same_status = status_match.status() == status;
switch (status_match.comparator()) {
case ScriptStatusMatchProto::DIFFERENT:
if (has_same_status)
return false;
break;
case ScriptStatusMatchProto::EQUAL:
default:
if (!has_same_status)
return false;
break;
}
}
return true;
}
void ScriptPrecondition::OnCheckElementExists(bool exists) {
ReportCheckResult(exists);
}
void ScriptPrecondition::OnGetFieldValue(bool exists,
const std::string& value) {
ReportCheckResult(!value.empty());
}
void ScriptPrecondition::ReportCheckResult(bool success) {
if (!check_preconditions_callback_)
return;
if (!success) {
std::move(check_preconditions_callback_).Run(false);
return;
}
--pending_check_count_;
if (pending_check_count_ <= 0) {
DCHECK_EQ(pending_check_count_, 0);
std::move(check_preconditions_callback_).Run(true);
}
}
} // namespace autofill_assistant