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