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 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 ®istration_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