Home | History | Annotate | Download | only in spellchecker
      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