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.
5 #include "cloud_print/service/service_state.h"
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_split.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "net/base/elements_upload_data_stream.h"
15 #include "net/base/escape.h"
16 #include "net/base/io_buffer.h"
17 #include "net/base/load_flags.h"
18 #include "net/base/request_priority.h"
19 #include "net/base/upload_bytes_element_reader.h"
20 #include "net/url_request/url_request.h"
21 #include "net/url_request/url_request_context.h"
22 #include "net/url_request/url_request_context_builder.h"
26 const char kCloudPrintJsonName
[] = "cloud_print";
27 const char kEnabledOptionName
[] = "enabled";
29 const char kEmailOptionName
[] = "email";
30 const char kProxyIdOptionName
[] = "proxy_id";
31 const char kRobotEmailOptionName
[] = "robot_email";
32 const char kRobotTokenOptionName
[] = "robot_refresh_token";
33 const char kAuthTokenOptionName
[] = "auth_token";
34 const char kXmppAuthTokenOptionName
[] = "xmpp_auth_token";
36 const char kClientLoginUrl
[] = "https://www.google.com/accounts/ClientLogin";
38 const int64 kRequestTimeoutMs
= 10 * 1000;
40 class ServiceStateURLRequestDelegate
: public net::URLRequest::Delegate
{
42 void OnResponseStarted(net::URLRequest
* request
) override
{
43 if (request
->GetResponseCode() == 200) {
45 if (request
->status().is_io_pending())
51 void OnReadCompleted(net::URLRequest
* request
, int bytes_read
) override
{
53 if (!request
->status().is_io_pending())
54 base::MessageLoop::current()->Quit();
57 const std::string
& data() const {
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
));
67 while (request
->Read(buf
.get(), kBufSize
, &num_bytes
)) {
68 data_
.append(buf
->data(), buf
->data() + num_bytes
);
75 void SetNotEmptyJsonString(base::DictionaryValue
* dictionary
,
76 const std::string
& name
,
77 const std::string
& value
) {
79 dictionary
->SetString(name
, value
);
84 ServiceState::ServiceState() {
88 ServiceState::~ServiceState() {
91 void ServiceState::Reset() {
97 xmpp_auth_token_
.clear();
100 bool ServiceState::FromString(const std::string
& json
) {
102 scoped_ptr
<base::Value
> data(base::JSONReader::Read(json
));
106 const base::DictionaryValue
* services
= NULL
;
107 if (!data
->GetAsDictionary(&services
))
110 const base::DictionaryValue
* cloud_print
= NULL
;
111 if (!services
->GetDictionary(kCloudPrintJsonName
, &cloud_print
))
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
))
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_
);
126 return valid_file
&& IsValid();
129 bool ServiceState::IsValid() const {
130 if (email_
.empty() || proxy_id_
.empty())
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
;
137 std::string
ServiceState::ToString() {
138 scoped_ptr
<base::DictionaryValue
> cloud_print(new base::DictionaryValue());
139 cloud_print
->SetBoolean(kEnabledOptionName
, true);
141 SetNotEmptyJsonString(cloud_print
.get(), kEmailOptionName
, email_
);
142 SetNotEmptyJsonString(cloud_print
.get(), kProxyIdOptionName
, proxy_id_
);
143 SetNotEmptyJsonString(cloud_print
.get(), kRobotEmailOptionName
, robot_email_
);
144 SetNotEmptyJsonString(cloud_print
.get(), kRobotTokenOptionName
, robot_token_
);
145 SetNotEmptyJsonString(cloud_print
.get(), kAuthTokenOptionName
, auth_token_
);
146 SetNotEmptyJsonString(cloud_print
.get(), kXmppAuthTokenOptionName
,
149 base::DictionaryValue services
;
150 services
.Set(kCloudPrintJsonName
, cloud_print
.Pass());
153 base::JSONWriter::WriteWithOptions(
154 services
, base::JSONWriter::OPTIONS_PRETTY_PRINT
, &json
);
158 std::string
ServiceState::LoginToGoogle(const std::string
& service
,
159 const std::string
& email
,
160 const std::string
& password
) {
161 base::MessageLoopForIO loop
;
163 net::URLRequestContextBuilder builder
;
164 scoped_ptr
<net::URLRequestContext
> context(builder
.Build());
166 ServiceStateURLRequestDelegate fetcher_delegate
;
167 GURL
url(kClientLoginUrl
);
169 std::string post_body
;
170 post_body
+= "accountType=GOOGLE";
171 post_body
+= "&Email=" + net::EscapeUrlEncodedData(email
, true);
172 post_body
+= "&Passwd=" + net::EscapeUrlEncodedData(password
, true);
173 post_body
+= "&source=" + net::EscapeUrlEncodedData("CP-Service", true);
174 post_body
+= "&service=" + net::EscapeUrlEncodedData(service
, true);
176 scoped_ptr
<net::URLRequest
> request(context
->CreateRequest(
177 url
, net::DEFAULT_PRIORITY
, &fetcher_delegate
));
178 int load_flags
= request
->load_flags();
179 load_flags
= load_flags
| net::LOAD_DO_NOT_SEND_COOKIES
;
180 load_flags
= load_flags
| net::LOAD_DO_NOT_SAVE_COOKIES
;
181 request
->SetLoadFlags(load_flags
);
183 scoped_ptr
<net::UploadElementReader
> reader(
184 net::UploadOwnedBytesElementReader::CreateWithString(post_body
));
186 net::ElementsUploadDataStream::CreateWithReader(reader
.Pass(), 0));
187 request
->SetExtraRequestHeaderByName(
188 "Content-Type", "application/x-www-form-urlencoded", true);
189 request
->set_method("POST");
192 base::MessageLoop::current()->PostDelayedTask(
194 base::MessageLoop::QuitClosure(),
195 base::TimeDelta::FromMilliseconds(kRequestTimeoutMs
));
197 base::MessageLoop::current()->Run();
199 const char kAuthStart
[] = "Auth=";
200 for (const base::StringPiece
& line
:
201 base::SplitStringPiece(fetcher_delegate
.data(), "\r\n",
202 base::KEEP_WHITESPACE
,
203 base::SPLIT_WANT_NONEMPTY
)) {
204 if (base::StartsWith(line
, kAuthStart
,
205 base::CompareCase::INSENSITIVE_ASCII
))
206 return line
.substr(arraysize(kAuthStart
) - 1).as_string();
209 return std::string();
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();
218 proxy_id_
= proxy_id
;
219 auth_token_
= LoginToGoogle("cloudprint", email_
, password
);
220 xmpp_auth_token_
= LoginToGoogle("chromiumsync", email_
, password
);