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 "cloud_print/service/service_state.h" 6 7 #include "base/json/json_reader.h" 8 #include "base/json/json_writer.h" 9 #include "base/logging.h" 10 #include "base/message_loop/message_loop.h" 11 #include "base/strings/string_util.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "net/base/escape.h" 14 #include "net/base/io_buffer.h" 15 #include "net/base/load_flags.h" 16 #include "net/base/request_priority.h" 17 #include "net/base/upload_bytes_element_reader.h" 18 #include "net/base/upload_data_stream.h" 19 #include "net/url_request/url_request.h" 20 #include "net/url_request/url_request_context.h" 21 #include "net/url_request/url_request_context_builder.h" 22 23 namespace { 24 25 const char kCloudPrintJsonName[] = "cloud_print"; 26 const char kEnabledOptionName[] = "enabled"; 27 28 const char kEmailOptionName[] = "email"; 29 const char kProxyIdOptionName[] = "proxy_id"; 30 const char kRobotEmailOptionName[] = "robot_email"; 31 const char kRobotTokenOptionName[] = "robot_refresh_token"; 32 const char kAuthTokenOptionName[] = "auth_token"; 33 const char kXmppAuthTokenOptionName[] = "xmpp_auth_token"; 34 35 const char kClientLoginUrl[] = "https://www.google.com/accounts/ClientLogin"; 36 37 const int64 kRequestTimeoutMs = 10 * 1000; 38 39 class ServiceStateURLRequestDelegate : public net::URLRequest::Delegate { 40 public: 41 virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE { 42 if (request->GetResponseCode() == 200) { 43 Read(request); 44 if (request->status().is_io_pending()) 45 return; 46 } 47 request->Cancel(); 48 } 49 50 virtual void OnReadCompleted(net::URLRequest* request, 51 int bytes_read) OVERRIDE { 52 Read(request); 53 if (!request->status().is_io_pending()) 54 base::MessageLoop::current()->Quit(); 55 } 56 57 const std::string& data() const { 58 return data_; 59 } 60 61 private: 62 void Read(net::URLRequest* request) { 63 // Read as many bytes as are available synchronously. 64 const int kBufSize = 100000; 65 scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kBufSize)); 66 int num_bytes = 0; 67 while (request->Read(buf.get(), kBufSize, &num_bytes)) { 68 data_.append(buf->data(), buf->data() + num_bytes); 69 } 70 } 71 std::string data_; 72 }; 73 74 75 void SetNotEmptyJsonString(base::DictionaryValue* dictionary, 76 const std::string& name, 77 const std::string& value) { 78 if (!value.empty()) 79 dictionary->SetString(name, value); 80 } 81 82 } // namespace 83 84 ServiceState::ServiceState() { 85 Reset(); 86 } 87 88 ServiceState::~ServiceState() { 89 } 90 91 void ServiceState::Reset() { 92 email_.clear(); 93 proxy_id_.clear(); 94 robot_email_.clear(); 95 robot_token_.clear(); 96 auth_token_.clear(); 97 xmpp_auth_token_.clear(); 98 } 99 100 bool ServiceState::FromString(const std::string& json) { 101 Reset(); 102 scoped_ptr<base::Value> data(base::JSONReader::Read(json)); 103 if (!data.get()) 104 return false; 105 106 const base::DictionaryValue* services = NULL; 107 if (!data->GetAsDictionary(&services)) 108 return false; 109 110 const base::DictionaryValue* cloud_print = NULL; 111 if (!services->GetDictionary(kCloudPrintJsonName, &cloud_print)) 112 return false; 113 114 bool valid_file = true; 115 // Don't exit on fail. Collect all data for re-use by user later. 116 if (!cloud_print->GetBoolean(kEnabledOptionName, &valid_file)) 117 valid_file = false; 118 119 cloud_print->GetString(kEmailOptionName, &email_); 120 cloud_print->GetString(kProxyIdOptionName, &proxy_id_); 121 cloud_print->GetString(kRobotEmailOptionName, &robot_email_); 122 cloud_print->GetString(kRobotTokenOptionName, &robot_token_); 123 cloud_print->GetString(kAuthTokenOptionName, &auth_token_); 124 cloud_print->GetString(kXmppAuthTokenOptionName, &xmpp_auth_token_); 125 126 return valid_file && IsValid(); 127 } 128 129 bool ServiceState::IsValid() const { 130 if (email_.empty() || proxy_id_.empty()) 131 return false; 132 bool valid_robot = !robot_email_.empty() && !robot_token_.empty(); 133 bool valid_auth = !auth_token_.empty() && !xmpp_auth_token_.empty(); 134 return valid_robot || valid_auth; 135 } 136 137 std::string ServiceState::ToString() { 138 scoped_ptr<base::DictionaryValue> services(new base::DictionaryValue()); 139 140 scoped_ptr<base::DictionaryValue> cloud_print(new base::DictionaryValue()); 141 cloud_print->SetBoolean(kEnabledOptionName, true); 142 143 SetNotEmptyJsonString(cloud_print.get(), kEmailOptionName, email_); 144 SetNotEmptyJsonString(cloud_print.get(), kProxyIdOptionName, proxy_id_); 145 SetNotEmptyJsonString(cloud_print.get(), kRobotEmailOptionName, robot_email_); 146 SetNotEmptyJsonString(cloud_print.get(), kRobotTokenOptionName, robot_token_); 147 SetNotEmptyJsonString(cloud_print.get(), kAuthTokenOptionName, auth_token_); 148 SetNotEmptyJsonString(cloud_print.get(), kXmppAuthTokenOptionName, 149 xmpp_auth_token_); 150 151 services->Set(kCloudPrintJsonName, cloud_print.release()); 152 153 std::string json; 154 base::JSONWriter::WriteWithOptions(services.get(), 155 base::JSONWriter::OPTIONS_PRETTY_PRINT, 156 &json); 157 return json; 158 } 159 160 std::string ServiceState::LoginToGoogle(const std::string& service, 161 const std::string& email, 162 const std::string& password) { 163 base::MessageLoopForIO loop; 164 165 net::URLRequestContextBuilder builder; 166 scoped_ptr<net::URLRequestContext> context(builder.Build()); 167 168 ServiceStateURLRequestDelegate fetcher_delegate; 169 GURL url(kClientLoginUrl); 170 171 std::string post_body; 172 post_body += "accountType=GOOGLE"; 173 post_body += "&Email=" + net::EscapeUrlEncodedData(email, true); 174 post_body += "&Passwd=" + net::EscapeUrlEncodedData(password, true); 175 post_body += "&source=" + net::EscapeUrlEncodedData("CP-Service", true); 176 post_body += "&service=" + net::EscapeUrlEncodedData(service, true); 177 178 scoped_ptr<net::URLRequest> request(context->CreateRequest( 179 url, net::DEFAULT_PRIORITY, &fetcher_delegate, NULL)); 180 int load_flags = request->load_flags(); 181 load_flags = load_flags | net::LOAD_DO_NOT_SEND_COOKIES; 182 load_flags = load_flags | net::LOAD_DO_NOT_SAVE_COOKIES; 183 request->SetLoadFlags(load_flags); 184 185 scoped_ptr<net::UploadElementReader> reader( 186 net::UploadOwnedBytesElementReader::CreateWithString(post_body)); 187 request->set_upload(make_scoped_ptr( 188 net::UploadDataStream::CreateWithReader(reader.Pass(), 0))); 189 request->SetExtraRequestHeaderByName( 190 "Content-Type", "application/x-www-form-urlencoded", true); 191 request->set_method("POST"); 192 request->Start(); 193 194 base::MessageLoop::current()->PostDelayedTask( 195 FROM_HERE, 196 base::MessageLoop::QuitClosure(), 197 base::TimeDelta::FromMilliseconds(kRequestTimeoutMs)); 198 199 base::MessageLoop::current()->Run(); 200 201 const char kAuthStart[] = "Auth="; 202 std::vector<std::string> lines; 203 Tokenize(fetcher_delegate.data(), "\r\n", &lines); 204 for (size_t i = 0; i < lines.size(); ++i) { 205 if (StartsWithASCII(lines[i], kAuthStart, false)) 206 return lines[i].substr(arraysize(kAuthStart) - 1); 207 } 208 209 return std::string(); 210 } 211 212 bool ServiceState::Configure(const std::string& email, 213 const std::string& password, 214 const std::string& proxy_id) { 215 robot_token_.clear(); 216 robot_email_.clear(); 217 email_ = email; 218 proxy_id_ = proxy_id; 219 auth_token_ = LoginToGoogle("cloudprint", email_, password); 220 xmpp_auth_token_ = LoginToGoogle("chromiumsync", email_, password); 221 return IsValid(); 222 } 223