blob: 395d863fb7691a893e65ffe7b30e794982d10020 [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 <string>
#include <utility>
#include "base/bind.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"
namespace browser_switcher {
namespace {
// How long to wait after |BrowserSwitcherService| is created before initiating
// the sitelist fetch.
const base::TimeDelta kFetchSitelistDelay = base::TimeDelta::FromSeconds(60);
// How long to wait after a fetch to re-fetch the sitelist to keep it fresh.
const base::TimeDelta kRefreshSitelistDelay = base::TimeDelta::FromMinutes(30);
// How many times to re-try fetching the XML file for the sitelist.
const int kFetchNumRetries = 1;
// TODO(nicolaso): Add chrome_policy for this annotation once the policy is
// implemented.
constexpr net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("browser_switcher_ieem_sitelist", R"(
semantics {
sender: "Browser Switcher"
description:
"BrowserSwitcher 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:
"This happens only once per profile, 60s after the first page "
"starts loading. The request may be retried once if it failed the "
"first time."
data:
"Up to 2 (plus retries) HTTP or HTTPS GET requests to the URLs "
"configured in Internet Explorer's SiteList policy, and Chrome's "
"BrowserSwitcherExternalSitelistUrl policy."
destination: OTHER
destination_other:
"URL configured in Internet Explorer's SiteList policy, and URL "
"configured in Chrome's BrowserSwitcherExternalSitelistUrl policy. "
}
policy {
cookies_allowed: NO
setting: "This feature cannot be disabled by settings."
policy_exception_justification:
"This feature still in development, and is disabled by default. "
"It needs to be enabled through policies."
})");
} // namespace
RulesetSource::RulesetSource(
GURL url_,
base::OnceCallback<void(ParsedXml xml)> parsed_callback_)
: url(std::move(url_)), parsed_callback(std::move(parsed_callback_)) {}
RulesetSource::RulesetSource(RulesetSource&&) = default;
RulesetSource::~RulesetSource() = default;
XmlDownloader::XmlDownloader(Profile* profile,
std::vector<RulesetSource> sources,
base::OnceCallback<void()> all_done_callback)
: sources_(std::move(sources)),
all_done_callback_(std::move(all_done_callback)),
weak_ptr_factory_(this) {
file_url_factory_ =
content::CreateFileURLLoaderFactory(base::FilePath(), nullptr);
other_url_factory_ =
content::BrowserContext::GetDefaultStoragePartition(profile)
->GetURLLoaderFactoryForBrowserProcess();
FetchXml();
}
XmlDownloader::~XmlDownloader() = default;
void XmlDownloader::FetchXml() {
for (auto& source : sources_) {
auto request = std::make_unique<network::ResourceRequest>();
request->url = source.url;
request->load_flags =
(net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES |
net::LOAD_DISABLE_CACHE);
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);
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())
std::move(all_done_callback_).Run();
}
BrowserSwitcherService::BrowserSwitcherService(Profile* profile)
: prefs_(profile),
driver_(new AlternativeBrowserDriverImpl(&prefs_)),
sitelist_(new BrowserSwitcherSitelistImpl(&prefs_)),
weak_ptr_factory_(this) {
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&BrowserSwitcherService::StartDownload,
weak_ptr_factory_.GetWeakPtr(), base::Unretained(profile)),
fetch_delay_);
}
BrowserSwitcherService::~BrowserSwitcherService() = default;
void BrowserSwitcherService::StartDownload(Profile* profile) {
auto sources = GetRulesetSources();
if (!sources.empty()) {
sitelist_downloader_ = std::make_unique<XmlDownloader>(
profile, std::move(sources),
base::BindOnce(&BrowserSwitcherService::OnAllRulesetsParsed,
base::Unretained(this)));
}
// Refresh in 30 minutes, so the sitelists are never too stale.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&BrowserSwitcherService::StartDownload,
weak_ptr_factory_.GetWeakPtr(), base::Unretained(profile)),
refresh_delay_);
}
void BrowserSwitcherService::Shutdown() {
prefs_.Shutdown();
}
AlternativeBrowserDriver* BrowserSwitcherService::driver() {
return driver_.get();
}
BrowserSwitcherSitelist* BrowserSwitcherService::sitelist() {
return sitelist_.get();
}
BrowserSwitcherPrefs& BrowserSwitcherService::prefs() {
return prefs_;
}
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 external_url = prefs_.GetExternalSitelistUrl();
if (!external_url.is_valid())
return sources;
sources.emplace_back(
external_url,
base::BindOnce(&BrowserSwitcherService::OnExternalSitelistParsed,
weak_ptr_factory_.GetWeakPtr()));
return sources;
}
void BrowserSwitcherService::OnAllRulesetsParsed() {
sitelist_downloader_.reset();
}
void BrowserSwitcherService::OnExternalSitelistParsed(ParsedXml xml) {
if (xml.error) {
LOG(ERROR) << "Unable to parse IEEM SiteList: " << *xml.error;
} else {
VLOG(2) << "Done parsing external SiteList. "
<< "Applying rules to future navigations.";
sitelist()->SetExternalSitelist(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;
}
void BrowserSwitcherService::SetRefreshDelayForTesting(base::TimeDelta delay) {
refresh_delay_ = delay;
}
} // namespace browser_switcher