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 |