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"
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
;
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
,
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";
68 const base::ListValue
* classifications_list
= nullptr;
69 if (!dict
->GetList("classifications", &classifications_list
)) {
70 DLOG(WARNING
) << "ParseResponse failed to parse classifications list";
73 if (classifications_list
->GetSize() != 1) {
74 DLOG(WARNING
) << "ParseResponse expected exactly one result";
77 const base::DictionaryValue
* classification_dict
= nullptr;
78 if (!classifications_list
->GetDictionary(0, &classification_dict
)) {
79 DLOG(WARNING
) << "ParseResponse failed to parse classification dict";
82 classification_dict
->GetBoolean("pornography", is_porn
);
88 struct SupervisedUserAsyncURLChecker::Check
{
89 Check(const GURL
& url
,
90 scoped_ptr
<net::URLFetcher
> fetcher
,
91 const CheckCallback
& callback
);
95 scoped_ptr
<net::URLFetcher
> fetcher
;
96 std::vector
<CheckCallback
> callbacks
;
97 base::Time start_time
;
100 SupervisedUserAsyncURLChecker::Check::Check(
102 scoped_ptr
<net::URLFetcher
> fetcher
,
103 const CheckCallback
& callback
)
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
,
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);
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);
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
);
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
);
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
));
172 checks_in_progress_
.push_back(new Check(url
, fetcher
.Pass(), callback
));
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())
184 DCHECK(it
!= checks_in_progress_
.end());
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
);
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
);