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
SpellingServiceClient()40 SpellingServiceClient::SpellingServiceClient() {
41 }
42
~SpellingServiceClient()43 SpellingServiceClient::~SpellingServiceClient() {
44 STLDeleteContainerPairPointers(spellcheck_fetchers_.begin(),
45 spellcheck_fetchers_.end());
46 }
47
RequestTextCheck(content::BrowserContext * context,ServiceType type,const base::string16 & text,const TextCheckCompleteCallback & callback)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
IsAvailable(content::BrowserContext * context,ServiceType type)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
ParseResponse(const std::string & data,std::vector<SpellCheckResult> * results)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
TextCheckCallbackData(TextCheckCompleteCallback callback,base::string16 text)226 SpellingServiceClient::TextCheckCallbackData::TextCheckCallbackData(
227 TextCheckCompleteCallback callback,
228 base::string16 text)
229 : callback(callback),
230 text(text) {
231 }
232
~TextCheckCallbackData()233 SpellingServiceClient::TextCheckCallbackData::~TextCheckCallbackData() {
234 }
235
OnURLFetchComplete(const net::URLFetcher * source)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
CreateURLFetcher(const GURL & url)256 net::URLFetcher* SpellingServiceClient::CreateURLFetcher(const GURL& url) {
257 return net::URLFetcher::Create(url, net::URLFetcher::POST, this);
258 }
259