| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cloud_print/gcp20/prototype/cloud_print_requester.h" |
| |
| #include "base/bind.h" |
| #include "base/json/json_writer.h" |
| #include "base/md5.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/rand_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "cloud_print/gcp20/prototype/cloud_print_url_request_context_getter.h" |
| #include "google_apis/google_api_keys.h" |
| #include "net/base/escape.h" |
| #include "net/base/mime_util.h" |
| #include "net/base/url_util.h" |
| #include "net/http/http_status_code.h" |
| #include "net/proxy/proxy_config_service_fixed.h" |
| #include "net/url_request/url_request_context.h" |
| #include "url/gurl.h" |
| |
| const char kCloudPrintUrl[] = "https://www.google.com/cloudprint"; |
| |
| namespace { |
| |
| const char kProxyIdValue[] = "proxy"; |
| const char kPrinterNameValue[] = "printer"; |
| const char kPrinterCapsValue[] = "capabilities"; |
| const char kPrinterCapsHashValue[] = "capsHash"; |
| const char kPrinterUserValue[] = "user"; |
| const char kPrinterGcpVersion[] = "gcp_version"; |
| const char kPrinterLocalSettings[] = "local_settings"; |
| const char kPrinterFirmware[] = "firmware"; |
| const char kPrinterManufacturer[] = "manufacturer"; |
| const char kPrinterModel[] = "model"; |
| const char kPrinterSetupUrl[] = "setup_url"; |
| const char kPrinterSupportUrl[] = "support_url"; |
| const char kPrinterUpdateUrl[] = "update_url"; |
| |
| const char kFirmwareValue[] = "2.0"; |
| const char kManufacturerValue[] = "Google"; |
| const char kModelValue[] = "GCPPrototype"; |
| |
| // TODO(maksymb): Replace GCP Version with "2.0" once GCP Server will support it |
| const char kGcpVersion[] = "1.5"; |
| |
| const int kGaiaMaxRetries = 3; |
| |
| GURL CreateRegisterUrl() { |
| return GURL(std::string(kCloudPrintUrl) + "/register"); |
| } |
| |
| GURL CreateFetchUrl(const std::string& device_id) { |
| GURL url(std::string(kCloudPrintUrl) + "/fetch"); |
| url = net::AppendQueryParameter(url, "printerid", device_id); |
| return url; |
| } |
| |
| GURL CreateControlUrl(const std::string& job_id, const std::string& status) { |
| GURL url(std::string(kCloudPrintUrl) + "/control"); |
| url = net::AppendQueryParameter(url, "jobid", job_id); |
| url = net::AppendQueryParameter(url, "status", status); |
| return url; |
| } |
| |
| GURL CreatePrinterUrl(const std::string& device_id) { |
| GURL url(std::string(kCloudPrintUrl) + "/printer"); |
| url = net::AppendQueryParameter(url, "printerid", device_id); |
| return url; |
| } |
| |
| GURL CreateUpdateUrl(const std::string& device_id) { |
| GURL url(std::string(kCloudPrintUrl) + "/update"); |
| url = net::AppendQueryParameter(url, "printerid", device_id); |
| return url; |
| } |
| |
| std::string LocalSettingsToJson(const LocalSettings& settings) { |
| base::DictionaryValue dictionary; |
| scoped_ptr<base::DictionaryValue> current(new base::DictionaryValue); |
| |
| // TODO(maksymb): Formalize text as constants. |
| current->SetBoolean("local_discovery", settings.local_discovery); |
| current->SetBoolean("access_token_enabled", settings.access_token_enabled); |
| current->SetBoolean("printer/local_printing_enabled", |
| settings.local_printing_enabled); |
| current->SetInteger("xmpp_timeout_value", settings.xmpp_timeout_value); |
| dictionary.Set("current", current.release()); |
| |
| std::string local_settings; |
| base::JSONWriter::Write(&dictionary, &local_settings); |
| return local_settings; |
| } |
| |
| } // namespace |
| |
| using cloud_print_response_parser::Job; |
| |
| CloudPrintRequester::CloudPrintRequester( |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner, |
| Delegate* delegate) |
| : context_getter_(new CloudPrintURLRequestContextGetter(task_runner)), |
| delegate_(delegate) { |
| oauth_client_info_.client_id = |
| google_apis::GetOAuth2ClientID(google_apis::CLIENT_CLOUD_PRINT); |
| oauth_client_info_.client_secret = |
| google_apis::GetOAuth2ClientSecret(google_apis::CLIENT_CLOUD_PRINT); |
| oauth_client_info_.redirect_uri = "oob"; |
| } |
| |
| CloudPrintRequester::~CloudPrintRequester() { |
| } |
| |
| bool CloudPrintRequester::IsBusy() const { |
| return request_ || gaia_; |
| } |
| |
| void CloudPrintRequester::StartRegistration(const std::string& proxy_id, |
| const std::string& device_name, |
| const std::string& user, |
| const LocalSettings& settings, |
| const std::string& cdd) { |
| std::string mime_boundary; |
| int r1 = base::RandInt(0, kint32max); |
| int r2 = base::RandInt(0, kint32max); |
| base::SStringPrintf(&mime_boundary, |
| "---------------------------%08X%08X", r1, r2); |
| |
| std::string data; |
| std::string data_mimetype; |
| data_mimetype = "multipart/form-data; boundary=" + mime_boundary; |
| |
| net::AddMultipartValueForUpload(kProxyIdValue, proxy_id, mime_boundary, |
| std::string(), &data); |
| net::AddMultipartValueForUpload(kPrinterNameValue, device_name, mime_boundary, |
| std::string(), &data); |
| net::AddMultipartValueForUpload("use_cdd", "true", mime_boundary, |
| std::string(), &data); |
| net::AddMultipartValueForUpload(kPrinterNameValue, device_name, mime_boundary, |
| std::string(), &data); |
| net::AddMultipartValueForUpload(kPrinterCapsValue, cdd, mime_boundary, |
| "application/json", &data); |
| net::AddMultipartValueForUpload(kPrinterCapsHashValue, base::MD5String(cdd), |
| mime_boundary, std::string(), &data); |
| net::AddMultipartValueForUpload(kPrinterUserValue, user, |
| mime_boundary, std::string(), &data); |
| net::AddMultipartValueForUpload(kPrinterGcpVersion, kGcpVersion, |
| mime_boundary, std::string(), &data); |
| net::AddMultipartValueForUpload(kPrinterLocalSettings, |
| LocalSettingsToJson(settings), |
| mime_boundary, std::string(), &data); |
| net::AddMultipartValueForUpload(kPrinterFirmware, |
| kFirmwareValue, |
| mime_boundary, std::string(), &data); |
| net::AddMultipartValueForUpload(kPrinterManufacturer, |
| kManufacturerValue, |
| mime_boundary, std::string(), &data); |
| net::AddMultipartValueForUpload(kPrinterModel, |
| kModelValue, |
| mime_boundary, std::string(), &data); |
| net::AddMultipartValueForUpload(kPrinterSetupUrl, |
| kCloudPrintUrl, |
| mime_boundary, std::string(), &data); |
| net::AddMultipartValueForUpload(kPrinterSupportUrl, |
| kCloudPrintUrl, |
| mime_boundary, std::string(), &data); |
| net::AddMultipartValueForUpload(kPrinterUpdateUrl, |
| kCloudPrintUrl, |
| mime_boundary, std::string(), &data); |
| net::AddMultipartFinalDelimiterForUpload(mime_boundary, &data); |
| |
| request_ = CreatePost( |
| CreateRegisterUrl(), |
| data, |
| data_mimetype, |
| base::Bind(&CloudPrintRequester::ParseRegisterStart, AsWeakPtr())); |
| request_->Run(delegate_->GetAccessToken(), context_getter_); |
| } |
| |
| void CloudPrintRequester::CompleteRegistration() { |
| request_ = CreateGet( |
| GURL(polling_url_ + oauth_client_info_.client_id), |
| base::Bind(&CloudPrintRequester::ParseRegisterComplete, AsWeakPtr())); |
| request_->Run(delegate_->GetAccessToken(), context_getter_); |
| } |
| |
| void CloudPrintRequester::FetchPrintJobs(const std::string& device_id) { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| if (IsBusy()) |
| return; |
| |
| DCHECK(!delegate_->GetAccessToken().empty()); |
| |
| VLOG(3) << "Function: " << __FUNCTION__ << |
| ": request created"; |
| request_ = CreateGet( |
| CreateFetchUrl(device_id), |
| base::Bind(&CloudPrintRequester::ParseFetch, AsWeakPtr())); |
| request_->Run(delegate_->GetAccessToken(), context_getter_); |
| } |
| |
| void CloudPrintRequester::UpdateAccesstoken(const std::string& refresh_token) { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| DCHECK(!IsBusy()); |
| gaia_.reset(new gaia::GaiaOAuthClient(context_getter_.get())); |
| gaia_->RefreshToken(oauth_client_info_, refresh_token, |
| std::vector<std::string>(), kGaiaMaxRetries, this); |
| } |
| |
| void CloudPrintRequester::RequestPrintJob(const Job& job) { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| current_print_job_.reset(new Job(job)); |
| request_ = CreateGet( |
| CreateControlUrl(current_print_job_->job_id, "IN_PROGRESS"), |
| base::Bind(&CloudPrintRequester::ParsePrintJobInProgress, AsWeakPtr())); |
| request_->Run(delegate_->GetAccessToken(), context_getter_); |
| } |
| |
| void CloudPrintRequester::SendPrintJobDone(const std::string& job_id) { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| request_ = CreateGet( |
| CreateControlUrl(job_id, "DONE"), |
| base::Bind(&CloudPrintRequester::ParsePrintJobDone, AsWeakPtr())); |
| request_->Run(delegate_->GetAccessToken(), context_getter_); |
| } |
| |
| void CloudPrintRequester::RequestLocalSettings(const std::string& device_id) { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| request_ = CreateGet( |
| CreatePrinterUrl(device_id), |
| base::Bind(&CloudPrintRequester::ParseLocalSettings, AsWeakPtr())); |
| request_->Run(delegate_->GetAccessToken(), context_getter_); |
| } |
| |
| void CloudPrintRequester::SendLocalSettings( |
| const std::string& device_id, |
| const LocalSettings& settings) { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| |
| std::string data_mimetype = "application/x-www-form-urlencoded"; |
| std::string data = base::StringPrintf( |
| "%s=%s", |
| kPrinterLocalSettings, |
| net::EscapeUrlEncodedData(LocalSettingsToJson(settings), false).c_str()); |
| |
| request_ = CreatePost( |
| CreateUpdateUrl(device_id), |
| data, data_mimetype, |
| base::Bind(&CloudPrintRequester::ParseLocalSettingUpdated, AsWeakPtr())); |
| request_->Run(delegate_->GetAccessToken(), context_getter_); |
| } |
| |
| |
| void CloudPrintRequester::OnFetchComplete(const std::string& response) { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| ParserCallback callback = parser_callback_; |
| EraseRequest(); |
| callback.Run(response); |
| } |
| |
| void CloudPrintRequester::OnFetchError(const std::string& server_api, |
| int server_code, |
| int server_http_code) { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| EraseRequest(); |
| current_print_job_.reset(); |
| |
| if (server_http_code == net::HTTP_FORBIDDEN) { |
| delegate_->OnAuthError(); |
| } else { |
| delegate_->OnServerError("Fetch error"); |
| } |
| |
| // TODO(maksymb): Add Privet |server_http_code| and |server_api| support in |
| // case of server errors. |
| } |
| |
| void CloudPrintRequester::OnFetchTimeoutReached() { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| EraseRequest(); |
| current_print_job_.reset(); |
| delegate_->OnNetworkError(); |
| } |
| |
| void CloudPrintRequester::OnGetTokensResponse(const std::string& refresh_token, |
| const std::string& access_token, |
| int expires_in_seconds) { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| gaia_.reset(); |
| delegate_->OnRegistrationFinished(refresh_token, |
| access_token, expires_in_seconds); |
| } |
| |
| void CloudPrintRequester::OnRefreshTokenResponse( |
| const std::string& access_token, |
| int expires_in_seconds) { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| gaia_.reset(); |
| delegate_->OnAccesstokenReceviced(access_token, expires_in_seconds); |
| } |
| |
| void CloudPrintRequester::OnOAuthError() { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| gaia_.reset(); |
| delegate_->OnAuthError(); |
| } |
| |
| void CloudPrintRequester::OnNetworkError(int response_code) { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| gaia_.reset(); |
| |
| if (response_code == net::HTTP_FORBIDDEN) { |
| // TODO(maksymb): delegate_->OnPrinterDeleted(); |
| } else { |
| delegate_->OnNetworkError(); |
| } |
| } |
| |
| scoped_ptr<CloudPrintRequest> CloudPrintRequester::CreateGet( |
| const GURL& url, |
| const ParserCallback& parser_callback) { |
| DCHECK(!IsBusy()); |
| DCHECK(parser_callback_.is_null()); |
| parser_callback_ = parser_callback; |
| return CloudPrintRequest::CreateGet(url, this); |
| } |
| |
| scoped_ptr<CloudPrintRequest> CloudPrintRequester::CreatePost( |
| const GURL& url, |
| const std::string& content, |
| const std::string& mimetype, |
| const ParserCallback& parser_callback) { |
| DCHECK(!IsBusy()); |
| DCHECK(parser_callback_.is_null()); |
| parser_callback_ = parser_callback; |
| return CloudPrintRequest::CreatePost(url, content, mimetype, this); |
| } |
| |
| void CloudPrintRequester::EraseRequest() { |
| DCHECK(request_); |
| DCHECK(!parser_callback_.is_null()); |
| request_.reset(); |
| parser_callback_.Reset(); |
| } |
| |
| void CloudPrintRequester::ParseRegisterStart(const std::string& response) { |
| std::string error_description; |
| std::string polling_url; |
| std::string registration_token; |
| std::string complete_invite_url; |
| std::string device_id; |
| |
| bool success = cloud_print_response_parser::ParseRegisterStartResponse( |
| response, |
| &error_description, |
| &polling_url, |
| ®istration_token, |
| &complete_invite_url, |
| &device_id); |
| |
| if (success) { |
| polling_url_ = polling_url; |
| delegate_->OnRegistrationStartResponseParsed(registration_token, |
| complete_invite_url, |
| device_id); |
| } else { |
| delegate_->OnRegistrationError(error_description); |
| } |
| } |
| |
| void CloudPrintRequester::ParseRegisterComplete(const std::string& response) { |
| std::string error_description; |
| std::string authorization_code; |
| |
| std::string xmpp_jid; |
| bool success = cloud_print_response_parser::ParseRegisterCompleteResponse( |
| response, |
| &error_description, |
| &authorization_code, |
| &xmpp_jid); |
| |
| if (success) { |
| delegate_->OnXmppJidReceived(xmpp_jid); |
| |
| gaia_.reset(new gaia::GaiaOAuthClient(context_getter_.get())); |
| gaia_->GetTokensFromAuthCode(oauth_client_info_, authorization_code, |
| kGaiaMaxRetries, this); |
| } else { |
| delegate_->OnRegistrationError(error_description); |
| } |
| } |
| |
| void CloudPrintRequester::ParseFetch(const std::string& response) { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| |
| std::string error_description; |
| std::vector<Job> list; |
| bool success = cloud_print_response_parser::ParseFetchResponse( |
| response, |
| &error_description, |
| &list); |
| |
| if (success) { |
| delegate_->OnPrintJobsAvailable(list); |
| } else { |
| delegate_->OnServerError(error_description); |
| } |
| } |
| |
| void CloudPrintRequester::ParseGetPrintJobTicket(const std::string& response) { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| current_print_job_->ticket = response; |
| |
| DCHECK(current_print_job_); |
| request_ = CreateGet( |
| GURL(current_print_job_->file_url), |
| base::Bind(&CloudPrintRequester::ParseGetPrintJobData, AsWeakPtr())); |
| request_->AddHeader("Accept: \"application/pdf\""); |
| request_->Run(delegate_->GetAccessToken(), context_getter_); |
| } |
| |
| void CloudPrintRequester::ParseGetPrintJobData(const std::string& response) { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| current_print_job_->file = response; |
| DCHECK(current_print_job_); |
| delegate_->OnPrintJobDownloaded(*current_print_job_); |
| } |
| |
| void CloudPrintRequester::ParsePrintJobDone(const std::string& response) { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| current_print_job_.reset(); |
| delegate_->OnPrintJobDone(); |
| } |
| |
| void CloudPrintRequester::ParsePrintJobInProgress(const std::string& response) { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| DCHECK(current_print_job_); |
| request_ = CreateGet( |
| GURL(current_print_job_->ticket_url), |
| base::Bind(&CloudPrintRequester::ParseGetPrintJobTicket, AsWeakPtr())); |
| request_->Run(delegate_->GetAccessToken(), context_getter_); |
| } |
| |
| void CloudPrintRequester::ParseLocalSettings(const std::string& response) { |
| VLOG(3) << "Function: " << __FUNCTION__; |
| |
| std::string error_description; |
| LocalSettings settings; |
| LocalSettings::State state; |
| |
| bool success = cloud_print_response_parser::ParseLocalSettingsResponse( |
| response, |
| &error_description, |
| &state, |
| &settings); |
| |
| if (success) { |
| delegate_->OnLocalSettingsReceived(state, settings); |
| } else { |
| delegate_->OnServerError(error_description); |
| } |
| } |
| |
| void CloudPrintRequester::ParseLocalSettingUpdated( |
| const std::string& response) { |
| delegate_->OnLocalSettingsUpdated(); |
| } |