Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / supervised_user / experimental / supervised_user_async_url_checker.cc
blobf2501b4516f99bb1c4e6be807ab02f3baf76f5df
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/experimental/supervised_user_async_url_checker.h"
7 #include <string>
9 #include "base/callback.h"
10 #include "base/json/json_reader.h"
11 #include "base/metrics/histogram.h"
12 #include "base/stl_util.h"
13 #include "base/strings/string_piece.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/time/time.h"
17 #include "base/values.h"
18 #include "components/google/core/browser/google_util.h"
19 #include "google_apis/google_api_keys.h"
20 #include "net/base/escape.h"
21 #include "net/base/load_flags.h"
22 #include "net/url_request/url_fetcher.h"
23 #include "net/url_request/url_request_context.h"
24 #include "url/url_constants.h"
26 using net::URLFetcher;
27 using net::URLFetcherDelegate;
28 using net::URLRequestContextGetter;
29 using net::URLRequestStatus;
31 namespace {
33 const char kApiUrl[] = "https://safesearch.googleapis.com/v1:classify";
34 const char kDataContentType[] = "application/x-www-form-urlencoded";
35 const char kDataFormat[] = "key=%s&urls=%s";
37 const size_t kDefaultCacheSize = 1000;
39 // Builds the POST data for SafeSearch API requests.
40 std::string BuildRequestData(const std::string& api_key, const GURL& url) {
41 std::string query = net::EscapeQueryParamValue(url.spec(), true);
42 return base::StringPrintf(kDataFormat, api_key.c_str(), query.c_str());
45 // Creates a URLFetcher to call the SafeSearch API for |url|.
46 scoped_ptr<net::URLFetcher> CreateFetcher(URLFetcherDelegate* delegate,
47 URLRequestContextGetter* context,
48 const std::string& api_key,
49 const GURL& url) {
50 scoped_ptr<net::URLFetcher> fetcher = URLFetcher::Create(
51 0, GURL(kApiUrl), URLFetcher::POST, delegate);
52 fetcher->SetUploadData(kDataContentType, BuildRequestData(api_key, url));
53 fetcher->SetRequestContext(context);
54 fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
55 net::LOAD_DO_NOT_SAVE_COOKIES);
56 return fetcher.Pass();
59 // Parses a SafeSearch API |response| and stores the result in |is_porn|.
60 // On errors, returns false and doesn't set |is_porn|.
61 bool ParseResponse(const std::string& response, bool* is_porn) {
62 scoped_ptr<base::Value> value = base::JSONReader::Read(response);
63 const base::DictionaryValue* dict = nullptr;
64 if (!value || !value->GetAsDictionary(&dict)) {
65 DLOG(WARNING) << "ParseResponse failed to parse global dictionary";
66 return false;
68 const base::ListValue* classifications_list = nullptr;
69 if (!dict->GetList("classifications", &classifications_list)) {
70 DLOG(WARNING) << "ParseResponse failed to parse classifications list";
71 return false;
73 if (classifications_list->GetSize() != 1) {
74 DLOG(WARNING) << "ParseResponse expected exactly one result";
75 return false;
77 const base::DictionaryValue* classification_dict = nullptr;
78 if (!classifications_list->GetDictionary(0, &classification_dict)) {
79 DLOG(WARNING) << "ParseResponse failed to parse classification dict";
80 return false;
82 classification_dict->GetBoolean("pornography", is_porn);
83 return true;
86 } // namespace
88 struct SupervisedUserAsyncURLChecker::Check {
89 Check(const GURL& url,
90 scoped_ptr<net::URLFetcher> fetcher,
91 const CheckCallback& callback);
92 ~Check();
94 GURL url;
95 scoped_ptr<net::URLFetcher> fetcher;
96 std::vector<CheckCallback> callbacks;
97 base::Time start_time;
100 SupervisedUserAsyncURLChecker::Check::Check(
101 const GURL& url,
102 scoped_ptr<net::URLFetcher> fetcher,
103 const CheckCallback& callback)
104 : url(url),
105 fetcher(fetcher.Pass()),
106 callbacks(1, callback),
107 start_time(base::Time::Now()) {
110 SupervisedUserAsyncURLChecker::Check::~Check() {}
112 SupervisedUserAsyncURLChecker::CheckResult::CheckResult(
113 SupervisedUserURLFilter::FilteringBehavior behavior, bool uncertain)
114 : behavior(behavior), uncertain(uncertain) {
117 SupervisedUserAsyncURLChecker::SupervisedUserAsyncURLChecker(
118 URLRequestContextGetter* context)
119 : context_(context), cache_(kDefaultCacheSize) {
122 SupervisedUserAsyncURLChecker::SupervisedUserAsyncURLChecker(
123 URLRequestContextGetter* context,
124 size_t cache_size)
125 : context_(context), cache_(cache_size) {
128 SupervisedUserAsyncURLChecker::~SupervisedUserAsyncURLChecker() {}
130 bool SupervisedUserAsyncURLChecker::CheckURL(const GURL& url,
131 const CheckCallback& callback) {
132 // TODO(treib): Hack: For now, allow all Google URLs to save search QPS. If we
133 // ever remove this, we should find a way to allow at least the NTP.
134 if (google_util::IsGoogleDomainUrl(url,
135 google_util::ALLOW_SUBDOMAIN,
136 google_util::ALLOW_NON_STANDARD_PORTS)) {
137 callback.Run(url, SupervisedUserURLFilter::ALLOW, false);
138 return true;
140 // TODO(treib): Hack: For now, allow all YouTube URLs since YouTube has its
141 // own Safety Mode anyway.
142 if (google_util::IsYoutubeDomainUrl(url,
143 google_util::ALLOW_SUBDOMAIN,
144 google_util::ALLOW_NON_STANDARD_PORTS)) {
145 callback.Run(url, SupervisedUserURLFilter::ALLOW, false);
146 return true;
149 auto cache_it = cache_.Get(url);
150 if (cache_it != cache_.end()) {
151 const CheckResult& result = cache_it->second;
152 DVLOG(1) << "Cache hit! " << url.spec() << " is "
153 << (result.behavior == SupervisedUserURLFilter::BLOCK ? "NOT" : "")
154 << " safe; certain: " << !result.uncertain;
155 callback.Run(url, result.behavior, result.uncertain);
156 return true;
159 // See if we already have a check in progress for this URL.
160 for (Check* check : checks_in_progress_) {
161 if (check->url == url) {
162 DVLOG(1) << "Adding to pending check for " << url.spec();
163 check->callbacks.push_back(callback);
164 return false;
168 DVLOG(1) << "Checking URL " << url;
169 std::string api_key = google_apis::GetSafeSitesAPIKey();
170 scoped_ptr<URLFetcher> fetcher(CreateFetcher(this, context_, api_key, url));
171 fetcher->Start();
172 checks_in_progress_.push_back(new Check(url, fetcher.Pass(), callback));
173 return false;
176 void SupervisedUserAsyncURLChecker::OnURLFetchComplete(
177 const net::URLFetcher* source) {
178 ScopedVector<Check>::iterator it = checks_in_progress_.begin();
179 while (it != checks_in_progress_.end()) {
180 if (source == (*it)->fetcher.get())
181 break;
182 ++it;
184 DCHECK(it != checks_in_progress_.end());
185 Check* check = *it;
187 const URLRequestStatus& status = source->GetStatus();
188 if (!status.is_success()) {
189 DLOG(WARNING) << "URL request failed! Letting through...";
190 for (size_t i = 0; i < check->callbacks.size(); i++)
191 check->callbacks[i].Run(check->url, SupervisedUserURLFilter::ALLOW, true);
192 checks_in_progress_.erase(it);
193 return;
196 std::string response_body;
197 source->GetResponseAsString(&response_body);
198 bool is_porn = false;
199 bool uncertain = !ParseResponse(response_body, &is_porn);
200 SupervisedUserURLFilter::FilteringBehavior behavior =
201 is_porn ? SupervisedUserURLFilter::BLOCK : SupervisedUserURLFilter::ALLOW;
203 UMA_HISTOGRAM_TIMES("ManagedUsers.SafeSitesDelay",
204 base::Time::Now() - check->start_time);
206 cache_.Put(check->url, CheckResult(behavior, uncertain));
208 for (size_t i = 0; i < check->callbacks.size(); i++)
209 check->callbacks[i].Run(check->url, behavior, uncertain);
210 checks_in_progress_.erase(it);