1// Copyright 2013 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/gcp20/prototype/cloud_print_requester.h"
6
7#include "base/bind.h"
8#include "base/json/json_writer.h"
9#include "base/md5.h"
10#include "base/message_loop/message_loop.h"
11#include "base/rand_util.h"
12#include "base/strings/stringprintf.h"
13#include "cloud_print/gcp20/prototype/cloud_print_url_request_context_getter.h"
14#include "google_apis/google_api_keys.h"
15#include "net/base/escape.h"
16#include "net/base/mime_util.h"
17#include "net/base/url_util.h"
18#include "net/http/http_status_code.h"
19#include "net/proxy/proxy_config_service_fixed.h"
20#include "net/url_request/url_request_context.h"
21#include "url/gurl.h"
22
23const char kCloudPrintUrl[] = "https://www.google.com/cloudprint";
24
25namespace {
26
27const char kProxyIdValue[] = "proxy";
28const char kPrinterNameValue[] = "printer";
29const char kPrinterCapsValue[] = "capabilities";
30const char kPrinterCapsHashValue[] = "capsHash";
31const char kPrinterUserValue[] = "user";
32const char kPrinterGcpVersion[] = "gcp_version";
33const char kPrinterLocalSettings[] = "local_settings";
34const char kPrinterFirmware[] = "firmware";
35const char kPrinterManufacturer[] = "manufacturer";
36const char kPrinterModel[] = "model";
37const char kPrinterSetupUrl[] = "setup_url";
38const char kPrinterSupportUrl[] = "support_url";
39const char kPrinterUpdateUrl[] = "update_url";
40
41const char kFirmwareValue[] = "2.0";
42const char kManufacturerValue[] = "Google";
43const char kModelValue[] = "GCPPrototype";
44
45// TODO(maksymb): Replace GCP Version with "2.0" once GCP Server will support it
46const char kGcpVersion[] = "1.5";
47
48const int kGaiaMaxRetries = 3;
49
50GURL CreateRegisterUrl() {
51  return GURL(std::string(kCloudPrintUrl) + "/register");
52}
53
54GURL CreateFetchUrl(const std::string& device_id) {
55  GURL url(std::string(kCloudPrintUrl) + "/fetch");
56  url = net::AppendQueryParameter(url, "printerid", device_id);
57  return url;
58}
59
60GURL CreateControlUrl(const std::string& job_id, const std::string& status) {
61  GURL url(std::string(kCloudPrintUrl) + "/control");
62  url = net::AppendQueryParameter(url, "jobid", job_id);
63  url = net::AppendQueryParameter(url, "status", status);
64  return url;
65}
66
67GURL CreatePrinterUrl(const std::string& device_id) {
68  GURL url(std::string(kCloudPrintUrl) + "/printer");
69  url = net::AppendQueryParameter(url, "printerid", device_id);
70  return url;
71}
72
73GURL CreateUpdateUrl(const std::string& device_id) {
74  GURL url(std::string(kCloudPrintUrl) + "/update");
75  url = net::AppendQueryParameter(url, "printerid", device_id);
76  return url;
77}
78
79std::string LocalSettingsToJson(const LocalSettings& settings) {
80  base::DictionaryValue dictionary;
81  scoped_ptr<base::DictionaryValue> current(new base::DictionaryValue);
82
83  // TODO(maksymb): Formalize text as constants.
84  current->SetBoolean("local_discovery", settings.local_discovery);
85  current->SetBoolean("access_token_enabled", settings.access_token_enabled);
86  current->SetBoolean("printer/local_printing_enabled",
87                         settings.local_printing_enabled);
88  current->SetInteger("xmpp_timeout_value", settings.xmpp_timeout_value);
89  dictionary.Set("current", current.release());
90
91  std::string local_settings;
92  base::JSONWriter::Write(&dictionary, &local_settings);
93  return local_settings;
94}
95
96}  // namespace
97
98using cloud_print_response_parser::Job;
99
100CloudPrintRequester::CloudPrintRequester(
101    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
102    Delegate* delegate)
103    : context_getter_(new CloudPrintURLRequestContextGetter(task_runner)),
104      delegate_(delegate) {
105  oauth_client_info_.client_id =
106      google_apis::GetOAuth2ClientID(google_apis::CLIENT_CLOUD_PRINT);
107  oauth_client_info_.client_secret =
108      google_apis::GetOAuth2ClientSecret(google_apis::CLIENT_CLOUD_PRINT);
109  oauth_client_info_.redirect_uri = "oob";
110}
111
112CloudPrintRequester::~CloudPrintRequester() {
113}
114
115bool CloudPrintRequester::IsBusy() const {
116  return request_ || gaia_;
117}
118
119void CloudPrintRequester::StartRegistration(const std::string& proxy_id,
120                                            const std::string& device_name,
121                                            const std::string& user,
122                                            const LocalSettings& settings,
123                                            const std::string& cdd) {
124  std::string mime_boundary;
125  int r1 = base::RandInt(0, kint32max);
126  int r2 = base::RandInt(0, kint32max);
127  base::SStringPrintf(&mime_boundary,
128                      "---------------------------%08X%08X", r1, r2);
129
130  std::string data;
131  std::string data_mimetype;
132  data_mimetype = "multipart/form-data; boundary=" + mime_boundary;
133
134  net::AddMultipartValueForUpload(kProxyIdValue, proxy_id, mime_boundary,
135                                  std::string(), &data);
136  net::AddMultipartValueForUpload(kPrinterNameValue, device_name, mime_boundary,
137                                  std::string(), &data);
138  net::AddMultipartValueForUpload("use_cdd", "true", mime_boundary,
139                                  std::string(), &data);
140  net::AddMultipartValueForUpload(kPrinterNameValue, device_name, mime_boundary,
141                                  std::string(), &data);
142  net::AddMultipartValueForUpload(kPrinterCapsValue, cdd, mime_boundary,
143                                  "application/json", &data);
144  net::AddMultipartValueForUpload(kPrinterCapsHashValue, base::MD5String(cdd),
145                                  mime_boundary, std::string(), &data);
146  net::AddMultipartValueForUpload(kPrinterUserValue, user,
147                                  mime_boundary, std::string(), &data);
148  net::AddMultipartValueForUpload(kPrinterGcpVersion, kGcpVersion,
149                                  mime_boundary, std::string(), &data);
150  net::AddMultipartValueForUpload(kPrinterLocalSettings,
151                                  LocalSettingsToJson(settings),
152                                  mime_boundary, std::string(), &data);
153  net::AddMultipartValueForUpload(kPrinterFirmware,
154                                  kFirmwareValue,
155                                  mime_boundary, std::string(), &data);
156  net::AddMultipartValueForUpload(kPrinterManufacturer,
157                                  kManufacturerValue,
158                                  mime_boundary, std::string(), &data);
159  net::AddMultipartValueForUpload(kPrinterModel,
160                                  kModelValue,
161                                  mime_boundary, std::string(), &data);
162  net::AddMultipartValueForUpload(kPrinterSetupUrl,
163                                  kCloudPrintUrl,
164                                  mime_boundary, std::string(), &data);
165  net::AddMultipartValueForUpload(kPrinterSupportUrl,
166                                  kCloudPrintUrl,
167                                  mime_boundary, std::string(), &data);
168  net::AddMultipartValueForUpload(kPrinterUpdateUrl,
169                                  kCloudPrintUrl,
170                                  mime_boundary, std::string(), &data);
171  net::AddMultipartFinalDelimiterForUpload(mime_boundary, &data);
172
173  request_ = CreatePost(
174      CreateRegisterUrl(),
175      data,
176      data_mimetype,
177      base::Bind(&CloudPrintRequester::ParseRegisterStart, AsWeakPtr()));
178  request_->Run(delegate_->GetAccessToken(), context_getter_);
179}
180
181void CloudPrintRequester::CompleteRegistration() {
182  request_ = CreateGet(
183      GURL(polling_url_ + oauth_client_info_.client_id),
184      base::Bind(&CloudPrintRequester::ParseRegisterComplete, AsWeakPtr()));
185  request_->Run(delegate_->GetAccessToken(), context_getter_);
186}
187
188void CloudPrintRequester::FetchPrintJobs(const std::string& device_id) {
189  VLOG(3) << "Function: " << __FUNCTION__;
190  if (IsBusy())
191    return;
192
193  DCHECK(!delegate_->GetAccessToken().empty());
194
195  VLOG(3) << "Function: " << __FUNCTION__ <<
196      ": request created";
197  request_ = CreateGet(
198      CreateFetchUrl(device_id),
199      base::Bind(&CloudPrintRequester::ParseFetch, AsWeakPtr()));
200  request_->Run(delegate_->GetAccessToken(), context_getter_);
201}
202
203void CloudPrintRequester::UpdateAccesstoken(const std::string& refresh_token) {
204  VLOG(3) << "Function: " << __FUNCTION__;
205  DCHECK(!IsBusy());
206  gaia_.reset(new gaia::GaiaOAuthClient(context_getter_.get()));
207  gaia_->RefreshToken(oauth_client_info_, refresh_token,
208                      std::vector<std::string>(), kGaiaMaxRetries, this);
209}
210
211void CloudPrintRequester::RequestPrintJob(const Job& job) {
212  VLOG(3) << "Function: " << __FUNCTION__;
213  current_print_job_.reset(new Job(job));
214  request_ = CreateGet(
215      CreateControlUrl(current_print_job_->job_id, "IN_PROGRESS"),
216      base::Bind(&CloudPrintRequester::ParsePrintJobInProgress, AsWeakPtr()));
217  request_->Run(delegate_->GetAccessToken(), context_getter_);
218}
219
220void CloudPrintRequester::SendPrintJobDone(const std::string& job_id) {
221  VLOG(3) << "Function: " << __FUNCTION__;
222  request_ = CreateGet(
223      CreateControlUrl(job_id, "DONE"),
224      base::Bind(&CloudPrintRequester::ParsePrintJobDone, AsWeakPtr()));
225  request_->Run(delegate_->GetAccessToken(), context_getter_);
226}
227
228void CloudPrintRequester::RequestLocalSettings(const std::string& device_id) {
229  VLOG(3) << "Function: " << __FUNCTION__;
230  request_ = CreateGet(
231      CreatePrinterUrl(device_id),
232      base::Bind(&CloudPrintRequester::ParseLocalSettings, AsWeakPtr()));
233  request_->Run(delegate_->GetAccessToken(), context_getter_);
234}
235
236void CloudPrintRequester::SendLocalSettings(
237    const std::string& device_id,
238    const LocalSettings& settings) {
239  VLOG(3) << "Function: " << __FUNCTION__;
240
241  std::string data_mimetype = "application/x-www-form-urlencoded";
242  std::string data = base::StringPrintf(
243      "%s=%s",
244      kPrinterLocalSettings,
245      net::EscapeUrlEncodedData(LocalSettingsToJson(settings), false).c_str());
246
247  request_ = CreatePost(
248      CreateUpdateUrl(device_id),
249      data, data_mimetype,
250      base::Bind(&CloudPrintRequester::ParseLocalSettingUpdated, AsWeakPtr()));
251  request_->Run(delegate_->GetAccessToken(), context_getter_);
252}
253
254
255void CloudPrintRequester::OnFetchComplete(const std::string& response) {
256  VLOG(3) << "Function: " << __FUNCTION__;
257  ParserCallback callback = parser_callback_;
258  EraseRequest();
259  callback.Run(response);
260}
261
262void CloudPrintRequester::OnFetchError(const std::string& server_api,
263                                       int server_code,
264                                       int server_http_code) {
265  VLOG(3) << "Function: " << __FUNCTION__;
266  EraseRequest();
267  current_print_job_.reset();
268
269  if (server_http_code == net::HTTP_FORBIDDEN) {
270    delegate_->OnAuthError();
271  } else {
272    delegate_->OnServerError("Fetch error");
273  }
274
275  // TODO(maksymb): Add Privet |server_http_code| and |server_api| support in
276  // case of server errors.
277}
278
279void CloudPrintRequester::OnFetchTimeoutReached() {
280  VLOG(3) << "Function: " << __FUNCTION__;
281  EraseRequest();
282  current_print_job_.reset();
283  delegate_->OnNetworkError();
284}
285
286void CloudPrintRequester::OnGetTokensResponse(const std::string& refresh_token,
287                                              const std::string& access_token,
288                                              int expires_in_seconds) {
289  VLOG(3) << "Function: " << __FUNCTION__;
290  gaia_.reset();
291  delegate_->OnRegistrationFinished(refresh_token,
292                                    access_token, expires_in_seconds);
293}
294
295void CloudPrintRequester::OnRefreshTokenResponse(
296    const std::string& access_token,
297    int expires_in_seconds) {
298  VLOG(3) << "Function: " << __FUNCTION__;
299  gaia_.reset();
300  delegate_->OnAccesstokenReceviced(access_token, expires_in_seconds);
301}
302
303void CloudPrintRequester::OnOAuthError() {
304  VLOG(3) << "Function: " << __FUNCTION__;
305  gaia_.reset();
306  delegate_->OnAuthError();
307}
308
309void CloudPrintRequester::OnNetworkError(int response_code) {
310  VLOG(3) << "Function: " << __FUNCTION__;
311  gaia_.reset();
312
313  if (response_code == net::HTTP_FORBIDDEN) {
314    // TODO(maksymb): delegate_->OnPrinterDeleted();
315  } else {
316    delegate_->OnNetworkError();
317  }
318}
319
320scoped_ptr<CloudPrintRequest> CloudPrintRequester::CreateGet(
321    const GURL& url,
322    const ParserCallback& parser_callback) {
323  DCHECK(!IsBusy());
324  DCHECK(parser_callback_.is_null());
325  parser_callback_ = parser_callback;
326  return CloudPrintRequest::CreateGet(url, this);
327}
328
329scoped_ptr<CloudPrintRequest> CloudPrintRequester::CreatePost(
330    const GURL& url,
331    const std::string& content,
332    const std::string& mimetype,
333    const ParserCallback& parser_callback) {
334  DCHECK(!IsBusy());
335  DCHECK(parser_callback_.is_null());
336  parser_callback_ = parser_callback;
337  return CloudPrintRequest::CreatePost(url, content, mimetype, this);
338}
339
340void CloudPrintRequester::EraseRequest() {
341  DCHECK(request_);
342  DCHECK(!parser_callback_.is_null());
343  request_.reset();
344  parser_callback_.Reset();
345}
346
347void CloudPrintRequester::ParseRegisterStart(const std::string& response) {
348  std::string error_description;
349  std::string polling_url;
350  std::string registration_token;
351  std::string complete_invite_url;
352  std::string device_id;
353
354  bool success = cloud_print_response_parser::ParseRegisterStartResponse(
355      response,
356      &error_description,
357      &polling_url,
358      &registration_token,
359      &complete_invite_url,
360      &device_id);
361
362  if (success) {
363    polling_url_ = polling_url;
364    delegate_->OnRegistrationStartResponseParsed(registration_token,
365                                                 complete_invite_url,
366                                                 device_id);
367  } else {
368    delegate_->OnRegistrationError(error_description);
369  }
370}
371
372void CloudPrintRequester::ParseRegisterComplete(const std::string& response) {
373  std::string error_description;
374  std::string authorization_code;
375
376  std::string xmpp_jid;
377  bool success = cloud_print_response_parser::ParseRegisterCompleteResponse(
378      response,
379      &error_description,
380      &authorization_code,
381      &xmpp_jid);
382
383  if (success) {
384    delegate_->OnXmppJidReceived(xmpp_jid);
385
386    gaia_.reset(new gaia::GaiaOAuthClient(context_getter_.get()));
387    gaia_->GetTokensFromAuthCode(oauth_client_info_, authorization_code,
388                                 kGaiaMaxRetries, this);
389  } else {
390    delegate_->OnRegistrationError(error_description);
391  }
392}
393
394void CloudPrintRequester::ParseFetch(const std::string& response) {
395  VLOG(3) << "Function: " << __FUNCTION__;
396
397  std::string error_description;
398  std::vector<Job> list;
399  bool success = cloud_print_response_parser::ParseFetchResponse(
400      response,
401      &error_description,
402      &list);
403
404  if (success) {
405    delegate_->OnPrintJobsAvailable(list);
406  } else {
407    delegate_->OnServerError(error_description);
408  }
409}
410
411void CloudPrintRequester::ParseGetPrintJobTicket(const std::string& response) {
412  VLOG(3) << "Function: " << __FUNCTION__;
413  current_print_job_->ticket = response;
414
415  DCHECK(current_print_job_);
416  request_ = CreateGet(
417      GURL(current_print_job_->file_url),
418      base::Bind(&CloudPrintRequester::ParseGetPrintJobData, AsWeakPtr()));
419  request_->AddHeader("Accept: \"application/pdf\"");
420  request_->Run(delegate_->GetAccessToken(), context_getter_);
421}
422
423void CloudPrintRequester::ParseGetPrintJobData(const std::string& response) {
424  VLOG(3) << "Function: " << __FUNCTION__;
425  current_print_job_->file = response;
426  DCHECK(current_print_job_);
427  delegate_->OnPrintJobDownloaded(*current_print_job_);
428}
429
430void CloudPrintRequester::ParsePrintJobDone(const std::string& response) {
431  VLOG(3) << "Function: " << __FUNCTION__;
432  current_print_job_.reset();
433  delegate_->OnPrintJobDone();
434}
435
436void CloudPrintRequester::ParsePrintJobInProgress(const std::string& response) {
437  VLOG(3) << "Function: " << __FUNCTION__;
438  DCHECK(current_print_job_);
439  request_ = CreateGet(
440      GURL(current_print_job_->ticket_url),
441      base::Bind(&CloudPrintRequester::ParseGetPrintJobTicket, AsWeakPtr()));
442  request_->Run(delegate_->GetAccessToken(), context_getter_);
443}
444
445void CloudPrintRequester::ParseLocalSettings(const std::string& response) {
446  VLOG(3) << "Function: " << __FUNCTION__;
447
448  std::string error_description;
449  LocalSettings settings;
450  LocalSettings::State state;
451
452  bool success = cloud_print_response_parser::ParseLocalSettingsResponse(
453      response,
454      &error_description,
455      &state,
456      &settings);
457
458  if (success) {
459    delegate_->OnLocalSettingsReceived(state, settings);
460  } else {
461    delegate_->OnServerError(error_description);
462  }
463}
464
465void CloudPrintRequester::ParseLocalSettingUpdated(
466    const std::string& response) {
467  delegate_->OnLocalSettingsUpdated();
468}
469