| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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/spellchecker/spelling_service_client.h" | |
| 6 | |
| 7 #include <stddef.h> | |
| 8 | |
| 9 #include <algorithm> | |
| 10 | |
| 11 #include "base/json/json_reader.h" | |
| 12 #include "base/json/string_escape.h" | |
| 13 #include "base/logging.h" | |
| 14 #include "base/stl_util.h" | |
| 15 #include "base/strings/string_util.h" | |
| 16 #include "base/strings/stringprintf.h" | |
| 17 #include "base/strings/utf_string_conversions.h" | |
| 18 #include "base/values.h" | |
| 19 #include "chrome/common/pref_names.h" | |
| 20 #include "chrome/common/spellcheck_common.h" | |
| 21 #include "chrome/common/spellcheck_result.h" | |
| 22 #include "components/data_use_measurement/core/data_use_user_data.h" | |
| 23 #include "components/prefs/pref_service.h" | |
| 24 #include "components/user_prefs/user_prefs.h" | |
| 25 #include "content/public/browser/browser_context.h" | |
| 26 #include "content/public/browser/storage_partition.h" | |
| 27 #include "google_apis/google_api_keys.h" | |
| 28 #include "net/base/load_flags.h" | |
| 29 #include "net/url_request/url_fetcher.h" | |
| 30 #include "url/gurl.h" | |
| 31 | |
| 32 namespace { | |
| 33 | |
| 34 // The URL for requesting spell checking and sending user feedback. | |
| 35 const char kSpellingServiceURL[] = "https://www.googleapis.com/rpc"; | |
| 36 | |
| 37 // The location of spellcheck suggestions in JSON response from spelling | |
| 38 // service. | |
| 39 const char kMisspellingsPath[] = "result.spellingCheckResponse.misspellings"; | |
| 40 | |
| 41 // The location of error messages in JSON response from spelling service. | |
| 42 const char kErrorPath[] = "error"; | |
| 43 | |
| 44 // Languages currently supported by SPELLCHECK. | |
| 45 const char* const kValidLanguages[] = {"en", "es", "fi", "da"}; | |
| 46 | |
| 47 } // namespace | |
| 48 | |
| 49 SpellingServiceClient::SpellingServiceClient() { | |
| 50 } | |
| 51 | |
| 52 SpellingServiceClient::~SpellingServiceClient() { | |
| 53 STLDeleteContainerPairPointers(spellcheck_fetchers_.begin(), | |
| 54 spellcheck_fetchers_.end()); | |
| 55 } | |
| 56 | |
| 57 bool SpellingServiceClient::RequestTextCheck( | |
| 58 content::BrowserContext* context, | |
| 59 ServiceType type, | |
| 60 const base::string16& text, | |
| 61 const TextCheckCompleteCallback& callback) { | |
| 62 DCHECK(type == SUGGEST || type == SPELLCHECK); | |
| 63 if (!context || !IsAvailable(context, type)) { | |
| 64 callback.Run(false, text, std::vector<SpellCheckResult>()); | |
| 65 return false; | |
| 66 } | |
| 67 | |
| 68 const PrefService* pref = user_prefs::UserPrefs::Get(context); | |
| 69 DCHECK(pref); | |
| 70 | |
| 71 std::string dictionary; | |
| 72 pref->GetList(prefs::kSpellCheckDictionaries)->GetString(0, &dictionary); | |
| 73 | |
| 74 std::string language_code; | |
| 75 std::string country_code; | |
| 76 chrome::spellcheck_common::GetISOLanguageCountryCodeFromLocale( | |
| 77 dictionary, &language_code, &country_code); | |
| 78 | |
| 79 // Replace typographical apostrophes with typewriter apostrophes, so that | |
| 80 // server word breaker behaves correctly. | |
| 81 const base::char16 kApostrophe = 0x27; | |
| 82 const base::char16 kRightSingleQuotationMark = 0x2019; | |
| 83 base::string16 text_copy = text; | |
| 84 std::replace(text_copy.begin(), text_copy.end(), kRightSingleQuotationMark, | |
| 85 kApostrophe); | |
| 86 | |
| 87 // Format the JSON request to be sent to the Spelling service. | |
| 88 std::string encoded_text = base::GetQuotedJSONString(text_copy); | |
| 89 | |
| 90 static const char kSpellingRequest[] = | |
| 91 "{" | |
| 92 "\"method\":\"spelling.check\"," | |
| 93 "\"apiVersion\":\"v%d\"," | |
| 94 "\"params\":{" | |
| 95 "\"text\":%s," | |
| 96 "\"language\":\"%s\"," | |
| 97 "\"originCountry\":\"%s\"," | |
| 98 "\"key\":%s" | |
| 99 "}" | |
| 100 "}"; | |
| 101 std::string api_key = base::GetQuotedJSONString(google_apis::GetAPIKey()); | |
| 102 std::string request = base::StringPrintf( | |
| 103 kSpellingRequest, | |
| 104 type, | |
| 105 encoded_text.c_str(), | |
| 106 language_code.c_str(), | |
| 107 country_code.c_str(), | |
| 108 api_key.c_str()); | |
| 109 | |
| 110 GURL url = GURL(kSpellingServiceURL); | |
| 111 net::URLFetcher* fetcher = CreateURLFetcher(url).release(); | |
| 112 data_use_measurement::DataUseUserData::AttachToFetcher( | |
| 113 fetcher, data_use_measurement::DataUseUserData::SPELL_CHECKER); | |
| 114 fetcher->SetRequestContext( | |
| 115 content::BrowserContext::GetDefaultStoragePartition(context)-> | |
| 116 GetURLRequestContext()); | |
| 117 fetcher->SetUploadData("application/json", request); | |
| 118 fetcher->SetLoadFlags( | |
| 119 net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES); | |
| 120 spellcheck_fetchers_[fetcher] = new TextCheckCallbackData(callback, text); | |
| 121 fetcher->Start(); | |
| 122 return true; | |
| 123 } | |
| 124 | |
| 125 bool SpellingServiceClient::IsAvailable( | |
| 126 content::BrowserContext* context, | |
| 127 ServiceType type) { | |
| 128 const PrefService* pref = user_prefs::UserPrefs::Get(context); | |
| 129 DCHECK(pref); | |
| 130 // If prefs don't allow spellchecking, if the context is off the record, or if | |
| 131 // multilingual spellchecking is enabled the spelling service should be | |
| 132 // unavailable. | |
| 133 if (!pref->GetBoolean(prefs::kEnableContinuousSpellcheck) || | |
| 134 !pref->GetBoolean(prefs::kSpellCheckUseSpellingService) || | |
| 135 context->IsOffTheRecord()) | |
| 136 return false; | |
| 137 | |
| 138 // If the locale for spelling has not been set, the user has not decided to | |
| 139 // use spellcheck so we don't do anything remote (suggest or spelling). | |
| 140 std::string locale; | |
| 141 pref->GetList(prefs::kSpellCheckDictionaries)->GetString(0, &locale); | |
| 142 if (locale.empty()) | |
| 143 return false; | |
| 144 | |
| 145 // Finally, if all options are available, we only enable only SUGGEST | |
| 146 // if SPELLCHECK is not available for our language because SPELLCHECK results | |
| 147 // are a superset of SUGGEST results. | |
| 148 for (const char* language : kValidLanguages) { | |
| 149 if (!locale.compare(0, 2, language)) | |
| 150 return type == SPELLCHECK; | |
| 151 } | |
| 152 | |
| 153 // Only SUGGEST is allowed. | |
| 154 return type == SUGGEST; | |
| 155 } | |
| 156 | |
| 157 bool SpellingServiceClient::ParseResponse( | |
| 158 const std::string& data, | |
| 159 std::vector<SpellCheckResult>* results) { | |
| 160 // When this JSON-RPC call finishes successfully, the Spelling service returns | |
| 161 // an JSON object listed below. | |
| 162 // * result - an envelope object representing the result from the APIARY | |
| 163 // server, which is the JSON-API front-end for the Spelling service. This | |
| 164 // object consists of the following variable: | |
| 165 // - spellingCheckResponse (SpellingCheckResponse). | |
| 166 // * SpellingCheckResponse - an object representing the result from the | |
| 167 // Spelling service. This object consists of the following variable: | |
| 168 // - misspellings (optional array of Misspelling) | |
| 169 // * Misspelling - an object representing a misspelling region and its | |
| 170 // suggestions. This object consists of the following variables: | |
| 171 // - charStart (number) - the beginning of the misspelled region; | |
| 172 // - charLength (number) - the length of the misspelled region; | |
| 173 // - suggestions (array of string) - the suggestions for the misspelling | |
| 174 // text, and; | |
| 175 // - canAutoCorrect (optional boolean) - whether we can use the first | |
| 176 // suggestion for auto-correction. | |
| 177 // For example, the Spelling service returns the following JSON when we send a | |
| 178 // spelling request for "duck goes quisk" as of 16 August, 2011. | |
| 179 // { | |
| 180 // "result": { | |
| 181 // "spellingCheckResponse": { | |
| 182 // "misspellings": [{ | |
| 183 // "charStart": 10, | |
| 184 // "charLength": 5, | |
| 185 // "suggestions": [{ "suggestion": "quack" }], | |
| 186 // "canAutoCorrect": false | |
| 187 // }] | |
| 188 // } | |
| 189 // } | |
| 190 // } | |
| 191 // If the service is not available, the Spelling service returns JSON with an | |
| 192 // error. | |
| 193 // { | |
| 194 // "error": { | |
| 195 // "code": 400, | |
| 196 // "message": "Bad Request", | |
| 197 // "data": [...] | |
| 198 // } | |
| 199 // } | |
| 200 std::unique_ptr<base::DictionaryValue> value( | |
| 201 static_cast<base::DictionaryValue*>( | |
| 202 base::JSONReader::Read(data, base::JSON_ALLOW_TRAILING_COMMAS) | |
| 203 .release())); | |
| 204 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) | |
| 205 return false; | |
| 206 | |
| 207 // Check for errors from spelling service. | |
| 208 base::DictionaryValue* error = NULL; | |
| 209 if (value->GetDictionary(kErrorPath, &error)) | |
| 210 return false; | |
| 211 | |
| 212 // Retrieve the array of Misspelling objects. When the input text does not | |
| 213 // have misspelled words, it returns an empty JSON. (In this case, its HTTP | |
| 214 // status is 200.) We just return true for this case. | |
| 215 base::ListValue* misspellings = NULL; | |
| 216 if (!value->GetList(kMisspellingsPath, &misspellings)) | |
| 217 return true; | |
| 218 | |
| 219 for (size_t i = 0; i < misspellings->GetSize(); ++i) { | |
| 220 // Retrieve the i-th misspelling region and put it to the given vector. When | |
| 221 // the Spelling service sends two or more suggestions, we read only the | |
| 222 // first one because SpellCheckResult can store only one suggestion. | |
| 223 base::DictionaryValue* misspelling = NULL; | |
| 224 if (!misspellings->GetDictionary(i, &misspelling)) | |
| 225 return false; | |
| 226 | |
| 227 int start = 0; | |
| 228 int length = 0; | |
| 229 base::ListValue* suggestions = NULL; | |
| 230 if (!misspelling->GetInteger("charStart", &start) || | |
| 231 !misspelling->GetInteger("charLength", &length) || | |
| 232 !misspelling->GetList("suggestions", &suggestions)) { | |
| 233 return false; | |
| 234 } | |
| 235 | |
| 236 base::DictionaryValue* suggestion = NULL; | |
| 237 base::string16 replacement; | |
| 238 if (!suggestions->GetDictionary(0, &suggestion) || | |
| 239 !suggestion->GetString("suggestion", &replacement)) { | |
| 240 return false; | |
| 241 } | |
| 242 SpellCheckResult result( | |
| 243 SpellCheckResult::SPELLING, start, length, replacement); | |
| 244 results->push_back(result); | |
| 245 } | |
| 246 return true; | |
| 247 } | |
| 248 | |
| 249 SpellingServiceClient::TextCheckCallbackData::TextCheckCallbackData( | |
| 250 TextCheckCompleteCallback callback, | |
| 251 base::string16 text) | |
| 252 : callback(callback), | |
| 253 text(text) { | |
| 254 } | |
| 255 | |
| 256 SpellingServiceClient::TextCheckCallbackData::~TextCheckCallbackData() { | |
| 257 } | |
| 258 | |
| 259 void SpellingServiceClient::OnURLFetchComplete( | |
| 260 const net::URLFetcher* source) { | |
| 261 DCHECK(spellcheck_fetchers_[source]); | |
| 262 std::unique_ptr<const net::URLFetcher> fetcher(source); | |
| 263 std::unique_ptr<TextCheckCallbackData> callback_data( | |
| 264 spellcheck_fetchers_[fetcher.get()]); | |
| 265 bool success = false; | |
| 266 std::vector<SpellCheckResult> results; | |
| 267 if (fetcher->GetResponseCode() / 100 == 2) { | |
| 268 std::string data; | |
| 269 fetcher->GetResponseAsString(&data); | |
| 270 success = ParseResponse(data, &results); | |
| 271 } | |
| 272 spellcheck_fetchers_.erase(fetcher.get()); | |
| 273 | |
| 274 // The callback may release the last (transitive) dependency on |this|. It | |
| 275 // MUST be the last function called. | |
| 276 callback_data->callback.Run(success, callback_data->text, results); | |
| 277 } | |
| 278 | |
| 279 std::unique_ptr<net::URLFetcher> SpellingServiceClient::CreateURLFetcher( | |
| 280 const GURL& url) { | |
| 281 return net::URLFetcher::Create(url, net::URLFetcher::POST, this); | |
| 282 } | |
| OLD | NEW |