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