// 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 "cloud_print/service/service_state.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "net/base/elements_upload_data_stream.h" #include "net/base/escape.h" #include "net/base/io_buffer.h" #include "net/base/load_flags.h" #include "net/base/request_priority.h" #include "net/base/upload_bytes_element_reader.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_builder.h" namespace { const char kCloudPrintJsonName[] = "cloud_print"; const char kEnabledOptionName[] = "enabled"; const char kEmailOptionName[] = "email"; const char kProxyIdOptionName[] = "proxy_id"; const char kRobotEmailOptionName[] = "robot_email"; const char kRobotTokenOptionName[] = "robot_refresh_token"; const char kAuthTokenOptionName[] = "auth_token"; const char kXmppAuthTokenOptionName[] = "xmpp_auth_token"; const char kClientLoginUrl[] = "https://www.google.com/accounts/ClientLogin"; const int64 kRequestTimeoutMs = 10 * 1000; class ServiceStateURLRequestDelegate : public net::URLRequest::Delegate { public: void OnResponseStarted(net::URLRequest* request) override { if (request->GetResponseCode() == 200) { Read(request); if (request->status().is_io_pending()) return; } request->Cancel(); } void OnReadCompleted(net::URLRequest* request, int bytes_read) override { Read(request); if (!request->status().is_io_pending()) base::MessageLoop::current()->Quit(); } const std::string& data() const { return data_; } private: void Read(net::URLRequest* request) { // Read as many bytes as are available synchronously. const int kBufSize = 100000; scoped_refptr buf(new net::IOBuffer(kBufSize)); int num_bytes = 0; while (request->Read(buf.get(), kBufSize, &num_bytes)) { data_.append(buf->data(), buf->data() + num_bytes); } } std::string data_; }; void SetNotEmptyJsonString(base::DictionaryValue* dictionary, const std::string& name, const std::string& value) { if (!value.empty()) dictionary->SetString(name, value); } } // namespace ServiceState::ServiceState() { Reset(); } ServiceState::~ServiceState() { } void ServiceState::Reset() { email_.clear(); proxy_id_.clear(); robot_email_.clear(); robot_token_.clear(); auth_token_.clear(); xmpp_auth_token_.clear(); } bool ServiceState::FromString(const std::string& json) { Reset(); scoped_ptr data(base::JSONReader::Read(json)); if (!data.get()) return false; const base::DictionaryValue* services = NULL; if (!data->GetAsDictionary(&services)) return false; const base::DictionaryValue* cloud_print = NULL; if (!services->GetDictionary(kCloudPrintJsonName, &cloud_print)) return false; bool valid_file = true; // Don't exit on fail. Collect all data for re-use by user later. if (!cloud_print->GetBoolean(kEnabledOptionName, &valid_file)) valid_file = false; cloud_print->GetString(kEmailOptionName, &email_); cloud_print->GetString(kProxyIdOptionName, &proxy_id_); cloud_print->GetString(kRobotEmailOptionName, &robot_email_); cloud_print->GetString(kRobotTokenOptionName, &robot_token_); cloud_print->GetString(kAuthTokenOptionName, &auth_token_); cloud_print->GetString(kXmppAuthTokenOptionName, &xmpp_auth_token_); return valid_file && IsValid(); } bool ServiceState::IsValid() const { if (email_.empty() || proxy_id_.empty()) return false; bool valid_robot = !robot_email_.empty() && !robot_token_.empty(); bool valid_auth = !auth_token_.empty() && !xmpp_auth_token_.empty(); return valid_robot || valid_auth; } std::string ServiceState::ToString() { scoped_ptr cloud_print(new base::DictionaryValue()); cloud_print->SetBoolean(kEnabledOptionName, true); SetNotEmptyJsonString(cloud_print.get(), kEmailOptionName, email_); SetNotEmptyJsonString(cloud_print.get(), kProxyIdOptionName, proxy_id_); SetNotEmptyJsonString(cloud_print.get(), kRobotEmailOptionName, robot_email_); SetNotEmptyJsonString(cloud_print.get(), kRobotTokenOptionName, robot_token_); SetNotEmptyJsonString(cloud_print.get(), kAuthTokenOptionName, auth_token_); SetNotEmptyJsonString(cloud_print.get(), kXmppAuthTokenOptionName, xmpp_auth_token_); base::DictionaryValue services; services.Set(kCloudPrintJsonName, cloud_print.Pass()); std::string json; base::JSONWriter::WriteWithOptions( services, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json); return json; } std::string ServiceState::LoginToGoogle(const std::string& service, const std::string& email, const std::string& password) { base::MessageLoopForIO loop; net::URLRequestContextBuilder builder; scoped_ptr context(builder.Build()); ServiceStateURLRequestDelegate fetcher_delegate; GURL url(kClientLoginUrl); std::string post_body; post_body += "accountType=GOOGLE"; post_body += "&Email=" + net::EscapeUrlEncodedData(email, true); post_body += "&Passwd=" + net::EscapeUrlEncodedData(password, true); post_body += "&source=" + net::EscapeUrlEncodedData("CP-Service", true); post_body += "&service=" + net::EscapeUrlEncodedData(service, true); scoped_ptr request(context->CreateRequest( url, net::DEFAULT_PRIORITY, &fetcher_delegate)); int load_flags = request->load_flags(); load_flags = load_flags | net::LOAD_DO_NOT_SEND_COOKIES; load_flags = load_flags | net::LOAD_DO_NOT_SAVE_COOKIES; request->SetLoadFlags(load_flags); scoped_ptr reader( net::UploadOwnedBytesElementReader::CreateWithString(post_body)); request->set_upload( net::ElementsUploadDataStream::CreateWithReader(reader.Pass(), 0)); request->SetExtraRequestHeaderByName( "Content-Type", "application/x-www-form-urlencoded", true); request->set_method("POST"); request->Start(); base::MessageLoop::current()->PostDelayedTask( FROM_HERE, base::MessageLoop::QuitClosure(), base::TimeDelta::FromMilliseconds(kRequestTimeoutMs)); base::MessageLoop::current()->Run(); const char kAuthStart[] = "Auth="; for (const base::StringPiece& line : base::SplitStringPiece(fetcher_delegate.data(), "\r\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) { if (base::StartsWith(line, kAuthStart, base::CompareCase::INSENSITIVE_ASCII)) return line.substr(arraysize(kAuthStart) - 1).as_string(); } return std::string(); } bool ServiceState::Configure(const std::string& email, const std::string& password, const std::string& proxy_id) { robot_token_.clear(); robot_email_.clear(); email_ = email; proxy_id_ = proxy_id; auth_token_ = LoginToGoogle("cloudprint", email_, password); xmpp_auth_token_ = LoginToGoogle("chromiumsync", email_, password); return IsValid(); }