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/print_system.h"
6
7 #include <cups/cups.h>
8 #include <dlfcn.h>
9 #include <errno.h>
10 #include <pthread.h>
11
12 #include <algorithm>
13 #include <list>
14 #include <map>
15
16 #include "base/bind.h"
17 #include "base/files/file_path.h"
18 #include "base/json/json_reader.h"
19 #include "base/logging.h"
20 #include "base/md5.h"
21 #include "base/memory/scoped_ptr.h"
22 #include "base/message_loop/message_loop.h"
23 #include "base/rand_util.h"
24 #include "base/strings/string_number_conversions.h"
25 #include "base/strings/string_util.h"
26 #include "base/strings/utf_string_conversions.h"
27 #include "base/values.h"
28 #include "chrome/common/cloud_print/cloud_print_constants.h"
29 #include "chrome/common/crash_keys.h"
30 #include "chrome/service/cloud_print/cloud_print_service_helpers.h"
31 #include "printing/backend/cups_helper.h"
32 #include "printing/backend/print_backend.h"
33 #include "printing/backend/print_backend_consts.h"
34 #include "url/gurl.h"
35
36 namespace {
37
38 // Print system config options.
39 const char kCUPSPrintServerURLs[] = "print_server_urls";
40 const char kCUPSUpdateTimeoutMs[] = "update_timeout_ms";
41 const char kCUPSNotifyDelete[] = "notify_delete";
42 const char kCUPSSupportedMimeTipes[] = "supported_mime_types";
43
44 // Default mime types supported by CUPS
45 // http://www.cups.org/articles.php?L205+TFAQ+Q
46 const char kCUPSDefaultSupportedTypes[] =
47 "application/pdf,application/postscript,image/jpeg,image/png,image/gif";
48
49 // Time interval to check for printer's updates.
50 const int kCheckForPrinterUpdatesMinutes = 5;
51
52 // Job update timeout
53 const int kJobUpdateTimeoutSeconds = 5;
54
55 // Job id for dry run (it should not affect CUPS job ids, since 0 job-id is
56 // invalid in CUPS.
57 const int kDryRunJobId = 0;
58
59 } // namespace
60
61 namespace cloud_print {
62
63 struct PrintServerInfoCUPS {
64 GURL url;
65 scoped_refptr<printing::PrintBackend> backend;
66 printing::PrinterList printers;
67 // CapsMap cache PPD until the next update and give a fast access to it by
68 // printer name. PPD request is relatively expensive and this should minimize
69 // the number of requests.
70 typedef std::map<std::string, printing::PrinterCapsAndDefaults> CapsMap;
71 CapsMap caps_cache;
72 };
73
74 class PrintSystemCUPS : public PrintSystem {
75 public:
76 explicit PrintSystemCUPS(const base::DictionaryValue* print_system_settings);
77
78 // PrintSystem implementation.
79 virtual PrintSystemResult Init() OVERRIDE;
80 virtual PrintSystem::PrintSystemResult EnumeratePrinters(
81 printing::PrinterList* printer_list) OVERRIDE;
82 virtual void GetPrinterCapsAndDefaults(
83 const std::string& printer_name,
84 const PrinterCapsAndDefaultsCallback& callback) OVERRIDE;
85 virtual bool IsValidPrinter(const std::string& printer_name) OVERRIDE;
86 virtual bool ValidatePrintTicket(
87 const std::string& printer_name,
88 const std::string& print_ticket_data,
89 const std::string& print_ticket_mime_type) OVERRIDE;
90 virtual bool GetJobDetails(const std::string& printer_name,
91 PlatformJobId job_id,
92 PrintJobDetails *job_details) OVERRIDE;
93 virtual PrintSystem::PrintServerWatcher* CreatePrintServerWatcher() OVERRIDE;
94 virtual PrintSystem::PrinterWatcher* CreatePrinterWatcher(
95 const std::string& printer_name) OVERRIDE;
96 virtual PrintSystem::JobSpooler* CreateJobSpooler() OVERRIDE;
97 virtual bool UseCddAndCjt() OVERRIDE;
98 virtual std::string GetSupportedMimeTypes() OVERRIDE;
99
100 // Helper functions.
101 PlatformJobId SpoolPrintJob(const std::string& print_ticket,
102 const base::FilePath& print_data_file_path,
103 const std::string& print_data_mime_type,
104 const std::string& printer_name,
105 const std::string& job_title,
106 const std::vector<std::string>& tags,
107 bool* dry_run);
108 bool GetPrinterInfo(const std::string& printer_name,
109 printing::PrinterBasicInfo* info);
110 bool ParsePrintTicket(const std::string& print_ticket,
111 std::map<std::string, std::string>* options);
112
113 // Synchronous version of GetPrinterCapsAndDefaults.
114 bool GetPrinterCapsAndDefaults(
115 const std::string& printer_name,
116 printing::PrinterCapsAndDefaults* printer_info);
117
GetUpdateTimeout() const118 base::TimeDelta GetUpdateTimeout() const {
119 return update_timeout_;
120 }
121
NotifyDelete() const122 bool NotifyDelete() const {
123 // Notify about deleted printers only when we
124 // fetched printers list without errors.
125 return notify_delete_ && printer_enum_succeeded_;
126 }
127
128 private:
~PrintSystemCUPS()129 virtual ~PrintSystemCUPS() {}
130
131 // Following functions are wrappers around corresponding CUPS functions.
132 // <functions>2() are called when print server is specified, and plain
133 // version in another case. There is an issue specifing CUPS_HTTP_DEFAULT
134 // in the <functions>2(), it does not work in CUPS prior to 1.4.
135 int GetJobs(cups_job_t** jobs, const GURL& url,
136 http_encryption_t encryption, const char* name,
137 int myjobs, int whichjobs);
138 int PrintFile(const GURL& url, http_encryption_t encryption,
139 const char* name, const char* filename,
140 const char* title, int num_options, cups_option_t* options);
141
142 void InitPrintBackends(const base::DictionaryValue* print_system_settings);
143 void AddPrintServer(const std::string& url);
144
145 void UpdatePrinters();
146
147 // Full name contains print server url:port and printer name. Short name
148 // is the name of the printer in the CUPS server.
149 std::string MakeFullPrinterName(const GURL& url,
150 const std::string& short_printer_name);
151 PrintServerInfoCUPS* FindServerByFullName(
152 const std::string& full_printer_name, std::string* short_printer_name);
153
154 // Helper method to invoke a PrinterCapsAndDefaultsCallback.
155 static void RunCapsCallback(
156 const PrinterCapsAndDefaultsCallback& callback,
157 bool succeeded,
158 const std::string& printer_name,
159 const printing::PrinterCapsAndDefaults& printer_info);
160
161 // PrintServerList contains information about all print servers and backends
162 // this proxy is connected to.
163 typedef std::list<PrintServerInfoCUPS> PrintServerList;
164 PrintServerList print_servers_;
165
166 base::TimeDelta update_timeout_;
167 bool initialized_;
168 bool printer_enum_succeeded_;
169 bool notify_delete_;
170 http_encryption_t cups_encryption_;
171 std::string supported_mime_types_;
172 };
173
174 class PrintServerWatcherCUPS
175 : public PrintSystem::PrintServerWatcher {
176 public:
PrintServerWatcherCUPS(PrintSystemCUPS * print_system)177 explicit PrintServerWatcherCUPS(PrintSystemCUPS* print_system)
178 : print_system_(print_system),
179 delegate_(NULL) {
180 }
181
182 // PrintSystem::PrintServerWatcher implementation.
StartWatching(PrintSystem::PrintServerWatcher::Delegate * delegate)183 virtual bool StartWatching(
184 PrintSystem::PrintServerWatcher::Delegate* delegate) OVERRIDE {
185 delegate_ = delegate;
186 printers_hash_ = GetPrintersHash();
187 base::MessageLoop::current()->PostDelayedTask(
188 FROM_HERE,
189 base::Bind(&PrintServerWatcherCUPS::CheckForUpdates, this),
190 print_system_->GetUpdateTimeout());
191 return true;
192 }
193
StopWatching()194 virtual bool StopWatching() OVERRIDE {
195 delegate_ = NULL;
196 return true;
197 }
198
CheckForUpdates()199 void CheckForUpdates() {
200 if (delegate_ == NULL)
201 return; // Orphan call. We have been stopped already.
202 VLOG(1) << "CP_CUPS: Checking for new printers";
203 std::string new_hash = GetPrintersHash();
204 if (printers_hash_ != new_hash) {
205 printers_hash_ = new_hash;
206 delegate_->OnPrinterAdded();
207 }
208 base::MessageLoop::current()->PostDelayedTask(
209 FROM_HERE,
210 base::Bind(&PrintServerWatcherCUPS::CheckForUpdates, this),
211 print_system_->GetUpdateTimeout());
212 }
213
214 protected:
~PrintServerWatcherCUPS()215 virtual ~PrintServerWatcherCUPS() {
216 StopWatching();
217 }
218
219 private:
GetPrintersHash()220 std::string GetPrintersHash() {
221 printing::PrinterList printer_list;
222 print_system_->EnumeratePrinters(&printer_list);
223
224 // Sort printer names.
225 std::vector<std::string> printers;
226 printing::PrinterList::iterator it;
227 for (it = printer_list.begin(); it != printer_list.end(); ++it)
228 printers.push_back(it->printer_name);
229 std::sort(printers.begin(), printers.end());
230
231 std::string to_hash;
232 for (size_t i = 0; i < printers.size(); i++)
233 to_hash += printers[i];
234
235 return base::MD5String(to_hash);
236 }
237
238 scoped_refptr<PrintSystemCUPS> print_system_;
239 PrintSystem::PrintServerWatcher::Delegate* delegate_;
240 std::string printers_hash_;
241
242 DISALLOW_COPY_AND_ASSIGN(PrintServerWatcherCUPS);
243 };
244
245 class PrinterWatcherCUPS
246 : public PrintSystem::PrinterWatcher {
247 public:
PrinterWatcherCUPS(PrintSystemCUPS * print_system,const std::string & printer_name)248 PrinterWatcherCUPS(PrintSystemCUPS* print_system,
249 const std::string& printer_name)
250 : printer_name_(printer_name),
251 delegate_(NULL),
252 print_system_(print_system) {
253 }
254
255 // PrintSystem::PrinterWatcher implementation.
StartWatching(PrintSystem::PrinterWatcher::Delegate * delegate)256 virtual bool StartWatching(
257 PrintSystem::PrinterWatcher::Delegate* delegate) OVERRIDE{
258 scoped_refptr<printing::PrintBackend> print_backend(
259 printing::PrintBackend::CreateInstance(NULL));
260 crash_keys::ScopedPrinterInfo crash_key(
261 print_backend->GetPrinterDriverInfo(printer_name_));
262 if (delegate_ != NULL)
263 StopWatching();
264 delegate_ = delegate;
265 settings_hash_ = GetSettingsHash();
266 // Schedule next job status update.
267 base::MessageLoop::current()->PostDelayedTask(
268 FROM_HERE,
269 base::Bind(&PrinterWatcherCUPS::JobStatusUpdate, this),
270 base::TimeDelta::FromSeconds(kJobUpdateTimeoutSeconds));
271 // Schedule next printer check.
272 // TODO(gene): Randomize time for the next printer update.
273 base::MessageLoop::current()->PostDelayedTask(
274 FROM_HERE,
275 base::Bind(&PrinterWatcherCUPS::PrinterUpdate, this),
276 print_system_->GetUpdateTimeout());
277 return true;
278 }
279
StopWatching()280 virtual bool StopWatching() OVERRIDE{
281 delegate_ = NULL;
282 return true;
283 }
284
GetCurrentPrinterInfo(printing::PrinterBasicInfo * printer_info)285 virtual bool GetCurrentPrinterInfo(
286 printing::PrinterBasicInfo* printer_info) OVERRIDE {
287 DCHECK(printer_info);
288 return print_system_->GetPrinterInfo(printer_name_, printer_info);
289 }
290
JobStatusUpdate()291 void JobStatusUpdate() {
292 if (delegate_ == NULL)
293 return; // Orphan call. We have been stopped already.
294 // For CUPS proxy, we are going to fire OnJobChanged notification
295 // periodically. Higher level will check if there are any outstanding
296 // jobs for this printer and check their status. If printer has no
297 // outstanding jobs, OnJobChanged() will do nothing.
298 delegate_->OnJobChanged();
299 base::MessageLoop::current()->PostDelayedTask(
300 FROM_HERE,
301 base::Bind(&PrinterWatcherCUPS::JobStatusUpdate, this),
302 base::TimeDelta::FromSeconds(kJobUpdateTimeoutSeconds));
303 }
304
PrinterUpdate()305 void PrinterUpdate() {
306 if (delegate_ == NULL)
307 return; // Orphan call. We have been stopped already.
308 VLOG(1) << "CP_CUPS: Checking for updates"
309 << ", printer name: " << printer_name_;
310 if (print_system_->NotifyDelete() &&
311 !print_system_->IsValidPrinter(printer_name_)) {
312 delegate_->OnPrinterDeleted();
313 VLOG(1) << "CP_CUPS: Printer deleted"
314 << ", printer name: " << printer_name_;
315 } else {
316 std::string new_hash = GetSettingsHash();
317 if (settings_hash_ != new_hash) {
318 settings_hash_ = new_hash;
319 delegate_->OnPrinterChanged();
320 VLOG(1) << "CP_CUPS: Printer configuration changed"
321 << ", printer name: " << printer_name_;
322 }
323 }
324 base::MessageLoop::current()->PostDelayedTask(
325 FROM_HERE,
326 base::Bind(&PrinterWatcherCUPS::PrinterUpdate, this),
327 print_system_->GetUpdateTimeout());
328 }
329
330 protected:
~PrinterWatcherCUPS()331 virtual ~PrinterWatcherCUPS() {
332 StopWatching();
333 }
334
335 private:
GetSettingsHash()336 std::string GetSettingsHash() {
337 printing::PrinterBasicInfo info;
338 if (!print_system_->GetPrinterInfo(printer_name_, &info))
339 return std::string();
340
341 printing::PrinterCapsAndDefaults caps;
342 if (!print_system_->GetPrinterCapsAndDefaults(printer_name_, &caps))
343 return std::string();
344
345 std::string to_hash(info.printer_name);
346 to_hash += info.printer_description;
347 std::map<std::string, std::string>::const_iterator it;
348 for (it = info.options.begin(); it != info.options.end(); ++it) {
349 to_hash += it->first;
350 to_hash += it->second;
351 }
352
353 to_hash += caps.printer_capabilities;
354 to_hash += caps.caps_mime_type;
355 to_hash += caps.printer_defaults;
356 to_hash += caps.defaults_mime_type;
357
358 return base::MD5String(to_hash);
359 }
360 std::string printer_name_;
361 PrintSystem::PrinterWatcher::Delegate* delegate_;
362 scoped_refptr<PrintSystemCUPS> print_system_;
363 std::string settings_hash_;
364
365 DISALLOW_COPY_AND_ASSIGN(PrinterWatcherCUPS);
366 };
367
368 class JobSpoolerCUPS : public PrintSystem::JobSpooler {
369 public:
JobSpoolerCUPS(PrintSystemCUPS * print_system)370 explicit JobSpoolerCUPS(PrintSystemCUPS* print_system)
371 : print_system_(print_system) {
372 DCHECK(print_system_.get());
373 }
374
375 // PrintSystem::JobSpooler implementation.
Spool(const std::string & print_ticket,const std::string & print_ticket_mime_type,const base::FilePath & print_data_file_path,const std::string & print_data_mime_type,const std::string & printer_name,const std::string & job_title,const std::vector<std::string> & tags,JobSpooler::Delegate * delegate)376 virtual bool Spool(const std::string& print_ticket,
377 const std::string& print_ticket_mime_type,
378 const base::FilePath& print_data_file_path,
379 const std::string& print_data_mime_type,
380 const std::string& printer_name,
381 const std::string& job_title,
382 const std::vector<std::string>& tags,
383 JobSpooler::Delegate* delegate) OVERRIDE{
384 DCHECK(delegate);
385 bool dry_run = false;
386 int job_id = print_system_->SpoolPrintJob(
387 print_ticket, print_data_file_path, print_data_mime_type,
388 printer_name, job_title, tags, &dry_run);
389 base::MessageLoop::current()->PostTask(
390 FROM_HERE,
391 base::Bind(&JobSpoolerCUPS::NotifyDelegate, delegate, job_id, dry_run));
392 return true;
393 }
394
NotifyDelegate(JobSpooler::Delegate * delegate,int job_id,bool dry_run)395 static void NotifyDelegate(JobSpooler::Delegate* delegate,
396 int job_id, bool dry_run) {
397 if (dry_run || job_id)
398 delegate->OnJobSpoolSucceeded(job_id);
399 else
400 delegate->OnJobSpoolFailed();
401 }
402
403 protected:
~JobSpoolerCUPS()404 virtual ~JobSpoolerCUPS() {}
405
406 private:
407 scoped_refptr<PrintSystemCUPS> print_system_;
408
409 DISALLOW_COPY_AND_ASSIGN(JobSpoolerCUPS);
410 };
411
PrintSystemCUPS(const base::DictionaryValue * print_system_settings)412 PrintSystemCUPS::PrintSystemCUPS(
413 const base::DictionaryValue* print_system_settings)
414 : update_timeout_(base::TimeDelta::FromMinutes(
415 kCheckForPrinterUpdatesMinutes)),
416 initialized_(false),
417 printer_enum_succeeded_(false),
418 notify_delete_(true),
419 cups_encryption_(HTTP_ENCRYPT_NEVER),
420 supported_mime_types_(kCUPSDefaultSupportedTypes) {
421 if (print_system_settings) {
422 int timeout;
423 if (print_system_settings->GetInteger(kCUPSUpdateTimeoutMs, &timeout))
424 update_timeout_ = base::TimeDelta::FromMilliseconds(timeout);
425
426 int encryption;
427 if (print_system_settings->GetInteger(kCUPSEncryption, &encryption))
428 cups_encryption_ =
429 static_cast<http_encryption_t>(encryption);
430
431 bool notify_delete = true;
432 if (print_system_settings->GetBoolean(kCUPSNotifyDelete, ¬ify_delete))
433 notify_delete_ = notify_delete;
434
435 std::string types;
436 if (print_system_settings->GetString(kCUPSSupportedMimeTipes, &types))
437 supported_mime_types_ = types;
438 }
439
440 InitPrintBackends(print_system_settings);
441 }
442
InitPrintBackends(const base::DictionaryValue * print_system_settings)443 void PrintSystemCUPS::InitPrintBackends(
444 const base::DictionaryValue* print_system_settings) {
445 const base::ListValue* url_list;
446 if (print_system_settings &&
447 print_system_settings->GetList(kCUPSPrintServerURLs, &url_list)) {
448 for (size_t i = 0; i < url_list->GetSize(); i++) {
449 std::string print_server_url;
450 if (url_list->GetString(i, &print_server_url))
451 AddPrintServer(print_server_url);
452 }
453 }
454
455 // If server list is empty, use default print server.
456 if (print_servers_.empty())
457 AddPrintServer(std::string());
458 }
459
AddPrintServer(const std::string & url)460 void PrintSystemCUPS::AddPrintServer(const std::string& url) {
461 if (url.empty())
462 LOG(WARNING) << "No print server specified. Using default print server.";
463
464 // Get Print backend for the specific print server.
465 base::DictionaryValue backend_settings;
466 backend_settings.SetString(kCUPSPrintServerURL, url);
467
468 // Make CUPS requests non-blocking.
469 backend_settings.SetString(kCUPSBlocking, kValueFalse);
470
471 // Set encryption for backend.
472 backend_settings.SetInteger(kCUPSEncryption, cups_encryption_);
473
474 PrintServerInfoCUPS print_server;
475 print_server.backend =
476 printing::PrintBackend::CreateInstance(&backend_settings);
477 print_server.url = GURL(url.c_str());
478
479 print_servers_.push_back(print_server);
480 }
481
Init()482 PrintSystem::PrintSystemResult PrintSystemCUPS::Init() {
483 UpdatePrinters();
484 initialized_ = true;
485 return PrintSystemResult(true, std::string());
486 }
487
UpdatePrinters()488 void PrintSystemCUPS::UpdatePrinters() {
489 PrintServerList::iterator it;
490 printer_enum_succeeded_ = true;
491 for (it = print_servers_.begin(); it != print_servers_.end(); ++it) {
492 if (!it->backend->EnumeratePrinters(&it->printers))
493 printer_enum_succeeded_ = false;
494 it->caps_cache.clear();
495 printing::PrinterList::iterator printer_it;
496 for (printer_it = it->printers.begin();
497 printer_it != it->printers.end(); ++printer_it) {
498 printer_it->printer_name = MakeFullPrinterName(it->url,
499 printer_it->printer_name);
500 }
501 VLOG(1) << "CP_CUPS: Updated printers list"
502 << ", server: " << it->url
503 << ", # of printers: " << it->printers.size();
504 }
505
506 // Schedule next update.
507 base::MessageLoop::current()->PostDelayedTask(
508 FROM_HERE,
509 base::Bind(&PrintSystemCUPS::UpdatePrinters, this),
510 GetUpdateTimeout());
511 }
512
EnumeratePrinters(printing::PrinterList * printer_list)513 PrintSystem::PrintSystemResult PrintSystemCUPS::EnumeratePrinters(
514 printing::PrinterList* printer_list) {
515 DCHECK(initialized_);
516 printer_list->clear();
517 PrintServerList::iterator it;
518 for (it = print_servers_.begin(); it != print_servers_.end(); ++it) {
519 printer_list->insert(printer_list->end(),
520 it->printers.begin(), it->printers.end());
521 }
522 VLOG(1) << "CP_CUPS: Total printers enumerated: " << printer_list->size();
523 // TODO(sanjeevr): Maybe some day we want to report the actual server names
524 // for which the enumeration failed.
525 return PrintSystemResult(printer_enum_succeeded_, std::string());
526 }
527
GetPrinterCapsAndDefaults(const std::string & printer_name,const PrinterCapsAndDefaultsCallback & callback)528 void PrintSystemCUPS::GetPrinterCapsAndDefaults(
529 const std::string& printer_name,
530 const PrinterCapsAndDefaultsCallback& callback) {
531 printing::PrinterCapsAndDefaults printer_info;
532 bool succeeded = GetPrinterCapsAndDefaults(printer_name, &printer_info);
533 base::MessageLoop::current()->PostTask(
534 FROM_HERE,
535 base::Bind(&PrintSystemCUPS::RunCapsCallback,
536 callback,
537 succeeded,
538 printer_name,
539 printer_info));
540 }
541
IsValidPrinter(const std::string & printer_name)542 bool PrintSystemCUPS::IsValidPrinter(const std::string& printer_name) {
543 return GetPrinterInfo(printer_name, NULL);
544 }
545
ValidatePrintTicket(const std::string & printer_name,const std::string & print_ticket_data,const std::string & print_ticket_mime_type)546 bool PrintSystemCUPS::ValidatePrintTicket(
547 const std::string& printer_name,
548 const std::string& print_ticket_data,
549 const std::string& print_ticket_mime_type) {
550 DCHECK(initialized_);
551 scoped_ptr<base::Value> ticket_value(
552 base::JSONReader::Read(print_ticket_data));
553 return ticket_value != NULL &&
554 ticket_value->IsType(base::Value::TYPE_DICTIONARY);
555 }
556
557 // Print ticket on linux is a JSON string containing only one dictionary.
ParsePrintTicket(const std::string & print_ticket,std::map<std::string,std::string> * options)558 bool PrintSystemCUPS::ParsePrintTicket(
559 const std::string& print_ticket,
560 std::map<std::string, std::string>* options) {
561 DCHECK(options);
562 scoped_ptr<base::Value> ticket_value(base::JSONReader::Read(print_ticket));
563 if (ticket_value == NULL ||
564 !ticket_value->IsType(base::Value::TYPE_DICTIONARY)) {
565 return false;
566 }
567
568 options->clear();
569 base::DictionaryValue* ticket_dict =
570 static_cast<base::DictionaryValue*>(ticket_value.get());
571 for (base::DictionaryValue::Iterator it(*ticket_dict); !it.IsAtEnd();
572 it.Advance()) {
573 std::string value;
574 if (it.value().GetAsString(&value))
575 (*options)[it.key()] = value;
576 }
577
578 return true;
579 }
580
GetPrinterCapsAndDefaults(const std::string & printer_name,printing::PrinterCapsAndDefaults * printer_info)581 bool PrintSystemCUPS::GetPrinterCapsAndDefaults(
582 const std::string& printer_name,
583 printing::PrinterCapsAndDefaults* printer_info) {
584 DCHECK(initialized_);
585 std::string short_printer_name;
586 PrintServerInfoCUPS* server_info =
587 FindServerByFullName(printer_name, &short_printer_name);
588 if (!server_info)
589 return false;
590
591 PrintServerInfoCUPS::CapsMap::iterator caps_it =
592 server_info->caps_cache.find(printer_name);
593 if (caps_it != server_info->caps_cache.end()) {
594 *printer_info = caps_it->second;
595 return true;
596 }
597
598 // TODO(gene): Retry multiple times in case of error.
599 crash_keys::ScopedPrinterInfo crash_key(
600 server_info->backend->GetPrinterDriverInfo(short_printer_name));
601 if (!server_info->backend->GetPrinterCapsAndDefaults(short_printer_name,
602 printer_info) ) {
603 return false;
604 }
605
606 server_info->caps_cache[printer_name] = *printer_info;
607 return true;
608 }
609
GetJobDetails(const std::string & printer_name,PlatformJobId job_id,PrintJobDetails * job_details)610 bool PrintSystemCUPS::GetJobDetails(const std::string& printer_name,
611 PlatformJobId job_id,
612 PrintJobDetails *job_details) {
613 DCHECK(initialized_);
614 DCHECK(job_details);
615
616 std::string short_printer_name;
617 PrintServerInfoCUPS* server_info =
618 FindServerByFullName(printer_name, &short_printer_name);
619 if (!server_info)
620 return false;
621
622 crash_keys::ScopedPrinterInfo crash_key(
623 server_info->backend->GetPrinterDriverInfo(short_printer_name));
624 cups_job_t* jobs = NULL;
625 int num_jobs = GetJobs(&jobs, server_info->url, cups_encryption_,
626 short_printer_name.c_str(), 1, -1);
627 bool error = (num_jobs == 0) && (cupsLastError() > IPP_OK_EVENTS_COMPLETE);
628 if (error) {
629 VLOG(1) << "CP_CUPS: Error getting jobs from CUPS server"
630 << ", printer name:" << printer_name
631 << ", error: " << static_cast<int>(cupsLastError());
632 return false;
633 }
634
635 // Check if the request is for dummy dry run job.
636 // We check this after calling GetJobs API to see if this printer is actually
637 // accessible through CUPS.
638 if (job_id == kDryRunJobId) {
639 job_details->status = PRINT_JOB_STATUS_COMPLETED;
640 VLOG(1) << "CP_CUPS: Dry run job succeeded"
641 << ", printer name: " << printer_name;
642 return true;
643 }
644
645 bool found = false;
646 for (int i = 0; i < num_jobs; i++) {
647 if (jobs[i].id == job_id) {
648 found = true;
649 switch (jobs[i].state) {
650 case IPP_JOB_PENDING :
651 case IPP_JOB_HELD :
652 case IPP_JOB_PROCESSING :
653 job_details->status = PRINT_JOB_STATUS_IN_PROGRESS;
654 break;
655 case IPP_JOB_STOPPED :
656 case IPP_JOB_CANCELLED :
657 case IPP_JOB_ABORTED :
658 job_details->status = PRINT_JOB_STATUS_ERROR;
659 break;
660 case IPP_JOB_COMPLETED :
661 job_details->status = PRINT_JOB_STATUS_COMPLETED;
662 break;
663 default:
664 job_details->status = PRINT_JOB_STATUS_INVALID;
665 }
666 job_details->platform_status_flags = jobs[i].state;
667
668 // We don't have any details on the number of processed pages here.
669 break;
670 }
671 }
672
673 if (found)
674 VLOG(1) << "CP_CUPS: Job found"
675 << ", printer name: " << printer_name
676 << ", cups job id: " << job_id
677 << ", cups job status: " << job_details->status;
678 else
679 LOG(WARNING) << "CP_CUPS: Job not found"
680 << ", printer name: " << printer_name
681 << ", cups job id: " << job_id;
682
683 cupsFreeJobs(num_jobs, jobs);
684 return found;
685 }
686
GetPrinterInfo(const std::string & printer_name,printing::PrinterBasicInfo * info)687 bool PrintSystemCUPS::GetPrinterInfo(const std::string& printer_name,
688 printing::PrinterBasicInfo* info) {
689 DCHECK(initialized_);
690 if (info)
691 VLOG(1) << "CP_CUPS: Getting printer info"
692 << ", printer name: " << printer_name;
693
694 std::string short_printer_name;
695 PrintServerInfoCUPS* server_info =
696 FindServerByFullName(printer_name, &short_printer_name);
697 if (!server_info)
698 return false;
699
700 printing::PrinterList::iterator it;
701 for (it = server_info->printers.begin();
702 it != server_info->printers.end(); ++it) {
703 if (it->printer_name == printer_name) {
704 if (info)
705 *info = *it;
706 return true;
707 }
708 }
709 return false;
710 }
711
712 PrintSystem::PrintServerWatcher*
CreatePrintServerWatcher()713 PrintSystemCUPS::CreatePrintServerWatcher() {
714 DCHECK(initialized_);
715 return new PrintServerWatcherCUPS(this);
716 }
717
CreatePrinterWatcher(const std::string & printer_name)718 PrintSystem::PrinterWatcher* PrintSystemCUPS::CreatePrinterWatcher(
719 const std::string& printer_name) {
720 DCHECK(initialized_);
721 DCHECK(!printer_name.empty());
722 return new PrinterWatcherCUPS(this, printer_name);
723 }
724
CreateJobSpooler()725 PrintSystem::JobSpooler* PrintSystemCUPS::CreateJobSpooler() {
726 DCHECK(initialized_);
727 return new JobSpoolerCUPS(this);
728 }
729
UseCddAndCjt()730 bool PrintSystemCUPS::UseCddAndCjt() {
731 return false;
732 }
733
GetSupportedMimeTypes()734 std::string PrintSystemCUPS::GetSupportedMimeTypes() {
735 return supported_mime_types_;
736 }
737
CreateInstance(const base::DictionaryValue * print_system_settings)738 scoped_refptr<PrintSystem> PrintSystem::CreateInstance(
739 const base::DictionaryValue* print_system_settings) {
740 return new PrintSystemCUPS(print_system_settings);
741 }
742
PrintFile(const GURL & url,http_encryption_t encryption,const char * name,const char * filename,const char * title,int num_options,cups_option_t * options)743 int PrintSystemCUPS::PrintFile(const GURL& url, http_encryption_t encryption,
744 const char* name, const char* filename,
745 const char* title, int num_options,
746 cups_option_t* options) {
747 if (url.is_empty()) { // Use default (local) print server.
748 return cupsPrintFile(name, filename, title, num_options, options);
749 } else {
750 printing::HttpConnectionCUPS http(url, encryption);
751 http.SetBlocking(false);
752 return cupsPrintFile2(http.http(), name, filename,
753 title, num_options, options);
754 }
755 }
756
GetJobs(cups_job_t ** jobs,const GURL & url,http_encryption_t encryption,const char * name,int myjobs,int whichjobs)757 int PrintSystemCUPS::GetJobs(cups_job_t** jobs, const GURL& url,
758 http_encryption_t encryption,
759 const char* name, int myjobs, int whichjobs) {
760 if (url.is_empty()) { // Use default (local) print server.
761 return cupsGetJobs(jobs, name, myjobs, whichjobs);
762 } else {
763 printing::HttpConnectionCUPS http(url, encryption);
764 http.SetBlocking(false);
765 return cupsGetJobs2(http.http(), jobs, name, myjobs, whichjobs);
766 }
767 }
768
SpoolPrintJob(const std::string & print_ticket,const base::FilePath & print_data_file_path,const std::string & print_data_mime_type,const std::string & printer_name,const std::string & job_title,const std::vector<std::string> & tags,bool * dry_run)769 PlatformJobId PrintSystemCUPS::SpoolPrintJob(
770 const std::string& print_ticket,
771 const base::FilePath& print_data_file_path,
772 const std::string& print_data_mime_type,
773 const std::string& printer_name,
774 const std::string& job_title,
775 const std::vector<std::string>& tags,
776 bool* dry_run) {
777 DCHECK(initialized_);
778 VLOG(1) << "CP_CUPS: Spooling print job, printer name: " << printer_name;
779
780 std::string short_printer_name;
781 PrintServerInfoCUPS* server_info =
782 FindServerByFullName(printer_name, &short_printer_name);
783 if (!server_info)
784 return false;
785
786 crash_keys::ScopedPrinterInfo crash_key(
787 server_info->backend->GetPrinterDriverInfo(printer_name));
788
789 // We need to store options as char* string for the duration of the
790 // cupsPrintFile2 call. We'll use map here to store options, since
791 // Dictionary value from JSON parser returns wchat_t.
792 std::map<std::string, std::string> options;
793 bool res = ParsePrintTicket(print_ticket, &options);
794 DCHECK(res); // If print ticket is invalid we still print using defaults.
795
796 // Check if this is a dry run (test) job.
797 *dry_run = IsDryRunJob(tags);
798 if (*dry_run) {
799 VLOG(1) << "CP_CUPS: Dry run job spooled";
800 return kDryRunJobId;
801 }
802
803 std::vector<cups_option_t> cups_options;
804 std::map<std::string, std::string>::iterator it;
805
806 for (it = options.begin(); it != options.end(); ++it) {
807 cups_option_t opt;
808 opt.name = const_cast<char*>(it->first.c_str());
809 opt.value = const_cast<char*>(it->second.c_str());
810 cups_options.push_back(opt);
811 }
812
813 int job_id = PrintFile(server_info->url,
814 cups_encryption_,
815 short_printer_name.c_str(),
816 print_data_file_path.value().c_str(),
817 job_title.c_str(),
818 cups_options.size(),
819 &(cups_options[0]));
820
821 // TODO(alexyu): Output printer id.
822 VLOG(1) << "CP_CUPS: Job spooled"
823 << ", printer name: " << printer_name
824 << ", cups job id: " << job_id;
825
826 return job_id;
827 }
828
MakeFullPrinterName(const GURL & url,const std::string & short_printer_name)829 std::string PrintSystemCUPS::MakeFullPrinterName(
830 const GURL& url, const std::string& short_printer_name) {
831 std::string full_name;
832 full_name += "\\\\";
833 full_name += url.host();
834 if (!url.port().empty()) {
835 full_name += ":";
836 full_name += url.port();
837 }
838 full_name += "\\";
839 full_name += short_printer_name;
840 return full_name;
841 }
842
FindServerByFullName(const std::string & full_printer_name,std::string * short_printer_name)843 PrintServerInfoCUPS* PrintSystemCUPS::FindServerByFullName(
844 const std::string& full_printer_name, std::string* short_printer_name) {
845 size_t front = full_printer_name.find("\\\\");
846 size_t separator = full_printer_name.find("\\", 2);
847 if (front == std::string::npos || separator == std::string::npos) {
848 LOG(WARNING) << "CP_CUPS: Invalid UNC"
849 << ", printer name: " << full_printer_name;
850 return NULL;
851 }
852 std::string server = full_printer_name.substr(2, separator - 2);
853
854 PrintServerList::iterator it;
855 for (it = print_servers_.begin(); it != print_servers_.end(); ++it) {
856 std::string cur_server;
857 cur_server += it->url.host();
858 if (!it->url.port().empty()) {
859 cur_server += ":";
860 cur_server += it->url.port();
861 }
862 if (cur_server == server) {
863 *short_printer_name = full_printer_name.substr(separator + 1);
864 return &(*it);
865 }
866 }
867
868 LOG(WARNING) << "CP_CUPS: Server not found"
869 << ", printer name: " << full_printer_name;
870 return NULL;
871 }
872
RunCapsCallback(const PrinterCapsAndDefaultsCallback & callback,bool succeeded,const std::string & printer_name,const printing::PrinterCapsAndDefaults & printer_info)873 void PrintSystemCUPS::RunCapsCallback(
874 const PrinterCapsAndDefaultsCallback& callback,
875 bool succeeded,
876 const std::string& printer_name,
877 const printing::PrinterCapsAndDefaults& printer_info) {
878 callback.Run(succeeded, printer_name, printer_info);
879 }
880
881 } // namespace cloud_print
882