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_cups.h"
6
7 #include <cups/ppd.h>
8 #include <dlfcn.h>
9 #include <errno.h>
10 #include <pthread.h>
11
12 #include <string>
13
14 #include "base/files/file_util.h"
15 #include "base/lazy_instance.h"
16 #include "base/logging.h"
17 #include "base/memory/ref_counted.h"
18 #include "base/no_destructor.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/synchronization/lock.h"
21 #include "base/values.h"
22 #include "build/build_config.h"
23 #include "printing/backend/cups_helper.h"
24 #include "printing/backend/print_backend_consts.h"
25 #include "url/gurl.h"
26
27 #if defined(OS_MACOSX)
28 #include "printing/backend/cups_connection.h"
29 #include "printing/backend/cups_ipp_utils.h"
30 #include "printing/backend/print_backend_cups_ipp.h"
31 #include "printing/printing_features.h"
32 #endif // defined(OS_MACOSX)
33
34 namespace printing {
35
PrintBackendCUPS(const GURL & print_server_url,http_encryption_t encryption,bool blocking,const std::string & locale)36 PrintBackendCUPS::PrintBackendCUPS(const GURL& print_server_url,
37 http_encryption_t encryption,
38 bool blocking,
39 const std::string& locale)
40 : PrintBackend(locale),
41 print_server_url_(print_server_url),
42 cups_encryption_(encryption),
43 blocking_(blocking) {}
44
45 // static
PrinterBasicInfoFromCUPS(const cups_dest_t & printer,PrinterBasicInfo * printer_info)46 bool PrintBackendCUPS::PrinterBasicInfoFromCUPS(
47 const cups_dest_t& printer,
48 PrinterBasicInfo* printer_info) {
49 const char* type_str =
50 cupsGetOption(kCUPSOptPrinterType, printer.num_options, printer.options);
51 if (type_str) {
52 cups_ptype_t type;
53 if (base::StringToUint(type_str, &type)) {
54 // Exclude fax and scanner devices.
55 // Also exclude discovered printers that have not been added locally.
56 // On macOS, AirPrint destinations show up even if they're not added to
57 // the system, and their capabilities cannot be read in that situation.
58 // (crbug.com/1027834)
59 constexpr cups_ptype_t kMask =
60 CUPS_PRINTER_FAX | CUPS_PRINTER_SCANNER | CUPS_PRINTER_DISCOVERED;
61 if (type & kMask)
62 return false;
63 }
64 }
65
66 printer_info->printer_name = printer.name;
67 printer_info->is_default = printer.is_default;
68
69 const char* info =
70 cupsGetOption(kCUPSOptPrinterInfo, printer.num_options, printer.options);
71
72 const char* state =
73 cupsGetOption(kCUPSOptPrinterState, printer.num_options, printer.options);
74 if (state)
75 base::StringToInt(state, &printer_info->printer_status);
76
77 const char* drv_info = cupsGetOption(kCUPSOptPrinterMakeAndModel,
78 printer.num_options, printer.options);
79 if (drv_info)
80 printer_info->options[kDriverInfoTagName] = *drv_info;
81
82 // Store printer options.
83 for (int opt_index = 0; opt_index < printer.num_options; ++opt_index) {
84 printer_info->options[printer.options[opt_index].name] =
85 printer.options[opt_index].value;
86 }
87
88 #if defined(OS_MACOSX)
89 // On Mac, "printer-info" option specifies the printer name and
90 // "printer-make-and-model" specifies the printer description.
91 if (info)
92 printer_info->display_name = info;
93 if (drv_info)
94 printer_info->printer_description = drv_info;
95 #else
96 // On Linux destination name specifies the printer name and "printer-info"
97 // specifies the printer description.
98 printer_info->display_name = printer.name;
99 if (info)
100 printer_info->printer_description = info;
101 #endif
102 return true;
103 }
104
operator ()(cups_dest_t * dest) const105 void PrintBackendCUPS::DestinationDeleter::operator()(cups_dest_t* dest) const {
106 cupsFreeDests(1, dest);
107 }
108
EnumeratePrinters(PrinterList * printer_list)109 bool PrintBackendCUPS::EnumeratePrinters(PrinterList* printer_list) {
110 DCHECK(printer_list);
111 printer_list->clear();
112
113 cups_dest_t* destinations = nullptr;
114 int num_dests = GetDests(&destinations);
115 if (!num_dests && cupsLastError() > IPP_OK_EVENTS_COMPLETE) {
116 VLOG(1) << "CUPS: Error getting printers from CUPS server"
117 << ", server: " << print_server_url_
118 << ", error: " << static_cast<int>(cupsLastError());
119 return false;
120 }
121
122 for (int printer_index = 0; printer_index < num_dests; ++printer_index) {
123 const cups_dest_t& printer = destinations[printer_index];
124
125 PrinterBasicInfo printer_info;
126 if (PrinterBasicInfoFromCUPS(printer, &printer_info))
127 printer_list->push_back(printer_info);
128 }
129
130 cupsFreeDests(num_dests, destinations);
131
132 VLOG(1) << "CUPS: Enumerated printers, server: " << print_server_url_
133 << ", # of printers: " << printer_list->size();
134 return true;
135 }
136
GetDefaultPrinterName()137 std::string PrintBackendCUPS::GetDefaultPrinterName() {
138 // Not using cupsGetDefault() because it lies about the default printer.
139 cups_dest_t* dests;
140 int num_dests = GetDests(&dests);
141 cups_dest_t* dest = cupsGetDest(nullptr, nullptr, num_dests, dests);
142 std::string name = dest ? std::string(dest->name) : std::string();
143 cupsFreeDests(num_dests, dests);
144 return name;
145 }
146
GetPrinterBasicInfo(const std::string & printer_name,PrinterBasicInfo * printer_info)147 bool PrintBackendCUPS::GetPrinterBasicInfo(const std::string& printer_name,
148 PrinterBasicInfo* printer_info) {
149 ScopedDestination dest = GetNamedDest(printer_name);
150 if (!dest)
151 return false;
152
153 DCHECK_EQ(printer_name, dest->name);
154 return PrinterBasicInfoFromCUPS(*dest, printer_info);
155 }
156
GetPrinterSemanticCapsAndDefaults(const std::string & printer_name,PrinterSemanticCapsAndDefaults * printer_info)157 bool PrintBackendCUPS::GetPrinterSemanticCapsAndDefaults(
158 const std::string& printer_name,
159 PrinterSemanticCapsAndDefaults* printer_info) {
160 PrinterCapsAndDefaults info;
161 if (!IsValidPrinter(printer_name))
162 return false;
163
164 if (!GetPrinterCapsAndDefaults(printer_name, &info))
165 return false;
166
167 return ParsePpdCapabilities(printer_name, locale(), info.printer_capabilities,
168 printer_info);
169 }
170
GetPrinterCapsAndDefaults(const std::string & printer_name,PrinterCapsAndDefaults * printer_info)171 bool PrintBackendCUPS::GetPrinterCapsAndDefaults(
172 const std::string& printer_name,
173 PrinterCapsAndDefaults* printer_info) {
174 DCHECK(printer_info);
175
176 VLOG(1) << "CUPS: Getting caps and defaults, printer name: " << printer_name;
177
178 base::FilePath ppd_path(GetPPD(printer_name.c_str()));
179 // In some cases CUPS failed to get ppd file.
180 if (ppd_path.empty()) {
181 LOG(ERROR) << "CUPS: Failed to get PPD, printer name: " << printer_name;
182 return false;
183 }
184
185 std::string content;
186 bool res = base::ReadFileToString(ppd_path, &content);
187
188 base::DeleteFile(ppd_path, false);
189
190 if (res) {
191 printer_info->printer_capabilities.swap(content);
192 printer_info->caps_mime_type = "application/pagemaker";
193 // In CUPS, printer defaults is a part of PPD file. Nothing to upload here.
194 printer_info->printer_defaults.clear();
195 printer_info->defaults_mime_type.clear();
196 }
197
198 return res;
199 }
200
GetPrinterDriverInfo(const std::string & printer_name)201 std::string PrintBackendCUPS::GetPrinterDriverInfo(
202 const std::string& printer_name) {
203 std::string result;
204
205 ScopedDestination dest = GetNamedDest(printer_name);
206 if (!dest)
207 return result;
208
209 DCHECK_EQ(printer_name, dest->name);
210 const char* info =
211 cupsGetOption(kDriverNameTagName, dest->num_options, dest->options);
212 if (info)
213 result = *info;
214 return result;
215 }
216
IsValidPrinter(const std::string & printer_name)217 bool PrintBackendCUPS::IsValidPrinter(const std::string& printer_name) {
218 return !!GetNamedDest(printer_name);
219 }
220
CreateInstanceImpl(const base::DictionaryValue * print_backend_settings,const std::string & locale,bool for_cloud_print)221 scoped_refptr<PrintBackend> PrintBackend::CreateInstanceImpl(
222 const base::DictionaryValue* print_backend_settings,
223 const std::string& locale,
224 bool for_cloud_print) {
225 #if defined(OS_MACOSX)
226 if (!for_cloud_print &&
227 base::FeatureList::IsEnabled(features::kCupsIppPrintingBackend)) {
228 return base::MakeRefCounted<PrintBackendCupsIpp>(
229 CreateConnection(print_backend_settings), locale);
230 }
231 #endif // defined(OS_MACOSX)
232 std::string print_server_url_str, cups_blocking;
233 int encryption = HTTP_ENCRYPT_NEVER;
234 if (print_backend_settings) {
235 print_backend_settings->GetString(kCUPSPrintServerURL,
236 &print_server_url_str);
237
238 print_backend_settings->GetString(kCUPSBlocking, &cups_blocking);
239
240 print_backend_settings->GetInteger(kCUPSEncryption, &encryption);
241 }
242 GURL print_server_url(print_server_url_str);
243 return base::MakeRefCounted<PrintBackendCUPS>(
244 print_server_url, static_cast<http_encryption_t>(encryption),
245 cups_blocking == kValueTrue, locale);
246 }
247
GetDests(cups_dest_t ** dests)248 int PrintBackendCUPS::GetDests(cups_dest_t** dests) {
249 // Default to the local print server (CUPS scheduler)
250 if (print_server_url_.is_empty())
251 return cupsGetDests2(CUPS_HTTP_DEFAULT, dests);
252
253 HttpConnectionCUPS http(print_server_url_, cups_encryption_, blocking_);
254
255 // This call must be made in the same scope as |http| because its destructor
256 // closes the connection.
257 return cupsGetDests2(http.http(), dests);
258 }
259
GetPPD(const char * name)260 base::FilePath PrintBackendCUPS::GetPPD(const char* name) {
261 // cupsGetPPD returns a filename stored in a static buffer in CUPS.
262 // Protect this code with lock.
263 static base::NoDestructor<base::Lock> ppd_lock;
264 base::AutoLock ppd_autolock(*ppd_lock);
265 base::FilePath ppd_path;
266 const char* ppd_file_path = nullptr;
267 if (print_server_url_.is_empty()) { // Use default (local) print server.
268 ppd_file_path = cupsGetPPD(name);
269 if (ppd_file_path)
270 ppd_path = base::FilePath(ppd_file_path);
271 } else {
272 // cupsGetPPD2 gets stuck sometimes in an infinite time due to network
273 // configuration/issues. To prevent that, use non-blocking http connection
274 // here.
275 // Note: After looking at CUPS sources, it looks like non-blocking
276 // connection will timeout after 10 seconds of no data period. And it will
277 // return the same way as if data was completely and successfully
278 // downloaded.
279 HttpConnectionCUPS http(print_server_url_, cups_encryption_, blocking_);
280 ppd_file_path = cupsGetPPD2(http.http(), name);
281 // Check if the get full PPD, since non-blocking call may simply return
282 // normally after timeout expired.
283 if (ppd_file_path) {
284 // There is no reliable way right now to detect full and complete PPD
285 // get downloaded. If we reach http timeout, it may simply return
286 // downloaded part as a full response. It might be good enough to check
287 // http->data_remaining or http->_data_remaining, unfortunately http_t
288 // is an internal structure and fields are not exposed in CUPS headers.
289 // httpGetLength or httpGetLength2 returning the full content size.
290 // Comparing file size against that content length might be unreliable
291 // since some http reponses are encoded and content_length > file size.
292 // Let's just check for the obvious CUPS and http errors here.
293 ppd_path = base::FilePath(ppd_file_path);
294 ipp_status_t error_code = cupsLastError();
295 int http_error = httpError(http.http());
296 if (error_code > IPP_OK_EVENTS_COMPLETE || http_error != 0) {
297 LOG(ERROR) << "Error downloading PPD file, name: " << name
298 << ", CUPS error: " << static_cast<int>(error_code)
299 << ", HTTP error: " << http_error;
300 base::DeleteFile(ppd_path, false);
301 ppd_path.clear();
302 }
303 }
304 }
305 return ppd_path;
306 }
307
GetNamedDest(const std::string & printer_name)308 PrintBackendCUPS::ScopedDestination PrintBackendCUPS::GetNamedDest(
309 const std::string& printer_name) {
310 cups_dest_t* dest;
311 if (print_server_url_.is_empty()) {
312 // Use default (local) print server.
313 dest = cupsGetNamedDest(CUPS_HTTP_DEFAULT, printer_name.c_str(), nullptr);
314 } else {
315 HttpConnectionCUPS http(print_server_url_, cups_encryption_, blocking_);
316 dest = cupsGetNamedDest(http.http(), printer_name.c_str(), nullptr);
317 }
318 return ScopedDestination(dest);
319 }
320
321 } // namespace printing
322