// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/spellchecker/spelling_service_client.h" #include "base/command_line.h" #include "base/json/json_reader.h" #include "base/json/string_escape.h" #include "base/logging.h" #include "base/string_util.h" #include "base/stringprintf.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/pref_names.h" #include "chrome/common/spellcheck_result.h" #include "content/public/browser/browser_thread.h" #include "google_apis/google_api_keys.h" #include "net/base/load_flags.h" #include "net/url_request/url_fetcher.h" #include "unicode/uloc.h" // Use the public URL to the Spelling service on Chromium. #ifndef SPELLING_SERVICE_URL #define SPELLING_SERVICE_URL "https://www.googleapis.com/rpc" #endif SpellingServiceClient::SpellingServiceClient() : tag_(0) { } SpellingServiceClient::~SpellingServiceClient() { } bool SpellingServiceClient::RequestTextCheck( Profile* profile, int tag, ServiceType type, const string16& text, const TextCheckCompleteCallback& callback) { DCHECK(type == SUGGEST || type == SPELLCHECK); std::string locale = profile->GetPrefs()->GetString( prefs::kSpellCheckDictionary); char language[ULOC_LANG_CAPACITY] = ULOC_ENGLISH; const char* country = "USA"; if (!locale.empty()) { // Create the parameters needed by Spelling API. Spelling API needs three // parameters: ISO language code, ISO3 country code, and text to be checked // by the service. On the other hand, Chrome uses an ISO locale ID and it // may not include a country ID, e.g. "fr", "de", etc. To create the input // parameters, we convert the UI locale to a full locale ID, and convert // the full locale ID to an ISO language code and and ISO3 country code. // Also, we convert the given text to a JSON string, i.e. quote all its // non-ASCII characters. UErrorCode error = U_ZERO_ERROR; char id[ULOC_LANG_CAPACITY + ULOC_SCRIPT_CAPACITY + ULOC_COUNTRY_CAPACITY]; uloc_addLikelySubtags(locale.c_str(), id, arraysize(id), &error); error = U_ZERO_ERROR; uloc_getLanguage(id, language, arraysize(language), &error); country = uloc_getISO3Country(id); } if (!IsAvailable(profile, type)) return false; // Format the JSON request to be sent to the Spelling service. std::string encoded_text; base::JsonDoubleQuote(text, false, &encoded_text); static const char kSpellingRequest[] = "{" "\"method\":\"spelling.check\"," "\"apiVersion\":\"v%d\"," "\"params\":{" "\"text\":\"%s\"," "\"language\":\"%s\"," "\"originCountry\":\"%s\"," "\"key\":\"%s\"" "}" "}"; std::string api_key = google_apis::GetAPIKey(); std::string request = base::StringPrintf( kSpellingRequest, type, encoded_text.c_str(), language, country, api_key.c_str()); static const char kSpellingServiceURL[] = SPELLING_SERVICE_URL; GURL url = GURL(kSpellingServiceURL); fetcher_.reset(CreateURLFetcher(url)); fetcher_->SetRequestContext(profile->GetRequestContext()); fetcher_->SetUploadData("application/json", request); fetcher_->SetLoadFlags( net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES); fetcher_->Start(); tag_ = tag; text_ = text; callback_ = callback; return true; } bool SpellingServiceClient::IsAvailable(Profile* profile, ServiceType type) { const PrefService* pref = profile->GetPrefs(); if (!pref->GetBoolean(prefs::kEnableSpellCheck) || !pref->GetBoolean(prefs::kSpellCheckUseSpellingService)) return false; // The spellchecking service should be avilable only when asynchronous // spellchecking is enabled because this service depends on it. // Enable the suggest service only on languages not supported by the // spellcheck service. When this client calls the spellcheck service, it // returns not only spellcheck results but also spelling suggestions provided // by the suggest service. That is, it is not useful to use the suggest // service when this client can use the spellcheck service. std::string locale = pref->GetString(prefs::kSpellCheckDictionary); // This means we are in a test. if (locale.empty()) return false; // Otherwise only suggest is currently valid. return type == SUGGEST; #if defined(OS_MACOSX) bool spellcheck_available = locale.empty(); #else bool spellcheck_available = locale.empty() || !locale.compare(0, 2, "en"); #endif return type == SUGGEST ? !spellcheck_available : spellcheck_available; } void SpellingServiceClient::OnURLFetchComplete( const net::URLFetcher* source) { scoped_ptr clean_up_fetcher(fetcher_.release()); bool success = false; std::vector results; if (source->GetResponseCode() / 100 == 2) { std::string data; source->GetResponseAsString(&data); success = ParseResponse(data, &results); } callback_.Run(tag_, success, text_, results); } net::URLFetcher* SpellingServiceClient::CreateURLFetcher(const GURL& url) { return net::URLFetcher::Create(url, net::URLFetcher::POST, this); } bool SpellingServiceClient::ParseResponse( const std::string& data, std::vector* results) { // When this JSON-RPC call finishes successfully, the Spelling service returns // an JSON object listed below. // * result - an envelope object representing the result from the APIARY // server, which is the JSON-API front-end for the Spelling service. This // object consists of the following variable: // - spellingCheckResponse (SpellingCheckResponse). // * SpellingCheckResponse - an object representing the result from the // Spelling service. This object consists of the following variable: // - misspellings (optional array of Misspelling) // * Misspelling - an object representing a misspelling region and its // suggestions. This object consists of the following variables: // - charStart (number) - the beginning of the misspelled region; // - charLength (number) - the length of the misspelled region; // - suggestions (array of string) - the suggestions for the misspelling // text, and; // - canAutoCorrect (optional boolean) - whether we can use the first // suggestion for auto-correction. // For example, the Spelling service returns the following JSON when we send a // spelling request for "duck goes quisk" as of 16 August, 2011. // { // "result": { // "spellingCheckResponse": { // "misspellings": [{ // "charStart": 10, // "charLength": 5, // "suggestions": [{ "suggestion": "quack" }], // "canAutoCorrect": false // }] // } // } // } scoped_ptr value( static_cast( base::JSONReader::Read(data, base::JSON_ALLOW_TRAILING_COMMAS))); if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) return false; // Retrieve the array of Misspelling objects. When the input text does not // have misspelled words, it returns an empty JSON. (In this case, its HTTP // status is 200.) We just return true for this case. ListValue* misspellings = NULL; const char kMisspellings[] = "result.spellingCheckResponse.misspellings"; if (!value->GetList(kMisspellings, &misspellings)) return true; for (size_t i = 0; i < misspellings->GetSize(); ++i) { // Retrieve the i-th misspelling region and put it to the given vector. When // the Spelling service sends two or more suggestions, we read only the // first one because SpellCheckResult can store only one suggestion. DictionaryValue* misspelling = NULL; if (!misspellings->GetDictionary(i, &misspelling)) return false; int start = 0; int length = 0; ListValue* suggestions = NULL; if (!misspelling->GetInteger("charStart", &start) || !misspelling->GetInteger("charLength", &length) || !misspelling->GetList("suggestions", &suggestions)) { return false; } DictionaryValue* suggestion = NULL; string16 replacement; if (!suggestions->GetDictionary(0, &suggestion) || !suggestion->GetString("suggestion", &replacement)) { return false; } SpellCheckResult result( SpellCheckResult::SPELLING, start, length, replacement); results->push_back(result); } return true; }