• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, &notify_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