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