| // Copyright 2015 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 "chrome/browser/android/data_usage/data_use_matcher.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <utility> |
| |
| #include "base/memory/weak_ptr.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/time/default_tick_clock.h" |
| #include "base/time/tick_clock.h" |
| #include "base/time/time.h" |
| #include "third_party/re2/src/re2/re2.h" |
| #include "url/gurl.h" |
| |
| namespace android { |
| |
| DataUseMatcher::DataUseMatcher( |
| const base::Callback<void(const std::string&)>& |
| on_tracking_label_removed_callback, |
| const base::Callback<void(bool)>& on_matching_rules_fetched_callback, |
| const base::TimeDelta& default_matching_rule_expiration_duration) |
| : default_matching_rule_expiration_duration_( |
| default_matching_rule_expiration_duration), |
| tick_clock_(new base::DefaultTickClock()), |
| on_tracking_label_removed_callback_(on_tracking_label_removed_callback), |
| on_matching_rules_fetched_callback_(on_matching_rules_fetched_callback) { |
| DCHECK(on_tracking_label_removed_callback_); |
| DCHECK(on_matching_rules_fetched_callback_); |
| // Detach from current thread since rest of DataUseMatcher lives on the UI |
| // thread and the current thread may not be UI thread.. |
| thread_checker_.DetachFromThread(); |
| } |
| |
| DataUseMatcher::~DataUseMatcher() {} |
| |
| void DataUseMatcher::RegisterURLRegexes( |
| const std::vector<std::string>& app_package_names, |
| const std::vector<std::string>& domain_path_regexes, |
| const std::vector<std::string>& labels) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK_EQ(app_package_names.size(), domain_path_regexes.size()); |
| DCHECK_EQ(app_package_names.size(), labels.size()); |
| |
| base::hash_set<std::string> removed_matching_rule_labels; |
| uint32_t invalid_rules = 0; |
| |
| for (const auto& matching_rule : matching_rules_) |
| removed_matching_rule_labels.insert(matching_rule->label()); |
| |
| matching_rules_.clear(); |
| re2::RE2::Options options(re2::RE2::DefaultOptions); |
| options.set_case_sensitive(false); |
| |
| for (size_t i = 0; i < domain_path_regexes.size(); ++i) { |
| const std::string& url_regex = domain_path_regexes.at(i); |
| std::string app_package_name; |
| base::TimeTicks expiration; |
| const base::TimeTicks now_ticks = tick_clock_->NowTicks(); |
| |
| ParsePackageField(app_package_names.at(i), &app_package_name, &expiration); |
| if (url_regex.empty() && app_package_name.empty()) { |
| invalid_rules++; |
| continue; |
| } |
| std::unique_ptr<re2::RE2> pattern(new re2::RE2(url_regex, options)); |
| if (!pattern->ok()) { |
| invalid_rules++; |
| continue; |
| } |
| |
| if (expiration <= now_ticks) |
| continue; // skip expired matching rules. |
| DCHECK(!labels.at(i).empty()); |
| matching_rules_.push_back(std::make_unique<MatchingRule>( |
| app_package_name, std::move(pattern), labels.at(i), expiration)); |
| |
| removed_matching_rule_labels.erase(labels.at(i)); |
| } |
| |
| for (const std::string& label : removed_matching_rule_labels) |
| on_tracking_label_removed_callback_.Run(label); |
| |
| UMA_HISTOGRAM_COUNTS_100("DataUsage.MatchingRulesCount.Valid", |
| matching_rules_.size()); |
| UMA_HISTOGRAM_COUNTS_100("DataUsage.MatchingRulesCount.Invalid", |
| invalid_rules); |
| |
| on_matching_rules_fetched_callback_.Run(!matching_rules_.empty()); |
| } |
| |
| bool DataUseMatcher::MatchesURL(const GURL& url, std::string* label) const { |
| const base::TimeTicks now_ticks = tick_clock_->NowTicks(); |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| *label = ""; |
| |
| if (!url.is_valid() || url.is_empty()) |
| return false; |
| |
| for (const auto& matching_rule : matching_rules_) { |
| if (matching_rule->expiration() <= now_ticks) |
| continue; // skip expired matching rules. |
| base::TimeTicks begin = base::TimeTicks::Now(); |
| bool match = re2::RE2::FullMatch(url.spec(), *(matching_rule->pattern())); |
| UMA_HISTOGRAM_TIMES("DataUsage.Perf.URLRegexMatchDuration", |
| base::TimeTicks::Now() - begin); |
| if (match) { |
| *label = matching_rule->label(); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool DataUseMatcher::MatchesAppPackageName(const std::string& app_package_name, |
| std::string* label) const { |
| const base::TimeTicks now_ticks = tick_clock_->NowTicks(); |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| *label = ""; |
| |
| if (app_package_name.empty()) |
| return false; |
| |
| for (const auto& matching_rule : matching_rules_) { |
| if (matching_rule->expiration() <= now_ticks) |
| continue; // skip expired matching rules. |
| if (app_package_name == matching_rule->app_package_name()) { |
| *label = matching_rule->label(); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool DataUseMatcher::HasRules() const { |
| return !matching_rules_.empty(); |
| } |
| |
| bool DataUseMatcher::HasValidRuleWithLabel(const std::string& label) const { |
| for (const auto& matching_rule : matching_rules_) { |
| if (matching_rule->expiration() > tick_clock_->NowTicks() && |
| label == matching_rule->label()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void DataUseMatcher::ParsePackageField(const std::string& app_package_name, |
| std::string* new_app_package_name, |
| base::TimeTicks* expiration) const { |
| const char separator = '|'; |
| size_t index = app_package_name.find_last_of(separator); |
| uint64_t |
| expiration_milliseconds; // expiration time as milliSeconds since epoch. |
| if (index != std::string::npos && |
| base::StringToUint64(app_package_name.substr(index + 1), |
| &expiration_milliseconds)) { |
| *new_app_package_name = app_package_name.substr(0, index); |
| *expiration = base::TimeTicks::UnixEpoch() + |
| base::TimeDelta::FromMilliseconds(expiration_milliseconds); |
| return; |
| } |
| *expiration = |
| tick_clock_->NowTicks() + default_matching_rule_expiration_duration_; |
| *new_app_package_name = app_package_name; |
| } |
| |
| DataUseMatcher::MatchingRule::MatchingRule(const std::string& app_package_name, |
| std::unique_ptr<re2::RE2> pattern, |
| const std::string& label, |
| const base::TimeTicks& expiration) |
| : app_package_name_(app_package_name), |
| pattern_(std::move(pattern)), |
| label_(label), |
| expiration_(expiration) {} |
| |
| DataUseMatcher::MatchingRule::~MatchingRule() {} |
| |
| const re2::RE2* DataUseMatcher::MatchingRule::pattern() const { |
| return pattern_.get(); |
| } |
| |
| const std::string& DataUseMatcher::MatchingRule::app_package_name() const { |
| return app_package_name_; |
| } |
| |
| const std::string& DataUseMatcher::MatchingRule::label() const { |
| return label_; |
| } |
| |
| const base::TimeTicks& DataUseMatcher::MatchingRule::expiration() const { |
| return expiration_; |
| } |
| |
| } // namespace android |