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