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 "printing/backend/print_backend.h"
6
7#include "build/build_config.h"
8
9#include <dlfcn.h>
10#include <errno.h>
11#include <pthread.h>
12
13#include "base/debug/leak_annotations.h"
14#include "base/files/file_util.h"
15#include "base/lazy_instance.h"
16#include "base/logging.h"
17#include "base/strings/string_number_conversions.h"
18#include "base/synchronization/lock.h"
19#include "base/values.h"
20#include "printing/backend/cups_helper.h"
21#include "printing/backend/print_backend_consts.h"
22#include "url/gurl.h"
23
24namespace printing {
25
26static const char kCUPSPrinterInfoOpt[] = "printer-info";
27static const char kCUPSPrinterStateOpt[] = "printer-state";
28static const char kCUPSPrinterTypeOpt[] = "printer-type";
29static const char kCUPSPrinterMakeModelOpt[] = "printer-make-and-model";
30
31class PrintBackendCUPS : public PrintBackend {
32 public:
33  PrintBackendCUPS(const GURL& print_server_url,
34                   http_encryption_t encryption, bool blocking);
35
36  // PrintBackend implementation.
37  virtual bool EnumeratePrinters(PrinterList* printer_list) OVERRIDE;
38  virtual std::string GetDefaultPrinterName() OVERRIDE;
39  virtual bool GetPrinterSemanticCapsAndDefaults(
40      const std::string& printer_name,
41      PrinterSemanticCapsAndDefaults* printer_info) OVERRIDE;
42  virtual bool GetPrinterCapsAndDefaults(
43      const std::string& printer_name,
44      PrinterCapsAndDefaults* printer_info) OVERRIDE;
45  virtual std::string GetPrinterDriverInfo(
46      const std::string& printer_name) OVERRIDE;
47  virtual bool IsValidPrinter(const std::string& printer_name) OVERRIDE;
48
49 protected:
50  virtual ~PrintBackendCUPS() {}
51
52 private:
53  // Following functions are wrappers around corresponding CUPS functions.
54  // <functions>2()  are called when print server is specified, and plain
55  // version in another case. There is an issue specifing CUPS_HTTP_DEFAULT
56  // in the <functions>2(), it does not work in CUPS prior to 1.4.
57  int GetDests(cups_dest_t** dests);
58  base::FilePath GetPPD(const char* name);
59
60  GURL print_server_url_;
61  http_encryption_t cups_encryption_;
62  bool blocking_;
63};
64
65PrintBackendCUPS::PrintBackendCUPS(const GURL& print_server_url,
66                                   http_encryption_t encryption,
67                                   bool blocking)
68    : print_server_url_(print_server_url),
69      cups_encryption_(encryption),
70      blocking_(blocking) {
71}
72
73bool PrintBackendCUPS::EnumeratePrinters(PrinterList* printer_list) {
74  DCHECK(printer_list);
75  printer_list->clear();
76
77  cups_dest_t* destinations = NULL;
78  int num_dests = GetDests(&destinations);
79  if ((num_dests == 0) && (cupsLastError() > IPP_OK_EVENTS_COMPLETE)) {
80    VLOG(1) << "CUPS: Error getting printers from CUPS server"
81            << ", server: " << print_server_url_
82            << ", error: " << static_cast<int>(cupsLastError());
83    return false;
84  }
85
86  for (int printer_index = 0; printer_index < num_dests; ++printer_index) {
87    const cups_dest_t& printer = destinations[printer_index];
88
89    // CUPS can have 'printers' that are actually scanners. (not MFC)
90    // At least on Mac. Check for scanners and skip them.
91    const char* type_str = cupsGetOption(kCUPSPrinterTypeOpt,
92        printer.num_options, printer.options);
93    if (type_str != NULL) {
94      int type;
95      if (base::StringToInt(type_str, &type) && (type & CUPS_PRINTER_SCANNER))
96        continue;
97    }
98
99    PrinterBasicInfo printer_info;
100    printer_info.printer_name = printer.name;
101    printer_info.is_default = printer.is_default;
102
103    const char* info = cupsGetOption(kCUPSPrinterInfoOpt,
104        printer.num_options, printer.options);
105    if (info != NULL)
106      printer_info.printer_description = info;
107
108    const char* state = cupsGetOption(kCUPSPrinterStateOpt,
109        printer.num_options, printer.options);
110    if (state != NULL)
111      base::StringToInt(state, &printer_info.printer_status);
112
113    const char* drv_info = cupsGetOption(kCUPSPrinterMakeModelOpt,
114                                         printer.num_options,
115                                         printer.options);
116    if (drv_info)
117      printer_info.options[kDriverInfoTagName] = *drv_info;
118
119    // Store printer options.
120    for (int opt_index = 0; opt_index < printer.num_options; ++opt_index) {
121      printer_info.options[printer.options[opt_index].name] =
122          printer.options[opt_index].value;
123    }
124
125    printer_list->push_back(printer_info);
126  }
127
128  cupsFreeDests(num_dests, destinations);
129
130  VLOG(1) << "CUPS: Enumerated printers"
131          << ", server: " << print_server_url_
132          << ", # of printers: " << printer_list->size();
133  return true;
134}
135
136std::string PrintBackendCUPS::GetDefaultPrinterName() {
137  // Not using cupsGetDefault() because it lies about the default printer.
138  cups_dest_t* dests;
139  int num_dests = GetDests(&dests);
140  cups_dest_t* dest = cupsGetDest(NULL, NULL, num_dests, dests);
141  std::string name = dest ? std::string(dest->name) : std::string();
142  cupsFreeDests(num_dests, dests);
143  return name;
144}
145
146bool PrintBackendCUPS::GetPrinterSemanticCapsAndDefaults(
147    const std::string& printer_name,
148    PrinterSemanticCapsAndDefaults* printer_info) {
149  PrinterCapsAndDefaults info;
150  if (!GetPrinterCapsAndDefaults(printer_name, &info) )
151    return false;
152
153  return ParsePpdCapabilities(
154      printer_name, info.printer_capabilities, printer_info);
155}
156
157bool PrintBackendCUPS::GetPrinterCapsAndDefaults(
158    const std::string& printer_name,
159    PrinterCapsAndDefaults* printer_info) {
160  DCHECK(printer_info);
161
162  VLOG(1) << "CUPS: Getting caps and defaults"
163          << ", printer name: " << printer_name;
164
165  base::FilePath ppd_path(GetPPD(printer_name.c_str()));
166  // In some cases CUPS failed to get ppd file.
167  if (ppd_path.empty()) {
168    LOG(ERROR) << "CUPS: Failed to get PPD, printer name: " << printer_name;
169    return false;
170  }
171
172  std::string content;
173  bool res = base::ReadFileToString(ppd_path, &content);
174
175  base::DeleteFile(ppd_path, false);
176
177  if (res) {
178    printer_info->printer_capabilities.swap(content);
179    printer_info->caps_mime_type = "application/pagemaker";
180    // In CUPS, printer defaults is a part of PPD file. Nothing to upload here.
181    printer_info->printer_defaults.clear();
182    printer_info->defaults_mime_type.clear();
183  }
184
185  return res;
186}
187
188std::string PrintBackendCUPS::GetPrinterDriverInfo(
189    const std::string& printer_name) {
190  cups_dest_t* destinations = NULL;
191  int num_dests = GetDests(&destinations);
192  std::string result;
193  for (int printer_index = 0; printer_index < num_dests; ++printer_index) {
194    const cups_dest_t& printer = destinations[printer_index];
195    if (printer_name == printer.name) {
196      const char* info = cupsGetOption(kCUPSPrinterMakeModelOpt,
197                                       printer.num_options,
198                                       printer.options);
199      if (info)
200        result = *info;
201    }
202  }
203
204  cupsFreeDests(num_dests, destinations);
205  return result;
206}
207
208bool PrintBackendCUPS::IsValidPrinter(const std::string& printer_name) {
209  // This is not very efficient way to get specific printer info. CUPS 1.4
210  // supports cupsGetNamedDest() function. However, CUPS 1.4 is not available
211  // everywhere (for example, it supported from Mac OS 10.6 only).
212  PrinterList printer_list;
213  EnumeratePrinters(&printer_list);
214
215  PrinterList::iterator it;
216  for (it = printer_list.begin(); it != printer_list.end(); ++it)
217    if (it->printer_name == printer_name)
218      return true;
219  return false;
220}
221
222scoped_refptr<PrintBackend> PrintBackend::CreateInstance(
223    const base::DictionaryValue* print_backend_settings) {
224  std::string print_server_url_str, cups_blocking;
225  int encryption = HTTP_ENCRYPT_NEVER;
226  if (print_backend_settings) {
227    print_backend_settings->GetString(kCUPSPrintServerURL,
228                                      &print_server_url_str);
229
230    print_backend_settings->GetString(kCUPSBlocking,
231                                      &cups_blocking);
232
233    print_backend_settings->GetInteger(kCUPSEncryption, &encryption);
234  }
235  GURL print_server_url(print_server_url_str.c_str());
236  return new PrintBackendCUPS(print_server_url,
237                              static_cast<http_encryption_t>(encryption),
238                              cups_blocking == kValueTrue);
239}
240
241int PrintBackendCUPS::GetDests(cups_dest_t** dests) {
242  if (print_server_url_.is_empty()) {  // Use default (local) print server.
243    // GnuTLS has a genuine small memory leak that is easier to annotate
244    // than suppress. See http://crbug.com/176888#c7
245    // In theory any CUPS function can trigger this leak, but in
246    // PrintBackendCUPS, this is the most likely spot.
247    // TODO(earthdok): remove this once the leak is fixed.
248    ANNOTATE_SCOPED_MEMORY_LEAK;
249    return cupsGetDests(dests);
250  } else {
251    HttpConnectionCUPS http(print_server_url_, cups_encryption_);
252    http.SetBlocking(blocking_);
253    return cupsGetDests2(http.http(), dests);
254  }
255}
256
257base::FilePath PrintBackendCUPS::GetPPD(const char* name) {
258  // cupsGetPPD returns a filename stored in a static buffer in CUPS.
259  // Protect this code with lock.
260  CR_DEFINE_STATIC_LOCAL(base::Lock, ppd_lock, ());
261  base::AutoLock ppd_autolock(ppd_lock);
262  base::FilePath ppd_path;
263  const char* ppd_file_path = NULL;
264  if (print_server_url_.is_empty()) {  // Use default (local) print server.
265    ppd_file_path = cupsGetPPD(name);
266    if (ppd_file_path)
267      ppd_path = base::FilePath(ppd_file_path);
268  } else {
269    // cupsGetPPD2 gets stuck sometimes in an infinite time due to network
270    // configuration/issues. To prevent that, use non-blocking http connection
271    // here.
272    // Note: After looking at CUPS sources, it looks like non-blocking
273    // connection will timeout after 10 seconds of no data period. And it will
274    // return the same way as if data was completely and sucessfully downloaded.
275    HttpConnectionCUPS http(print_server_url_, cups_encryption_);
276    http.SetBlocking(blocking_);
277    ppd_file_path = cupsGetPPD2(http.http(), name);
278    // Check if the get full PPD, since non-blocking call may simply return
279    // normally after timeout expired.
280    if (ppd_file_path) {
281      // There is no reliable way right now to detect full and complete PPD
282      // get downloaded. If we reach http timeout, it may simply return
283      // downloaded part as a full response. It might be good enough to check
284      // http->data_remaining or http->_data_remaining, unfortunately http_t
285      // is an internal structure and fields are not exposed in CUPS headers.
286      // httpGetLength or httpGetLength2 returning the full content size.
287      // Comparing file size against that content length might be unreliable
288      // since some http reponses are encoded and content_length > file size.
289      // Let's just check for the obvious CUPS and http errors here.
290      ppd_path = base::FilePath(ppd_file_path);
291      ipp_status_t error_code = cupsLastError();
292      int http_error = httpError(http.http());
293      if (error_code > IPP_OK_EVENTS_COMPLETE || http_error != 0) {
294        LOG(ERROR) << "Error downloading PPD file"
295                   << ", name: " << name
296                   << ", CUPS error: " << static_cast<int>(error_code)
297                   << ", HTTP error: " << http_error;
298        base::DeleteFile(ppd_path, false);
299        ppd_path.clear();
300      }
301    }
302  }
303  return ppd_path;
304}
305
306}  // namespace printing
307