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.
5 #include "printing/backend/print_backend.h"
7 #include "build/build_config.h"
13 #if defined(OS_MACOSX)
14 #include <AvailabilityMacros.h>
19 #include "base/file_util.h"
20 #include "base/lazy_instance.h"
21 #include "base/logging.h"
22 #include "base/string_number_conversions.h"
23 #include "base/synchronization/lock.h"
24 #include "base/values.h"
25 #include "googleurl/src/gurl.h"
26 #include "printing/backend/cups_helper.h"
27 #include "printing/backend/print_backend_consts.h"
29 #if (CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 4)
30 const int CUPS_PRINTER_SCANNER
= 0x2000000; // Scanner-only device
33 #if !defined(OS_MACOSX)
34 GCRY_THREAD_OPTION_PTHREAD_IMPL
;
38 // Init GCrypt library (needed for CUPS) using pthreads.
39 // There exists a bug in CUPS library, where it crashed with: "ath.c:184:
40 // _gcry_ath_mutex_lock: Assertion `*lock == ((ath_mutex_t) 0)' failed."
41 // It happened when multiple threads tried printing simultaneously.
42 // Google search for 'gnutls thread safety' provided a solution that
43 // initialized gcrypt and gnutls.
45 // TODO(phajdan.jr): Remove this after https://bugs.g10code.com/gnupg/issue1197
46 // gets fixed on all Linux distros we support (i.e. when they ship libgcrypt
49 // Initially, we linked with -lgnutls and simply called gnutls_global_init(),
50 // but this did not work well since we build one binary on Ubuntu Hardy and
51 // expect it to run on many Linux distros. (See http://crbug.com/46954)
52 // So instead we use dlopen() and dlsym() to dynamically load and call
53 // gnutls_global_init().
55 class GcryptInitializer
{
63 const char* kGnuTlsFiles
[] = {
68 gcry_control(GCRYCTL_SET_THREAD_CBS
, &gcry_threads_pthread
);
69 for (size_t i
= 0; i
< arraysize(kGnuTlsFiles
); ++i
) {
70 void* gnutls_lib
= dlopen(kGnuTlsFiles
[i
], RTLD_NOW
);
72 VLOG(1) << "Cannot load " << kGnuTlsFiles
[i
];
75 const char* kGnuTlsInitFuncName
= "gnutls_global_init";
76 int (*pgnutls_global_init
)(void) = reinterpret_cast<int(*)()>(
77 dlsym(gnutls_lib
, kGnuTlsInitFuncName
));
78 if (!pgnutls_global_init
) {
79 VLOG(1) << "Could not find " << kGnuTlsInitFuncName
80 << " in " << kGnuTlsFiles
[i
];
83 if ((*pgnutls_global_init
)() != 0)
84 LOG(ERROR
) << "gnutls_global_init() failed";
87 LOG(ERROR
) << "Cannot find libgnutls";
91 base::LazyInstance
<GcryptInitializer
> g_gcrypt_initializer
=
92 LAZY_INSTANCE_INITIALIZER
;
95 #endif // !defined(OS_MACOSX)
99 static const char kCUPSPrinterInfoOpt
[] = "printer-info";
100 static const char kCUPSPrinterStateOpt
[] = "printer-state";
101 static const char kCUPSPrinterTypeOpt
[] = "printer-type";
102 static const char kCUPSPrinterMakeModelOpt
[] = "printer-make-and-model";
104 class PrintBackendCUPS
: public PrintBackend
{
106 PrintBackendCUPS(const GURL
& print_server_url
,
107 http_encryption_t encryption
, bool blocking
);
109 // PrintBackend implementation.
110 virtual bool EnumeratePrinters(PrinterList
* printer_list
) OVERRIDE
;
111 virtual std::string
GetDefaultPrinterName() OVERRIDE
;
112 virtual bool GetPrinterSemanticCapsAndDefaults(
113 const std::string
& printer_name
,
114 PrinterSemanticCapsAndDefaults
* printer_info
) OVERRIDE
;
115 virtual bool GetPrinterCapsAndDefaults(
116 const std::string
& printer_name
,
117 PrinterCapsAndDefaults
* printer_info
) OVERRIDE
;
118 virtual std::string
GetPrinterDriverInfo(
119 const std::string
& printer_name
) OVERRIDE
;
120 virtual bool IsValidPrinter(const std::string
& printer_name
) OVERRIDE
;
123 virtual ~PrintBackendCUPS() {}
126 // Following functions are wrappers around corresponding CUPS functions.
127 // <functions>2() are called when print server is specified, and plain
128 // version in another case. There is an issue specifing CUPS_HTTP_DEFAULT
129 // in the <functions>2(), it does not work in CUPS prior to 1.4.
130 int GetDests(cups_dest_t
** dests
);
131 base::FilePath
GetPPD(const char* name
);
133 GURL print_server_url_
;
134 http_encryption_t cups_encryption_
;
138 PrintBackendCUPS::PrintBackendCUPS(const GURL
& print_server_url
,
139 http_encryption_t encryption
,
141 : print_server_url_(print_server_url
),
142 cups_encryption_(encryption
),
143 blocking_(blocking
) {
146 bool PrintBackendCUPS::EnumeratePrinters(PrinterList
* printer_list
) {
147 DCHECK(printer_list
);
148 printer_list
->clear();
150 cups_dest_t
* destinations
= NULL
;
151 int num_dests
= GetDests(&destinations
);
152 if ((num_dests
== 0) && (cupsLastError() > IPP_OK_EVENTS_COMPLETE
)) {
153 VLOG(1) << "CUPS: Error getting printers from CUPS server"
154 << ", server: " << print_server_url_
155 << ", error: " << static_cast<int>(cupsLastError());
159 for (int printer_index
= 0; printer_index
< num_dests
; printer_index
++) {
160 const cups_dest_t
& printer
= destinations
[printer_index
];
162 // CUPS can have 'printers' that are actually scanners. (not MFC)
163 // At least on Mac. Check for scanners and skip them.
164 const char* type_str
= cupsGetOption(kCUPSPrinterTypeOpt
,
165 printer
.num_options
, printer
.options
);
166 if (type_str
!= NULL
) {
168 if (base::StringToInt(type_str
, &type
) && (type
& CUPS_PRINTER_SCANNER
))
172 PrinterBasicInfo printer_info
;
173 printer_info
.printer_name
= printer
.name
;
174 printer_info
.is_default
= printer
.is_default
;
176 const char* info
= cupsGetOption(kCUPSPrinterInfoOpt
,
177 printer
.num_options
, printer
.options
);
179 printer_info
.printer_description
= info
;
181 const char* state
= cupsGetOption(kCUPSPrinterStateOpt
,
182 printer
.num_options
, printer
.options
);
184 base::StringToInt(state
, &printer_info
.printer_status
);
186 const char* drv_info
= cupsGetOption(kCUPSPrinterMakeModelOpt
,
190 printer_info
.options
[kDriverInfoTagName
] = *drv_info
;
192 // Store printer options.
193 for (int opt_index
= 0; opt_index
< printer
.num_options
; opt_index
++) {
194 printer_info
.options
[printer
.options
[opt_index
].name
] =
195 printer
.options
[opt_index
].value
;
198 printer_list
->push_back(printer_info
);
201 cupsFreeDests(num_dests
, destinations
);
203 VLOG(1) << "CUPS: Enumerated printers"
204 << ", server: " << print_server_url_
205 << ", # of printers: " << printer_list
->size();
209 std::string
PrintBackendCUPS::GetDefaultPrinterName() {
210 // Not using cupsGetDefault() because it lies about the default printer.
212 int num_dests
= GetDests(&dests
);
213 cups_dest_t
* dest
= cupsGetDest(NULL
, NULL
, num_dests
, dests
);
214 std::string name
= dest
? std::string(dest
->name
) : std::string();
215 cupsFreeDests(num_dests
, dests
);
219 bool PrintBackendCUPS::GetPrinterSemanticCapsAndDefaults(
220 const std::string
& printer_name
,
221 PrinterSemanticCapsAndDefaults
* printer_info
) {
222 PrinterCapsAndDefaults info
;
223 if (!GetPrinterCapsAndDefaults(printer_name
, &info
) )
226 return parsePpdCapabilities(
227 printer_name
, info
.printer_capabilities
, printer_info
);
230 bool PrintBackendCUPS::GetPrinterCapsAndDefaults(
231 const std::string
& printer_name
,
232 PrinterCapsAndDefaults
* printer_info
) {
233 DCHECK(printer_info
);
235 VLOG(1) << "CUPS: Getting caps and defaults"
236 << ", printer name: " << printer_name
;
238 base::FilePath
ppd_path(GetPPD(printer_name
.c_str()));
239 // In some cases CUPS failed to get ppd file.
240 if (ppd_path
.empty()) {
241 LOG(ERROR
) << "CUPS: Failed to get PPD"
242 << ", printer name: " << printer_name
;
247 bool res
= file_util::ReadFileToString(ppd_path
, &content
);
249 file_util::Delete(ppd_path
, false);
252 printer_info
->printer_capabilities
.swap(content
);
253 printer_info
->caps_mime_type
= "application/pagemaker";
254 // In CUPS, printer defaults is a part of PPD file. Nothing to upload here.
255 printer_info
->printer_defaults
.clear();
256 printer_info
->defaults_mime_type
.clear();
262 std::string
PrintBackendCUPS::GetPrinterDriverInfo(
263 const std::string
& printer_name
) {
264 cups_dest_t
* destinations
= NULL
;
265 int num_dests
= GetDests(&destinations
);
267 for (int printer_index
= 0; printer_index
< num_dests
; printer_index
++) {
268 const cups_dest_t
& printer
= destinations
[printer_index
];
269 if (printer_name
== printer
.name
) {
270 const char* info
= cupsGetOption(kCUPSPrinterMakeModelOpt
,
278 cupsFreeDests(num_dests
, destinations
);
282 bool PrintBackendCUPS::IsValidPrinter(const std::string
& printer_name
) {
283 // This is not very efficient way to get specific printer info. CUPS 1.4
284 // supports cupsGetNamedDest() function. However, CUPS 1.4 is not available
285 // everywhere (for example, it supported from Mac OS 10.6 only).
286 PrinterList printer_list
;
287 EnumeratePrinters(&printer_list
);
289 PrinterList::iterator it
;
290 for (it
= printer_list
.begin(); it
!= printer_list
.end(); ++it
)
291 if (it
->printer_name
== printer_name
)
296 scoped_refptr
<PrintBackend
> PrintBackend::CreateInstance(
297 const DictionaryValue
* print_backend_settings
) {
298 #if !defined(OS_MACOSX)
299 // Initialize gcrypt library.
300 g_gcrypt_initializer
.Get();
303 std::string print_server_url_str
, cups_blocking
;
304 int encryption
= HTTP_ENCRYPT_NEVER
;
305 if (print_backend_settings
) {
306 print_backend_settings
->GetString(kCUPSPrintServerURL
,
307 &print_server_url_str
);
309 print_backend_settings
->GetString(kCUPSBlocking
,
312 print_backend_settings
->GetInteger(kCUPSEncryption
, &encryption
);
314 GURL
print_server_url(print_server_url_str
.c_str());
315 return new PrintBackendCUPS(print_server_url
,
316 static_cast<http_encryption_t
>(encryption
),
317 cups_blocking
== kValueTrue
);
320 int PrintBackendCUPS::GetDests(cups_dest_t
** dests
) {
321 if (print_server_url_
.is_empty()) { // Use default (local) print server.
322 return cupsGetDests(dests
);
324 HttpConnectionCUPS
http(print_server_url_
, cups_encryption_
);
325 http
.SetBlocking(blocking_
);
326 return cupsGetDests2(http
.http(), dests
);
330 base::FilePath
PrintBackendCUPS::GetPPD(const char* name
) {
331 // cupsGetPPD returns a filename stored in a static buffer in CUPS.
332 // Protect this code with lock.
333 CR_DEFINE_STATIC_LOCAL(base::Lock
, ppd_lock
, ());
334 base::AutoLock
ppd_autolock(ppd_lock
);
335 base::FilePath ppd_path
;
336 const char* ppd_file_path
= NULL
;
337 if (print_server_url_
.is_empty()) { // Use default (local) print server.
338 ppd_file_path
= cupsGetPPD(name
);
340 ppd_path
= base::FilePath(ppd_file_path
);
342 // cupsGetPPD2 gets stuck sometimes in an infinite time due to network
343 // configuration/issues. To prevent that, use non-blocking http connection
345 // Note: After looking at CUPS sources, it looks like non-blocking
346 // connection will timeout after 10 seconds of no data period. And it will
347 // return the same way as if data was completely and sucessfully downloaded.
348 HttpConnectionCUPS
http(print_server_url_
, cups_encryption_
);
349 http
.SetBlocking(blocking_
);
350 ppd_file_path
= cupsGetPPD2(http
.http(), name
);
351 // Check if the get full PPD, since non-blocking call may simply return
352 // normally after timeout expired.
354 // There is no reliable way right now to detect full and complete PPD
355 // get downloaded. If we reach http timeout, it may simply return
356 // downloaded part as a full response. It might be good enough to check
357 // http->data_remaining or http->_data_remaining, unfortunately http_t
358 // is an internal structure and fields are not exposed in CUPS headers.
359 // httpGetLength or httpGetLength2 returning the full content size.
360 // Comparing file size against that content length might be unreliable
361 // since some http reponses are encoded and content_length > file size.
362 // Let's just check for the obvious CUPS and http errors here.
363 ppd_path
= base::FilePath(ppd_file_path
);
364 ipp_status_t error_code
= cupsLastError();
365 int http_error
= httpError(http
.http());
366 if (error_code
> IPP_OK_EVENTS_COMPLETE
|| http_error
!= 0) {
367 LOG(ERROR
) << "Error downloading PPD file"
368 << ", name: " << name
369 << ", CUPS error: " << static_cast<int>(error_code
)
370 << ", HTTP error: " << http_error
;
371 file_util::Delete(ppd_path
, false);
379 } // namespace printing