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 #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"
26 static const char kCUPSPrinterInfoOpt
[] = "printer-info";
27 static const char kCUPSPrinterStateOpt
[] = "printer-state";
28 static const char kCUPSPrinterTypeOpt
[] = "printer-type";
29 static const char kCUPSPrinterMakeModelOpt
[] = "printer-make-and-model";
31 class PrintBackendCUPS
: public PrintBackend
{
33 PrintBackendCUPS(const GURL
& print_server_url
,
34 http_encryption_t encryption
, bool blocking
);
36 // PrintBackend implementation.
37 bool EnumeratePrinters(PrinterList
* printer_list
) override
;
38 std::string
GetDefaultPrinterName() override
;
39 bool GetPrinterSemanticCapsAndDefaults(
40 const std::string
& printer_name
,
41 PrinterSemanticCapsAndDefaults
* printer_info
) override
;
42 bool GetPrinterCapsAndDefaults(const std::string
& printer_name
,
43 PrinterCapsAndDefaults
* printer_info
) override
;
44 std::string
GetPrinterDriverInfo(const std::string
& printer_name
) override
;
45 bool IsValidPrinter(const std::string
& printer_name
) override
;
48 ~PrintBackendCUPS() override
{}
51 // Following functions are wrappers around corresponding CUPS functions.
52 // <functions>2() are called when print server is specified, and plain
53 // version in another case. There is an issue specifing CUPS_HTTP_DEFAULT
54 // in the <functions>2(), it does not work in CUPS prior to 1.4.
55 int GetDests(cups_dest_t
** dests
);
56 base::FilePath
GetPPD(const char* name
);
58 GURL print_server_url_
;
59 http_encryption_t cups_encryption_
;
63 PrintBackendCUPS::PrintBackendCUPS(const GURL
& print_server_url
,
64 http_encryption_t encryption
,
66 : print_server_url_(print_server_url
),
67 cups_encryption_(encryption
),
71 bool PrintBackendCUPS::EnumeratePrinters(PrinterList
* printer_list
) {
73 printer_list
->clear();
75 cups_dest_t
* destinations
= NULL
;
76 int num_dests
= GetDests(&destinations
);
77 if ((num_dests
== 0) && (cupsLastError() > IPP_OK_EVENTS_COMPLETE
)) {
78 VLOG(1) << "CUPS: Error getting printers from CUPS server"
79 << ", server: " << print_server_url_
80 << ", error: " << static_cast<int>(cupsLastError());
84 for (int printer_index
= 0; printer_index
< num_dests
; ++printer_index
) {
85 const cups_dest_t
& printer
= destinations
[printer_index
];
87 // CUPS can have 'printers' that are actually scanners. (not MFC)
88 // At least on Mac. Check for scanners and skip them.
89 const char* type_str
= cupsGetOption(kCUPSPrinterTypeOpt
,
90 printer
.num_options
, printer
.options
);
91 if (type_str
!= NULL
) {
93 if (base::StringToInt(type_str
, &type
) && (type
& CUPS_PRINTER_SCANNER
))
97 PrinterBasicInfo printer_info
;
98 printer_info
.printer_name
= printer
.name
;
99 printer_info
.is_default
= printer
.is_default
;
101 const char* info
= cupsGetOption(kCUPSPrinterInfoOpt
,
102 printer
.num_options
, printer
.options
);
104 printer_info
.printer_description
= info
;
106 const char* state
= cupsGetOption(kCUPSPrinterStateOpt
,
107 printer
.num_options
, printer
.options
);
109 base::StringToInt(state
, &printer_info
.printer_status
);
111 const char* drv_info
= cupsGetOption(kCUPSPrinterMakeModelOpt
,
115 printer_info
.options
[kDriverInfoTagName
] = *drv_info
;
117 // Store printer options.
118 for (int opt_index
= 0; opt_index
< printer
.num_options
; ++opt_index
) {
119 printer_info
.options
[printer
.options
[opt_index
].name
] =
120 printer
.options
[opt_index
].value
;
123 printer_list
->push_back(printer_info
);
126 cupsFreeDests(num_dests
, destinations
);
128 VLOG(1) << "CUPS: Enumerated printers"
129 << ", server: " << print_server_url_
130 << ", # of printers: " << printer_list
->size();
134 std::string
PrintBackendCUPS::GetDefaultPrinterName() {
135 // Not using cupsGetDefault() because it lies about the default printer.
137 int num_dests
= GetDests(&dests
);
138 cups_dest_t
* dest
= cupsGetDest(NULL
, NULL
, num_dests
, dests
);
139 std::string name
= dest
? std::string(dest
->name
) : std::string();
140 cupsFreeDests(num_dests
, dests
);
144 bool PrintBackendCUPS::GetPrinterSemanticCapsAndDefaults(
145 const std::string
& printer_name
,
146 PrinterSemanticCapsAndDefaults
* printer_info
) {
147 PrinterCapsAndDefaults info
;
148 if (!GetPrinterCapsAndDefaults(printer_name
, &info
) )
151 return ParsePpdCapabilities(
152 printer_name
, info
.printer_capabilities
, printer_info
);
155 bool PrintBackendCUPS::GetPrinterCapsAndDefaults(
156 const std::string
& printer_name
,
157 PrinterCapsAndDefaults
* printer_info
) {
158 DCHECK(printer_info
);
160 VLOG(1) << "CUPS: Getting caps and defaults"
161 << ", printer name: " << printer_name
;
163 base::FilePath
ppd_path(GetPPD(printer_name
.c_str()));
164 // In some cases CUPS failed to get ppd file.
165 if (ppd_path
.empty()) {
166 LOG(ERROR
) << "CUPS: Failed to get PPD, printer name: " << printer_name
;
171 bool res
= base::ReadFileToString(ppd_path
, &content
);
173 base::DeleteFile(ppd_path
, false);
176 printer_info
->printer_capabilities
.swap(content
);
177 printer_info
->caps_mime_type
= "application/pagemaker";
178 // In CUPS, printer defaults is a part of PPD file. Nothing to upload here.
179 printer_info
->printer_defaults
.clear();
180 printer_info
->defaults_mime_type
.clear();
186 std::string
PrintBackendCUPS::GetPrinterDriverInfo(
187 const std::string
& printer_name
) {
188 cups_dest_t
* destinations
= NULL
;
189 int num_dests
= GetDests(&destinations
);
191 for (int printer_index
= 0; printer_index
< num_dests
; ++printer_index
) {
192 const cups_dest_t
& printer
= destinations
[printer_index
];
193 if (printer_name
== printer
.name
) {
194 const char* info
= cupsGetOption(kCUPSPrinterMakeModelOpt
,
202 cupsFreeDests(num_dests
, destinations
);
206 bool PrintBackendCUPS::IsValidPrinter(const std::string
& printer_name
) {
207 // This is not very efficient way to get specific printer info. CUPS 1.4
208 // supports cupsGetNamedDest() function. However, CUPS 1.4 is not available
209 // everywhere (for example, it supported from Mac OS 10.6 only).
210 PrinterList printer_list
;
211 EnumeratePrinters(&printer_list
);
213 PrinterList::iterator it
;
214 for (it
= printer_list
.begin(); it
!= printer_list
.end(); ++it
)
215 if (it
->printer_name
== printer_name
)
220 scoped_refptr
<PrintBackend
> PrintBackend::CreateInstance(
221 const base::DictionaryValue
* print_backend_settings
) {
222 std::string print_server_url_str
, cups_blocking
;
223 int encryption
= HTTP_ENCRYPT_NEVER
;
224 if (print_backend_settings
) {
225 print_backend_settings
->GetString(kCUPSPrintServerURL
,
226 &print_server_url_str
);
228 print_backend_settings
->GetString(kCUPSBlocking
,
231 print_backend_settings
->GetInteger(kCUPSEncryption
, &encryption
);
233 GURL
print_server_url(print_server_url_str
.c_str());
234 return new PrintBackendCUPS(print_server_url
,
235 static_cast<http_encryption_t
>(encryption
),
236 cups_blocking
== kValueTrue
);
239 int PrintBackendCUPS::GetDests(cups_dest_t
** dests
) {
240 if (print_server_url_
.is_empty()) { // Use default (local) print server.
241 // GnuTLS has a genuine small memory leak that is easier to annotate
242 // than suppress. See http://crbug.com/176888#c7
243 // In theory any CUPS function can trigger this leak, but in
244 // PrintBackendCUPS, this is the most likely spot.
245 // TODO(earthdok): remove this once the leak is fixed.
246 ANNOTATE_SCOPED_MEMORY_LEAK
;
247 return cupsGetDests(dests
);
249 HttpConnectionCUPS
http(print_server_url_
, cups_encryption_
);
250 http
.SetBlocking(blocking_
);
251 return cupsGetDests2(http
.http(), dests
);
255 base::FilePath
PrintBackendCUPS::GetPPD(const char* name
) {
256 // cupsGetPPD returns a filename stored in a static buffer in CUPS.
257 // Protect this code with lock.
258 CR_DEFINE_STATIC_LOCAL(base::Lock
, ppd_lock
, ());
259 base::AutoLock
ppd_autolock(ppd_lock
);
260 base::FilePath ppd_path
;
261 const char* ppd_file_path
= NULL
;
262 if (print_server_url_
.is_empty()) { // Use default (local) print server.
263 ppd_file_path
= cupsGetPPD(name
);
265 ppd_path
= base::FilePath(ppd_file_path
);
267 // cupsGetPPD2 gets stuck sometimes in an infinite time due to network
268 // configuration/issues. To prevent that, use non-blocking http connection
270 // Note: After looking at CUPS sources, it looks like non-blocking
271 // connection will timeout after 10 seconds of no data period. And it will
272 // return the same way as if data was completely and sucessfully downloaded.
273 HttpConnectionCUPS
http(print_server_url_
, cups_encryption_
);
274 http
.SetBlocking(blocking_
);
275 ppd_file_path
= cupsGetPPD2(http
.http(), name
);
276 // Check if the get full PPD, since non-blocking call may simply return
277 // normally after timeout expired.
279 // There is no reliable way right now to detect full and complete PPD
280 // get downloaded. If we reach http timeout, it may simply return
281 // downloaded part as a full response. It might be good enough to check
282 // http->data_remaining or http->_data_remaining, unfortunately http_t
283 // is an internal structure and fields are not exposed in CUPS headers.
284 // httpGetLength or httpGetLength2 returning the full content size.
285 // Comparing file size against that content length might be unreliable
286 // since some http reponses are encoded and content_length > file size.
287 // Let's just check for the obvious CUPS and http errors here.
288 ppd_path
= base::FilePath(ppd_file_path
);
289 ipp_status_t error_code
= cupsLastError();
290 int http_error
= httpError(http
.http());
291 if (error_code
> IPP_OK_EVENTS_COMPLETE
|| http_error
!= 0) {
292 LOG(ERROR
) << "Error downloading PPD file"
293 << ", name: " << name
294 << ", CUPS error: " << static_cast<int>(error_code
)
295 << ", HTTP error: " << http_error
;
296 base::DeleteFile(ppd_path
, false);
304 } // namespace printing