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
36namespace {
37
38// Print system config options.
39const char kCUPSPrintServerURLs[] = "print_server_urls";
40const char kCUPSUpdateTimeoutMs[] = "update_timeout_ms";
41const char kCUPSNotifyDelete[] = "notify_delete";
42const char kCUPSSupportedMimeTipes[] = "supported_mime_types";
43
44// Default mime types supported by CUPS
45// http://www.cups.org/articles.php?L205+TFAQ+Q
46const char kCUPSDefaultSupportedTypes[] =
47    "application/pdf,application/postscript,image/jpeg,image/png,image/gif";
48
49// Time interval to check for printer's updates.
50const int kCheckForPrinterUpdatesMinutes = 5;
51
52// Job update timeout
53const 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.
57const int kDryRunJobId = 0;
58
59}  // namespace
60
61namespace cloud_print {
62
63struct 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
74class 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
118  base::TimeDelta GetUpdateTimeout() const {
119    return update_timeout_;
120  }
121
122  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:
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
174class PrintServerWatcherCUPS
175  : public PrintSystem::PrintServerWatcher {
176 public:
177  explicit PrintServerWatcherCUPS(PrintSystemCUPS* print_system)
178      : print_system_(print_system),
179        delegate_(NULL) {
180  }
181
182  // PrintSystem::PrintServerWatcher implementation.
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
194  virtual bool StopWatching() OVERRIDE {
195    delegate_ = NULL;
196    return true;
197  }
198
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:
215  virtual ~PrintServerWatcherCUPS() {
216    StopWatching();
217  }
218
219 private:
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
245class PrinterWatcherCUPS
246    : public PrintSystem::PrinterWatcher {
247 public:
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.
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
280  virtual bool StopWatching() OVERRIDE{
281    delegate_ = NULL;
282    return true;
283  }
284
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
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
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:
331  virtual ~PrinterWatcherCUPS() {
332    StopWatching();
333  }
334
335 private:
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
368class JobSpoolerCUPS : public PrintSystem::JobSpooler {
369 public:
370  explicit JobSpoolerCUPS(PrintSystemCUPS* print_system)
371      : print_system_(print_system) {
372    DCHECK(print_system_.get());
373  }
374
375  // PrintSystem::JobSpooler implementation.
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
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:
404  virtual ~JobSpoolerCUPS() {}
405
406 private:
407  scoped_refptr<PrintSystemCUPS> print_system_;
408
409  DISALLOW_COPY_AND_ASSIGN(JobSpoolerCUPS);
410};
411
412PrintSystemCUPS::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
443void 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
460void 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
482PrintSystem::PrintSystemResult PrintSystemCUPS::Init() {
483  UpdatePrinters();
484  initialized_ = true;
485  return PrintSystemResult(true, std::string());
486}
487
488void 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
513PrintSystem::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
528void 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
542bool PrintSystemCUPS::IsValidPrinter(const std::string& printer_name) {
543  return GetPrinterInfo(printer_name, NULL);
544}
545
546bool 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.
558bool 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
581bool 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
610bool 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
687bool 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
712PrintSystem::PrintServerWatcher*
713PrintSystemCUPS::CreatePrintServerWatcher() {
714  DCHECK(initialized_);
715  return new PrintServerWatcherCUPS(this);
716}
717
718PrintSystem::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
725PrintSystem::JobSpooler* PrintSystemCUPS::CreateJobSpooler() {
726  DCHECK(initialized_);
727  return new JobSpoolerCUPS(this);
728}
729
730bool PrintSystemCUPS::UseCddAndCjt() {
731  return false;
732}
733
734std::string PrintSystemCUPS::GetSupportedMimeTypes() {
735  return supported_mime_types_;
736}
737
738scoped_refptr<PrintSystem> PrintSystem::CreateInstance(
739    const base::DictionaryValue* print_system_settings) {
740  return new PrintSystemCUPS(print_system_settings);
741}
742
743int 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
757int 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
769PlatformJobId 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
829std::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
843PrintServerInfoCUPS* 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
873void 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