blob: 1b8cc8e8db88a1ab1e51089aecfe2fbc901bdafd [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 "chrome/browser/browser_switcher/browser_switcher_service.h"
#include <algorithm>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/syslog_logging.h"
#include "chrome/browser/browser_switcher/alternative_browser_driver.h"
#include "chrome/browser/browser_switcher/browser_switcher_prefs.h"
#include "chrome/browser/browser_switcher/browser_switcher_sitelist.h"
#include "chrome/browser/browser_switcher/ieem_sitelist_parser.h"
#include "chrome/browser/profiles/profile.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/file_url_loader.h"
#include "content/public/browser/shared_cors_origin_access_list.h"
#include "content/public/browser/storage_partition.h"
#include "net/base/load_flags.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
namespace browser_switcher {
namespace {
// How long to wait after |BrowserSwitcherService| is created before initiating
// the sitelist fetch. Non-zero values are used for testing.
//
// TODO(nicolaso): get rid of this.
const base::TimeDelta kFetchSitelistDelay = base::TimeDelta();
// How long to wait after a fetch to re-fetch the sitelist to keep it fresh.
const base::TimeDelta kRefreshSitelistDelay = base::Minutes(30);
// How many times to re-try fetching the XML file for the sitelist.
const int kFetchNumRetries = 1;
constexpr net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("browser_switcher_ieem_sitelist", R"(
semantics {
sender: "Legacy Browser Support "
description:
"Legacy Browser Support may download Internet Explorer's "
"Enterprise Mode SiteList XML, to load the list of URLs to open in "
"an alternative browser. This is often on the organization's "
"intranet. For more information on Internet Explorer's Enterprise "
"Mode, see: "
"https://docs.microsoft.com/internet-explorer/ie11-deploy-guide"
"/what-is-enterprise-mode"
trigger:
"1 minute after browser startup, and then refreshes every 30 "
"minutes afterwards. Only happens if Legacy Browser Support is "
"enabled via enterprise policies."
data:
"Up to 3 (plus retries) HTTP or HTTPS GET requests to the URLs "
"configured in Internet Explorer's SiteList policy, and Chrome's "
"BrowserSwitcherExternalSitelistUrl and "
"BrowserSwitcherExternalGreylistUrl policies."
destination: OTHER
destination_other:
"URL configured in Internet Explorer's SiteList policy, and URLs "
"configured in Chrome's BrowserSwitcherExternalSitelistUrl and "
"BrowserSwitcherExternalGreylistUrl policies."
}
policy {
cookies_allowed: NO
setting: "This feature cannot be disabled by settings."
chrome_policy: {
BrowserSwitcherEnabled: {
BrowserSwitcherEnabled: false
}
}
})");
} // namespace
RulesetSource::RulesetSource(
std::string pref_name_,
GURL url_,
bool contains_inverted_rules_,
base::OnceCallback<void(ParsedXml xml)> parsed_callback_)
: pref_name(std::move(pref_name_)),
url(std::move(url_)),
contains_inverted_rules(contains_inverted_rules_),
parsed_callback(std::move(parsed_callback_)) {}
RulesetSource::RulesetSource(RulesetSource&&) = default;
RulesetSource::~RulesetSource() = default;
XmlDownloader::XmlDownloader(Profile* profile,
BrowserSwitcherService* service,
base::TimeDelta first_fetch_delay,
base::RepeatingCallback<void()> all_done_callback)
: service_(service), all_done_callback_(std::move(all_done_callback)) {
file_url_factory_.Bind(
content::CreateFileURLLoaderFactory(base::FilePath(), nullptr));
other_url_factory_ = profile->GetDefaultStoragePartition()
->GetURLLoaderFactoryForBrowserProcess();
sources_ = service_->GetRulesetSources();
for (auto& source : sources_) {
if (!source.url.is_valid())
DoneParsing(&source, ParsedXml({}));
}
// Fetch in 1 minute.
ScheduleRefresh(first_fetch_delay);
}
XmlDownloader::~XmlDownloader() = default;
bool XmlDownloader::HasValidSources() const {
return std::any_of(
sources_.begin(), sources_.end(),
[](const RulesetSource& source) { return source.url.is_valid(); });
}
base::Time XmlDownloader::last_refresh_time() const {
return last_refresh_time_;
}
base::Time XmlDownloader::next_refresh_time() const {
return next_refresh_time_;
}
void XmlDownloader::FetchXml() {
for (auto& source : sources_) {
if (!source.url.is_valid()) {
DoneParsing(&source, ParsedXml({}));
continue;
}
auto request = std::make_unique<network::ResourceRequest>();
request->url = source.url;
request->load_flags = net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
request->credentials_mode = network::mojom::CredentialsMode::kInclude;
request->priority = net::IDLE;
source.url_loader = network::SimpleURLLoader::Create(std::move(request),
traffic_annotation);
source.url_loader->SetRetryOptions(
kFetchNumRetries,
network::SimpleURLLoader::RetryMode::RETRY_ON_NETWORK_CHANGE);
source.url_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
GetURLLoaderFactoryForURL(source.url),
base::BindOnce(&XmlDownloader::ParseXml, weak_ptr_factory_.GetWeakPtr(),
base::Unretained(&source)));
}
}
network::mojom::URLLoaderFactory* XmlDownloader::GetURLLoaderFactoryForURL(
const GURL& url) {
if (url.SchemeIsFile())
return file_url_factory_.get();
return other_url_factory_.get();
}
void XmlDownloader::ParseXml(RulesetSource* source,
std::unique_ptr<std::string> bytes) {
if (!bytes) {
DoneParsing(source, ParsedXml({}, "could not fetch XML"));
return;
}
ParseIeemXml(*bytes, base::BindOnce(&XmlDownloader::DoneParsing,
weak_ptr_factory_.GetWeakPtr(),
base::Unretained(source)));
}
void XmlDownloader::DoneParsing(RulesetSource* source, ParsedXml xml) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Greylists can't contain any negative rules, so remove the leading "!".
if (source->contains_inverted_rules) {
for (auto& rule : xml.rules) {
if (base::StartsWith(rule, "!", base::CompareCase::SENSITIVE))
rule.erase(0, 1);
}
}
if (xml.error)
LOG(ERROR) << *xml.error;
std::move(source->parsed_callback).Run(std::move(xml));
// Run the "all done" callback if this was the last ruleset.
counter_++;
DCHECK(counter_ <= sources_.size());
if (counter_ == sources_.size()) {
all_done_callback_.Run();
if (HasValidSources())
last_refresh_time_ = base::Time::Now();
ScheduleRefresh(service_->refresh_delay());
}
}
void XmlDownloader::ScheduleRefresh(base::TimeDelta delay) {
// Avoid doing unnecessary work.
if (!HasValidSources())
return;
// Refresh in 30 minutes, so the sitelists are never too stale.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&XmlDownloader::Refresh, weak_ptr_factory_.GetWeakPtr()),
delay);
next_refresh_time_ = base::Time::Now() + delay;
}
void XmlDownloader::Refresh() {
sources_ = service_->GetRulesetSources();
counter_ = 0;
FetchXml();
}
BrowserSwitcherService::BrowserSwitcherService(Profile* profile)
: profile_(profile),
prefs_(profile),
driver_(new AlternativeBrowserDriverImpl(&prefs_)),
sitelist_(new BrowserSwitcherSitelistImpl(&prefs_)) {
prefs_subscription_ =
prefs().RegisterPrefsChangedCallback(base::BindRepeating(
&BrowserSwitcherService::OnBrowserSwitcherPrefsChanged,
base::Unretained(this)));
if (prefs_.IsEnabled()) {
UMA_HISTOGRAM_ENUMERATION("BrowserSwitcher.AlternativeBrowser",
driver_->GetBrowserType());
}
}
BrowserSwitcherService::~BrowserSwitcherService() = default;
void BrowserSwitcherService::Init() {
LoadRulesFromPrefs();
StartDownload(fetch_delay());
}
void BrowserSwitcherService::OnAllRulesetsLoadedForTesting(
base::OnceCallback<void()> cb) {
all_rulesets_loaded_callback_for_testing_ = std::move(cb);
}
void BrowserSwitcherService::StartDownload(base::TimeDelta delay) {
// This destroys the previous XmlDownloader, which cancels any scheduled
// refresh operations.
sitelist_downloader_ = std::make_unique<XmlDownloader>(
profile_, this, delay,
base::BindRepeating(&BrowserSwitcherService::OnAllRulesetsParsed,
base::Unretained(this)));
}
void BrowserSwitcherService::Shutdown() {
prefs_.Shutdown();
}
AlternativeBrowserDriver* BrowserSwitcherService::driver() {
return driver_.get();
}
BrowserSwitcherSitelist* BrowserSwitcherService::sitelist() {
return sitelist_.get();
}
BrowserSwitcherPrefs& BrowserSwitcherService::prefs() {
return prefs_;
}
Profile* BrowserSwitcherService::profile() {
return profile_;
}
XmlDownloader* BrowserSwitcherService::sitelist_downloader() {
return sitelist_downloader_.get();
}
base::TimeDelta BrowserSwitcherService::fetch_delay() {
return fetch_delay_;
}
base::TimeDelta BrowserSwitcherService::refresh_delay() {
return refresh_delay_;
}
void BrowserSwitcherService::SetDriverForTesting(
std::unique_ptr<AlternativeBrowserDriver> driver) {
driver_ = std::move(driver);
}
void BrowserSwitcherService::SetSitelistForTesting(
std::unique_ptr<BrowserSwitcherSitelist> sitelist) {
sitelist_ = std::move(sitelist);
}
std::vector<RulesetSource> BrowserSwitcherService::GetRulesetSources() {
std::vector<RulesetSource> sources;
GURL sitelist_url = prefs_.GetExternalSitelistUrl();
sources.emplace_back(
prefs::kExternalSitelistUrl, sitelist_url, /* invert_rules */ false,
base::BindOnce(&BrowserSwitcherService::OnExternalSitelistParsed,
weak_ptr_factory_.GetWeakPtr()));
GURL greylist_url = prefs_.GetExternalGreylistUrl();
sources.emplace_back(
prefs::kExternalGreylistUrl, greylist_url, /* invert_rules */ true,
base::BindOnce(&BrowserSwitcherService::OnExternalGreylistParsed,
weak_ptr_factory_.GetWeakPtr()));
return sources;
}
void BrowserSwitcherService::LoadRulesFromPrefs() {
if (prefs().GetExternalSitelistUrl().is_valid())
sitelist()->SetExternalSitelist(
ParsedXml(prefs().GetCachedExternalSitelist(), absl::nullopt));
if (prefs().GetExternalGreylistUrl().is_valid())
sitelist()->SetExternalGreylist(
ParsedXml(prefs().GetCachedExternalGreylist(), absl::nullopt));
}
void BrowserSwitcherService::OnAllRulesetsParsed() {
callback_list_.Notify(this);
if (all_rulesets_loaded_callback_for_testing_)
std::move(all_rulesets_loaded_callback_for_testing_).Run();
}
base::CallbackListSubscription
BrowserSwitcherService::RegisterAllRulesetsParsedCallback(
AllRulesetsParsedCallback callback) {
return callback_list_.Add(callback);
}
void BrowserSwitcherService::OnBrowserSwitcherPrefsChanged(
BrowserSwitcherPrefs* prefs,
const std::vector<std::string>& changed_prefs) {
// Record |BrowserSwitcher.AlternativeBrowser| when the
// |BrowserSwitcherEnabled| or |AlternativeBrowserPath| policies change.
bool should_record_metrics =
changed_prefs.end() !=
std::find_if(changed_prefs.begin(), changed_prefs.end(),
[](const std::string& pref) {
return pref == prefs::kEnabled ||
pref == prefs::kAlternativeBrowserPath;
});
if (should_record_metrics && prefs_.IsEnabled()) {
UMA_HISTOGRAM_ENUMERATION("BrowserSwitcher.AlternativeBrowser",
driver_->GetBrowserType());
}
auto sources = GetRulesetSources();
// Re-download if one of the URLs changed. O(n^2), with n <= 3.
bool should_redownload = std::any_of(
sources.begin(), sources.end(),
[&changed_prefs](const RulesetSource& source) {
return (std::find(changed_prefs.begin(), changed_prefs.end(),
source.pref_name) != changed_prefs.end());
});
if (should_redownload)
StartDownload(fetch_delay());
}
void BrowserSwitcherService::OnExternalSitelistParsed(ParsedXml xml) {
if (xml.error) {
SYSLOG(INFO) << "Unable to parse IEEM SiteList: " << *xml.error;
} else {
VLOG(2) << "Done parsing external SiteList for sitelist rules. "
<< "Applying rules to future navigations.";
if (prefs().GetExternalSitelistUrl().is_valid())
prefs().SetCachedExternalSitelist(xml.rules);
sitelist()->SetExternalSitelist(std::move(xml));
}
}
void BrowserSwitcherService::OnExternalGreylistParsed(ParsedXml xml) {
if (xml.error) {
SYSLOG(INFO) << "Unable to parse IEEM SiteList: " << *xml.error;
} else {
VLOG(2) << "Done parsing external SiteList for greylist rules. "
<< "Applying rules to future navigations.";
if (prefs().GetExternalGreylistUrl().is_valid())
prefs().SetCachedExternalGreylist(xml.rules);
sitelist()->SetExternalGreylist(std::move(xml));
}
}
base::TimeDelta BrowserSwitcherService::fetch_delay_ = kFetchSitelistDelay;
base::TimeDelta BrowserSwitcherService::refresh_delay_ = kRefreshSitelistDelay;
// static
void BrowserSwitcherService::SetFetchDelayForTesting(base::TimeDelta delay) {
fetch_delay_ = delay;
}
// static
void BrowserSwitcherService::SetRefreshDelayForTesting(base::TimeDelta delay) {
refresh_delay_ = delay;
}
} // namespace browser_switcher