Home | History | Annotate | Download | only in prototype
      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 
     23 const char kCloudPrintUrl[] = "https://www.google.com/cloudprint";
     24 
     25 namespace {
     26 
     27 const char kProxyIdValue[] = "proxy";
     28 const char kPrinterNameValue[] = "printer";
     29 const char kPrinterCapsValue[] = "capabilities";
     30 const char kPrinterCapsHashValue[] = "capsHash";
     31 const char kPrinterUserValue[] = "user";
     32 const char kPrinterGcpVersion[] = "gcp_version";
     33 const char kPrinterLocalSettings[] = "local_settings";
     34 const char kPrinterFirmware[] = "firmware";
     35 const char kPrinterManufacturer[] = "manufacturer";
     36 const char kPrinterModel[] = "model";
     37 const char kPrinterSetupUrl[] = "setup_url";
     38 const char kPrinterSupportUrl[] = "support_url";
     39 const char kPrinterUpdateUrl[] = "update_url";
     40 
     41 const char kFirmwareValue[] = "2.0";
     42 const char kManufacturerValue[] = "Google";
     43 const char kModelValue[] = "GCPPrototype";
     44 
     45 // TODO(maksymb): Replace GCP Version with "2.0" once GCP Server will support it
     46 const char kGcpVersion[] = "1.5";
     47 
     48 const int kGaiaMaxRetries = 3;
     49 
     50 GURL CreateRegisterUrl() {
     51   return GURL(std::string(kCloudPrintUrl) + "/register");
     52 }
     53 
     54 GURL 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 
     60 GURL 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 
     67 GURL 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 
     73 GURL 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 
     79 std::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 
     98 using cloud_print_response_parser::Job;
     99 
    100 CloudPrintRequester::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 
    112 CloudPrintRequester::~CloudPrintRequester() {
    113 }
    114 
    115 bool CloudPrintRequester::IsBusy() const {
    116   return request_ || gaia_;
    117 }
    118 
    119 void 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 
    181 void 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 
    188 void 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 
    203 void 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 
    211 void 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 
    220 void 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 
    228 void 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 
    236 void 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 
    255 void CloudPrintRequester::OnFetchComplete(const std::string& response) {
    256   VLOG(3) << "Function: " << __FUNCTION__;
    257   ParserCallback callback = parser_callback_;
    258   EraseRequest();
    259   callback.Run(response);
    260 }
    261 
    262 void 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 
    279 void CloudPrintRequester::OnFetchTimeoutReached() {
    280   VLOG(3) << "Function: " << __FUNCTION__;
    281   EraseRequest();
    282   current_print_job_.reset();
    283   delegate_->OnNetworkError();
    284 }
    285 
    286 void 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 
    295 void 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 
    303 void CloudPrintRequester::OnOAuthError() {
    304   VLOG(3) << "Function: " << __FUNCTION__;
    305   gaia_.reset();
    306   delegate_->OnAuthError();
    307 }
    308 
    309 void 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 
    320 scoped_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 
    329 scoped_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 
    340 void CloudPrintRequester::EraseRequest() {
    341   DCHECK(request_);
    342   DCHECK(!parser_callback_.is_null());
    343   request_.reset();
    344   parser_callback_.Reset();
    345 }
    346 
    347 void 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 
    372 void 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 
    394 void 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 
    411 void 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 
    423 void 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 
    430 void CloudPrintRequester::ParsePrintJobDone(const std::string& response) {
    431   VLOG(3) << "Function: " << __FUNCTION__;
    432   current_print_job_.reset();
    433   delegate_->OnPrintJobDone();
    434 }
    435 
    436 void 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 
    445 void 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 
    465 void CloudPrintRequester::ParseLocalSettingUpdated(
    466     const std::string& response) {
    467   delegate_->OnLocalSettingsUpdated();
    468 }
    469