1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/supervised_user/supervised_user_site_list.h"
7 #include "base/files/file_util.h"
8 #include "base/json/json_file_value_serializer.h"
9 #include "base/logging.h"
10 #include "base/metrics/histogram_macros.h"
11 #include "base/task_runner_util.h"
12 #include "base/values.h"
13 #include "components/safe_json/safe_json_parser.h"
14 #include "content/public/browser/browser_thread.h"
17 const int kSitelistFormatVersion
= 1;
19 const char kHostnameHashesKey
[] = "hostname_hashes";
20 const char kNameKey
[] = "name";
21 const char kSitesKey
[] = "sites";
22 const char kSitelistFormatVersionKey
[] = "version";
23 const char kUrlKey
[] = "url";
24 const char kWhitelistKey
[] = "whitelist";
28 std::string
ReadFileOnBlockingThread(const base::FilePath
& path
) {
29 SCOPED_UMA_HISTOGRAM_TIMER("ManagedUsers.Whitelist.ReadDuration");
31 base::ReadFileToString(path
, &contents
);
35 void HandleError(const base::FilePath
& path
, const std::string
& error
) {
36 LOG(ERROR
) << "Couldn't load site list " << path
.value() << ": " << error
;
39 // Takes a DictionaryValue entry from the JSON file and fills the whitelist
40 // (via URL patterns or hostname hashes) and the URL in the corresponding Site
42 void AddWhitelistEntries(const base::DictionaryValue
* site_dict
,
43 SupervisedUserSiteList::Site
* site
) {
46 const base::ListValue
* whitelist
= nullptr;
47 if (site_dict
->GetList(kWhitelistKey
, &whitelist
)) {
49 for (const base::Value
* entry
: *whitelist
) {
51 if (!entry
->GetAsString(&pattern
)) {
52 LOG(ERROR
) << "Invalid whitelist entry";
56 site
->patterns
.push_back(pattern
);
60 const base::ListValue
* hash_list
= nullptr;
61 if (site_dict
->GetList(kHostnameHashesKey
, &hash_list
)) {
63 for (const base::Value
* entry
: *hash_list
) {
65 if (!entry
->GetAsString(&hash
)) {
66 LOG(ERROR
) << "Invalid hostname_hashes entry";
69 // TODO(treib): Check that |hash| has exactly 40 (2*base::kSHA1Length)
70 // characters from [0-9a-fA-F]. Or just store the raw bytes (from
71 // base::HexStringToBytes).
73 site
->hostname_hashes
.push_back(hash
);
80 // Fall back to using a whitelist based on the URL.
82 if (!site_dict
->GetString(kUrlKey
, &url_str
)) {
83 LOG(ERROR
) << "Whitelist is invalid";
88 if (!url
.is_valid()) {
89 LOG(ERROR
) << "URL " << url_str
<< " is invalid";
93 site
->patterns
.push_back(url
.host());
98 SupervisedUserSiteList::Site::Site(const base::string16
& name
) : name(name
) {
101 SupervisedUserSiteList::Site::~Site() {
104 void SupervisedUserSiteList::Load(const base::FilePath
& path
,
105 const LoadedCallback
& callback
) {
106 base::PostTaskAndReplyWithResult(
107 content::BrowserThread::GetBlockingPool()
108 ->GetTaskRunnerWithShutdownBehavior(
109 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN
).get(),
111 base::Bind(&ReadFileOnBlockingThread
, path
),
112 base::Bind(&SupervisedUserSiteList::ParseJson
, path
, callback
));
115 SupervisedUserSiteList::SupervisedUserSiteList(const base::ListValue
& sites
) {
116 for (const base::Value
* site
: sites
) {
117 const base::DictionaryValue
* entry
= nullptr;
118 if (!site
->GetAsDictionary(&entry
)) {
119 LOG(ERROR
) << "Entry is invalid";
124 entry
->GetString(kNameKey
, &name
);
125 sites_
.push_back(Site(name
));
126 AddWhitelistEntries(entry
, &sites_
.back());
130 SupervisedUserSiteList::~SupervisedUserSiteList() {
134 void SupervisedUserSiteList::ParseJson(
135 const base::FilePath
& path
,
136 const SupervisedUserSiteList::LoadedCallback
& callback
,
137 const std::string
& json
) {
138 // TODO(bauerb): Use JSONSanitizer to sanitize whitelists on installation
139 // instead of using the expensive SafeJsonParser on every load.
140 safe_json::SafeJsonParser::Parse(
141 json
, base::Bind(&SupervisedUserSiteList::OnJsonParseSucceeded
, path
,
142 base::TimeTicks::Now(), callback
),
143 base::Bind(&HandleError
, path
));
147 void SupervisedUserSiteList::OnJsonParseSucceeded(
148 const base::FilePath
& path
,
149 base::TimeTicks start_time
,
150 const SupervisedUserSiteList::LoadedCallback
& callback
,
151 scoped_ptr
<base::Value
> value
) {
152 if (!start_time
.is_null()) {
153 UMA_HISTOGRAM_TIMES("ManagedUsers.Whitelist.JsonParseDuration",
154 base::TimeTicks::Now() - start_time
);
157 base::DictionaryValue
* dict
= nullptr;
158 if (!value
->GetAsDictionary(&dict
)) {
159 LOG(ERROR
) << "Site list " << path
.value() << " is invalid";
164 if (!dict
->GetInteger(kSitelistFormatVersionKey
, &version
)) {
165 LOG(ERROR
) << "Site list " << path
.value() << " has invalid version";
169 if (version
> kSitelistFormatVersion
) {
170 LOG(ERROR
) << "Site list " << path
.value() << " has a too new format";
174 base::ListValue
* sites
= nullptr;
175 if (!dict
->GetList(kSitesKey
, &sites
)) {
176 LOG(ERROR
) << "Site list " << path
.value() << " does not contain any sites";
180 callback
.Run(make_scoped_refptr(new SupervisedUserSiteList(*sites
)));