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. 4 5 #include "chrome/service/cloud_print/cloud_print_connector.h" 6 7 #include "base/bind.h" 8 #include "base/bind_helpers.h" 9 #include "base/md5.h" 10 #include "base/rand_util.h" 11 #include "base/strings/string_number_conversions.h" 12 #include "base/strings/string_split.h" 13 #include "base/strings/stringprintf.h" 14 #include "base/strings/utf_string_conversions.h" 15 #include "base/values.h" 16 #include "chrome/common/cloud_print/cloud_print_constants.h" 17 #include "chrome/common/cloud_print/cloud_print_helpers.h" 18 #include "chrome/service/cloud_print/cloud_print_helpers.h" 19 #include "grit/generated_resources.h" 20 #include "net/base/mime_util.h" 21 #include "ui/base/l10n/l10n_util.h" 22 23 namespace cloud_print { 24 25 CloudPrintConnector::CloudPrintConnector(Client* client, 26 const ConnectorSettings& settings) 27 : client_(client), 28 next_response_handler_(NULL) { 29 settings_.CopyFrom(settings); 30 } 31 32 bool CloudPrintConnector::InitPrintSystem() { 33 if (print_system_.get()) 34 return true; 35 print_system_ = PrintSystem::CreateInstance( 36 settings_.print_system_settings()); 37 if (!print_system_.get()) { 38 NOTREACHED(); 39 return false; // No memory. 40 } 41 PrintSystem::PrintSystemResult result = print_system_->Init(); 42 if (!result.succeeded()) { 43 print_system_ = NULL; 44 // We could not initialize the print system. We need to notify the server. 45 ReportUserMessage(kPrintSystemFailedMessageId, result.message()); 46 return false; 47 } 48 return true; 49 } 50 51 bool CloudPrintConnector::Start() { 52 VLOG(1) << "CP_CONNECTOR: Starting connector" 53 << ", proxy id: " << settings_.proxy_id(); 54 55 pending_tasks_.clear(); 56 57 if (!InitPrintSystem()) 58 return false; 59 60 // Start watching for updates from the print system. 61 print_server_watcher_ = print_system_->CreatePrintServerWatcher(); 62 print_server_watcher_->StartWatching(this); 63 64 // Get list of registered printers. 65 AddPendingAvailableTask(); 66 return true; 67 } 68 69 void CloudPrintConnector::Stop() { 70 VLOG(1) << "CP_CONNECTOR: Stopping connector" 71 << ", proxy id: " << settings_.proxy_id(); 72 DCHECK(IsRunning()); 73 // Do uninitialization here. 74 pending_tasks_.clear(); 75 print_server_watcher_ = NULL; 76 request_ = NULL; 77 } 78 79 bool CloudPrintConnector::IsRunning() { 80 return print_server_watcher_.get() != NULL; 81 } 82 83 void CloudPrintConnector::GetPrinterIds(std::list<std::string>* printer_ids) { 84 DCHECK(printer_ids); 85 printer_ids->clear(); 86 for (JobHandlerMap::const_iterator iter = job_handler_map_.begin(); 87 iter != job_handler_map_.end(); ++iter) { 88 printer_ids->push_back(iter->first); 89 } 90 } 91 92 void CloudPrintConnector::RegisterPrinters( 93 const printing::PrinterList& printers) { 94 if (!IsRunning()) 95 return; 96 printing::PrinterList::const_iterator it; 97 for (it = printers.begin(); it != printers.end(); ++it) { 98 if (settings_.ShouldConnect(it->printer_name)) 99 AddPendingRegisterTask(*it); 100 } 101 } 102 103 // Check for jobs for specific printer 104 void CloudPrintConnector::CheckForJobs(const std::string& reason, 105 const std::string& printer_id) { 106 if (!IsRunning()) 107 return; 108 if (!printer_id.empty()) { 109 JobHandlerMap::iterator index = job_handler_map_.find(printer_id); 110 if (index != job_handler_map_.end()) { 111 index->second->CheckForJobs(reason); 112 } else { 113 std::string status_message = l10n_util::GetStringUTF8( 114 IDS_CLOUD_PRINT_ZOMBIE_PRINTER); 115 LOG(ERROR) << "CP_CONNECTOR: " << status_message << 116 " Printer_id: " << printer_id; 117 ReportUserMessage(kZombiePrinterMessageId, status_message); 118 } 119 } else { 120 for (JobHandlerMap::iterator index = job_handler_map_.begin(); 121 index != job_handler_map_.end(); index++) { 122 index->second->CheckForJobs(reason); 123 } 124 } 125 } 126 127 void CloudPrintConnector::OnPrinterAdded() { 128 AddPendingAvailableTask(); 129 } 130 131 void CloudPrintConnector::OnPrinterDeleted(const std::string& printer_id) { 132 AddPendingDeleteTask(printer_id); 133 } 134 135 void CloudPrintConnector::OnAuthError() { 136 client_->OnAuthFailed(); 137 } 138 139 // CloudPrintURLFetcher::Delegate implementation. 140 CloudPrintURLFetcher::ResponseAction CloudPrintConnector::HandleRawData( 141 const net::URLFetcher* source, 142 const GURL& url, 143 const std::string& data) { 144 // If this notification came as a result of user message call, stop it. 145 // Otherwise proceed continue processing. 146 if (user_message_request_.get() && 147 user_message_request_->IsSameRequest(source)) 148 return CloudPrintURLFetcher::STOP_PROCESSING; 149 return CloudPrintURLFetcher::CONTINUE_PROCESSING; 150 } 151 152 CloudPrintURLFetcher::ResponseAction CloudPrintConnector::HandleJSONData( 153 const net::URLFetcher* source, 154 const GURL& url, 155 DictionaryValue* json_data, 156 bool succeeded) { 157 if (!IsRunning()) // Orphant response. Connector has been stopped already. 158 return CloudPrintURLFetcher::STOP_PROCESSING; 159 160 DCHECK(next_response_handler_); 161 return (this->*next_response_handler_)(source, url, json_data, succeeded); 162 } 163 164 CloudPrintURLFetcher::ResponseAction CloudPrintConnector::OnRequestAuthError() { 165 OnAuthError(); 166 return CloudPrintURLFetcher::STOP_PROCESSING; 167 } 168 169 std::string CloudPrintConnector::GetAuthHeader() { 170 return GetCloudPrintAuthHeaderFromStore(); 171 } 172 173 CloudPrintConnector::~CloudPrintConnector() {} 174 175 CloudPrintURLFetcher::ResponseAction 176 CloudPrintConnector::HandlePrinterListResponse( 177 const net::URLFetcher* source, 178 const GURL& url, 179 DictionaryValue* json_data, 180 bool succeeded) { 181 DCHECK(succeeded); 182 if (!succeeded) 183 return CloudPrintURLFetcher::RETRY_REQUEST; 184 185 // Now we need to get the list of printers from the print system 186 // and split printers into 3 categories: 187 // - existing and registered printers 188 // - new printers 189 // - deleted printers 190 191 // Get list of the printers from the print system. 192 printing::PrinterList local_printers; 193 PrintSystem::PrintSystemResult result = 194 print_system_->EnumeratePrinters(&local_printers); 195 bool full_list = result.succeeded(); 196 if (!full_list) { 197 std::string message = result.message(); 198 if (message.empty()) 199 message = l10n_util::GetStringFUTF8(IDS_CLOUD_PRINT_ENUM_FAILED, 200 l10n_util::GetStringUTF16(IDS_GOOGLE_CLOUD_PRINT)); 201 // There was a failure enumerating printers. Send a message to the server. 202 ReportUserMessage(kEnumPrintersFailedMessageId, message); 203 } 204 205 // Go through the list of the cloud printers and init print job handlers. 206 ListValue* printer_list = NULL; 207 // There may be no "printers" value in the JSON 208 if (json_data->GetList(kPrinterListValue, &printer_list) && printer_list) { 209 for (size_t index = 0; index < printer_list->GetSize(); index++) { 210 DictionaryValue* printer_data = NULL; 211 if (printer_list->GetDictionary(index, &printer_data)) { 212 std::string printer_name; 213 printer_data->GetString(kNameValue, &printer_name); 214 std::string printer_id; 215 printer_data->GetString(kIdValue, &printer_id); 216 217 if (!settings_.ShouldConnect(printer_name)) { 218 VLOG(1) << "CP_CONNECTOR: Deleting " << printer_name << 219 " id: " << printer_id << " as blacklisted"; 220 AddPendingDeleteTask(printer_id); 221 } else if (RemovePrinterFromList(printer_name, &local_printers)) { 222 InitJobHandlerForPrinter(printer_data); 223 } else { 224 // Cloud printer is not found on the local system. 225 if (full_list || settings_.delete_on_enum_fail()) { 226 // Delete if we get the full list of printers or 227 // |delete_on_enum_fail_| is set. 228 VLOG(1) << "CP_CONNECTOR: Deleting " << printer_name << 229 " id: " << printer_id << 230 " full_list: " << full_list << 231 " delete_on_enum_fail: " << settings_.delete_on_enum_fail(); 232 AddPendingDeleteTask(printer_id); 233 } else { 234 LOG(ERROR) << "CP_CONNECTOR: Printer: " << printer_name << 235 " id: " << printer_id << 236 " not found in print system and full printer list was" << 237 " not received. Printer will not be able to process" << 238 " jobs."; 239 } 240 } 241 } else { 242 NOTREACHED(); 243 } 244 } 245 } 246 247 request_ = NULL; 248 249 RegisterPrinters(local_printers); 250 ContinuePendingTaskProcessing(); // Continue processing background tasks. 251 return CloudPrintURLFetcher::STOP_PROCESSING; 252 } 253 254 CloudPrintURLFetcher::ResponseAction 255 CloudPrintConnector::HandlePrinterDeleteResponse( 256 const net::URLFetcher* source, 257 const GURL& url, 258 DictionaryValue* json_data, 259 bool succeeded) { 260 VLOG(1) << "CP_CONNECTOR: Handler printer delete response" 261 << ", succeeded: " << succeeded 262 << ", url: " << url; 263 ContinuePendingTaskProcessing(); // Continue processing background tasks. 264 return CloudPrintURLFetcher::STOP_PROCESSING; 265 } 266 267 CloudPrintURLFetcher::ResponseAction 268 CloudPrintConnector::HandleRegisterPrinterResponse( 269 const net::URLFetcher* source, 270 const GURL& url, 271 DictionaryValue* json_data, 272 bool succeeded) { 273 VLOG(1) << "CP_CONNECTOR: Handler printer register response" 274 << ", succeeded: " << succeeded 275 << ", url: " << url; 276 if (succeeded) { 277 ListValue* printer_list = NULL; 278 // There should be a "printers" value in the JSON 279 if (json_data->GetList(kPrinterListValue, &printer_list)) { 280 DictionaryValue* printer_data = NULL; 281 if (printer_list->GetDictionary(0, &printer_data)) 282 InitJobHandlerForPrinter(printer_data); 283 } 284 } 285 ContinuePendingTaskProcessing(); // Continue processing background tasks. 286 return CloudPrintURLFetcher::STOP_PROCESSING; 287 } 288 289 290 void CloudPrintConnector::StartGetRequest(const GURL& url, 291 int max_retries, 292 ResponseHandler handler) { 293 next_response_handler_ = handler; 294 request_ = CloudPrintURLFetcher::Create(); 295 request_->StartGetRequest(url, this, max_retries, std::string()); 296 } 297 298 void CloudPrintConnector::StartPostRequest(const GURL& url, 299 int max_retries, 300 const std::string& mime_type, 301 const std::string& post_data, 302 ResponseHandler handler) { 303 next_response_handler_ = handler; 304 request_ = CloudPrintURLFetcher::Create(); 305 request_->StartPostRequest( 306 url, this, max_retries, mime_type, post_data, std::string()); 307 } 308 309 void CloudPrintConnector::ReportUserMessage(const std::string& message_id, 310 const std::string& failure_msg) { 311 // This is a fire and forget type of function. 312 // Result of this request will be ignored. 313 std::string mime_boundary; 314 CreateMimeBoundaryForUpload(&mime_boundary); 315 GURL url = GetUrlForUserMessage(settings_.server_url(), message_id); 316 std::string post_data; 317 net::AddMultipartValueForUpload(kMessageTextValue, failure_msg, mime_boundary, 318 std::string(), &post_data); 319 net::AddMultipartFinalDelimiterForUpload(mime_boundary, &post_data); 320 std::string mime_type("multipart/form-data; boundary="); 321 mime_type += mime_boundary; 322 user_message_request_ = CloudPrintURLFetcher::Create(); 323 user_message_request_->StartPostRequest(url, this, 1, mime_type, post_data, 324 std::string()); 325 } 326 327 bool CloudPrintConnector::RemovePrinterFromList( 328 const std::string& printer_name, 329 printing::PrinterList* printer_list) { 330 for (printing::PrinterList::iterator index = printer_list->begin(); 331 index != printer_list->end(); index++) { 332 if (IsSamePrinter(index->printer_name, printer_name)) { 333 index = printer_list->erase(index); 334 return true; 335 } 336 } 337 return false; 338 } 339 340 void CloudPrintConnector::InitJobHandlerForPrinter( 341 DictionaryValue* printer_data) { 342 DCHECK(printer_data); 343 PrinterJobHandler::PrinterInfoFromCloud printer_info_cloud; 344 printer_data->GetString(kIdValue, &printer_info_cloud.printer_id); 345 DCHECK(!printer_info_cloud.printer_id.empty()); 346 VLOG(1) << "CP_CONNECTOR: Init job handler" 347 << ", printer id: " << printer_info_cloud.printer_id; 348 JobHandlerMap::iterator index = job_handler_map_.find( 349 printer_info_cloud.printer_id); 350 if (index != job_handler_map_.end()) 351 return; // Nothing to do if we already have a job handler for this printer. 352 353 printing::PrinterBasicInfo printer_info; 354 printer_data->GetString(kNameValue, &printer_info.printer_name); 355 DCHECK(!printer_info.printer_name.empty()); 356 printer_data->GetString(kPrinterDescValue, 357 &printer_info.printer_description); 358 // Printer status is a string value which actually contains an integer. 359 std::string printer_status; 360 if (printer_data->GetString(kPrinterStatusValue, &printer_status)) { 361 base::StringToInt(printer_status, &printer_info.printer_status); 362 } 363 printer_data->GetString(kPrinterCapsHashValue, 364 &printer_info_cloud.caps_hash); 365 ListValue* tags_list = NULL; 366 if (printer_data->GetList(kTagsValue, &tags_list) && tags_list) { 367 for (size_t index = 0; index < tags_list->GetSize(); index++) { 368 std::string tag; 369 if (tags_list->GetString(index, &tag) && 370 StartsWithASCII(tag, kCloudPrintServiceTagsHashTagName, false)) { 371 std::vector<std::string> tag_parts; 372 base::SplitStringDontTrim(tag, '=', &tag_parts); 373 DCHECK_EQ(tag_parts.size(), 2U); 374 if (tag_parts.size() == 2) 375 printer_info_cloud.tags_hash = tag_parts[1]; 376 } 377 } 378 } 379 scoped_refptr<PrinterJobHandler> job_handler; 380 job_handler = new PrinterJobHandler(printer_info, 381 printer_info_cloud, 382 settings_.server_url(), 383 print_system_.get(), 384 this); 385 job_handler_map_[printer_info_cloud.printer_id] = job_handler; 386 job_handler->Initialize(); 387 } 388 389 void CloudPrintConnector::AddPendingAvailableTask() { 390 PendingTask task; 391 task.type = PENDING_PRINTERS_AVAILABLE; 392 AddPendingTask(task); 393 } 394 395 void CloudPrintConnector::AddPendingDeleteTask(const std::string& id) { 396 PendingTask task; 397 task.type = PENDING_PRINTER_DELETE; 398 task.printer_id = id; 399 AddPendingTask(task); 400 } 401 402 void CloudPrintConnector::AddPendingRegisterTask( 403 const printing::PrinterBasicInfo& info) { 404 PendingTask task; 405 task.type = PENDING_PRINTER_REGISTER; 406 task.printer_info = info; 407 AddPendingTask(task); 408 } 409 410 void CloudPrintConnector::AddPendingTask(const PendingTask& task) { 411 pending_tasks_.push_back(task); 412 // If this is the only pending task, we need to start the process. 413 if (pending_tasks_.size() == 1) { 414 base::MessageLoop::current()->PostTask( 415 FROM_HERE, base::Bind(&CloudPrintConnector::ProcessPendingTask, this)); 416 } 417 } 418 419 void CloudPrintConnector::ProcessPendingTask() { 420 if (!IsRunning()) 421 return; // Orphant call. 422 if (pending_tasks_.size() == 0) 423 return; // No peding tasks. 424 425 PendingTask task = pending_tasks_.front(); 426 427 switch (task.type) { 428 case PENDING_PRINTERS_AVAILABLE : 429 OnPrintersAvailable(); 430 break; 431 case PENDING_PRINTER_REGISTER : 432 OnPrinterRegister(task.printer_info); 433 break; 434 case PENDING_PRINTER_DELETE : 435 OnPrinterDelete(task.printer_id); 436 break; 437 default: 438 NOTREACHED(); 439 } 440 } 441 442 void CloudPrintConnector::ContinuePendingTaskProcessing() { 443 if (pending_tasks_.size() == 0) 444 return; // No pending tasks. 445 446 // Delete current task and repost if we have more task available. 447 pending_tasks_.pop_front(); 448 if (pending_tasks_.size() != 0) { 449 base::MessageLoop::current()->PostTask( 450 FROM_HERE, base::Bind(&CloudPrintConnector::ProcessPendingTask, this)); 451 } 452 } 453 454 void CloudPrintConnector::OnPrintersAvailable() { 455 GURL printer_list_url = GetUrlForPrinterList( 456 settings_.server_url(), settings_.proxy_id()); 457 StartGetRequest(printer_list_url, 458 kCloudPrintRegisterMaxRetryCount, 459 &CloudPrintConnector::HandlePrinterListResponse); 460 } 461 462 void CloudPrintConnector::OnPrinterRegister( 463 const printing::PrinterBasicInfo& info) { 464 for (JobHandlerMap::iterator it = job_handler_map_.begin(); 465 it != job_handler_map_.end(); ++it) { 466 if (IsSamePrinter(it->second->GetPrinterName(), info.printer_name)) { 467 // Printer already registered, continue to the next task. 468 ContinuePendingTaskProcessing(); 469 return; 470 } 471 } 472 473 // Asynchronously fetch the printer caps and defaults. The story will 474 // continue in OnReceivePrinterCaps. 475 print_system_->GetPrinterCapsAndDefaults( 476 info.printer_name.c_str(), 477 base::Bind(&CloudPrintConnector::OnReceivePrinterCaps, 478 base::Unretained(this))); 479 } 480 481 void CloudPrintConnector::OnPrinterDelete(const std::string& printer_id) { 482 // Remove corresponding printer job handler. 483 JobHandlerMap::iterator it = job_handler_map_.find(printer_id); 484 if (it != job_handler_map_.end()) { 485 it->second->Shutdown(); 486 job_handler_map_.erase(it); 487 } 488 489 // TODO(gene): We probably should not try indefinitely here. Just once or 490 // twice should be enough. 491 // Bug: http://code.google.com/p/chromium/issues/detail?id=101850 492 GURL url = GetUrlForPrinterDelete( 493 settings_.server_url(), printer_id, "printer_deleted"); 494 StartGetRequest(url, 495 kCloudPrintAPIMaxRetryCount, 496 &CloudPrintConnector::HandlePrinterDeleteResponse); 497 } 498 499 void CloudPrintConnector::OnReceivePrinterCaps( 500 bool succeeded, 501 const std::string& printer_name, 502 const printing::PrinterCapsAndDefaults& caps_and_defaults) { 503 if (!IsRunning()) 504 return; // Orphant call. 505 DCHECK(pending_tasks_.size() > 0 && 506 pending_tasks_.front().type == PENDING_PRINTER_REGISTER); 507 508 if (!succeeded) { 509 LOG(ERROR) << "CP_CONNECTOR: Failed to get printer info" 510 << ", printer name: " << printer_name; 511 // This printer failed to register, notify the server of this failure. 512 string16 printer_name_utf16 = UTF8ToUTF16(printer_name); 513 std::string status_message = l10n_util::GetStringFUTF8( 514 IDS_CLOUD_PRINT_REGISTER_PRINTER_FAILED, 515 printer_name_utf16, 516 l10n_util::GetStringUTF16(IDS_GOOGLE_CLOUD_PRINT)); 517 ReportUserMessage(kGetPrinterCapsFailedMessageId, status_message); 518 519 ContinuePendingTaskProcessing(); // Skip this printer registration. 520 return; 521 } 522 523 const printing::PrinterBasicInfo& info = pending_tasks_.front().printer_info; 524 DCHECK(IsSamePrinter(info.printer_name, printer_name)); 525 526 std::string mime_boundary; 527 CreateMimeBoundaryForUpload(&mime_boundary); 528 std::string post_data; 529 530 net::AddMultipartValueForUpload(kProxyIdValue, 531 settings_.proxy_id(), mime_boundary, std::string(), &post_data); 532 net::AddMultipartValueForUpload(kPrinterNameValue, 533 info.printer_name, mime_boundary, std::string(), &post_data); 534 net::AddMultipartValueForUpload(kPrinterDescValue, 535 info.printer_description, mime_boundary, std::string(), &post_data); 536 net::AddMultipartValueForUpload(kPrinterStatusValue, 537 base::StringPrintf("%d", info.printer_status), 538 mime_boundary, std::string(), &post_data); 539 post_data += GetPostDataForPrinterInfo(info, mime_boundary); 540 net::AddMultipartValueForUpload(kPrinterCapsValue, 541 caps_and_defaults.printer_capabilities, mime_boundary, 542 caps_and_defaults.caps_mime_type, &post_data); 543 net::AddMultipartValueForUpload(kPrinterDefaultsValue, 544 caps_and_defaults.printer_defaults, mime_boundary, 545 caps_and_defaults.defaults_mime_type, &post_data); 546 // Send a hash of the printer capabilities to the server. We will use this 547 // later to check if the capabilities have changed 548 net::AddMultipartValueForUpload(kPrinterCapsHashValue, 549 base::MD5String(caps_and_defaults.printer_capabilities), 550 mime_boundary, std::string(), &post_data); 551 net::AddMultipartFinalDelimiterForUpload(mime_boundary, &post_data); 552 std::string mime_type("multipart/form-data; boundary="); 553 mime_type += mime_boundary; 554 555 GURL post_url = GetUrlForPrinterRegistration(settings_.server_url()); 556 StartPostRequest(post_url, 557 kCloudPrintAPIMaxRetryCount, 558 mime_type, 559 post_data, 560 &CloudPrintConnector::HandleRegisterPrinterResponse); 561 } 562 563 bool CloudPrintConnector::IsSamePrinter(const std::string& name1, 564 const std::string& name2) const { 565 return (0 == base::strcasecmp(name1.c_str(), name2.c_str())); 566 } 567 568 } // namespace cloud_print 569