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