[email protected] | 203fb82 | 2012-01-26 20:52:56 | [diff] [blame] | 1 | // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
skau | 8ffe775 | 2016-06-29 02:10:28 | [diff] [blame] | 5 | #include "printing/backend/print_backend_cups.h" |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 6 | |
pwnall | 29c1262 | 2016-08-17 22:33:25 | [diff] [blame] | 7 | #include <cups/ppd.h> |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 8 | #include <dlfcn.h> |
| 9 | #include <errno.h> |
[email protected] | ce80f5f | 2011-08-09 05:41:27 | [diff] [blame] | 10 | #include <pthread.h> |
| 11 | |
skau | 8ffe775 | 2016-06-29 02:10:28 | [diff] [blame] | 12 | #include <string> |
| 13 | |
thestig | 22dfc401 | 2014-09-05 08:29:44 | [diff] [blame] | 14 | #include "base/files/file_util.h" |
[email protected] | 625332e0 | 2010-12-14 07:48:49 | [diff] [blame] | 15 | #include "base/lazy_instance.h" |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 16 | #include "base/logging.h" |
[email protected] | 896d161f | 2013-06-11 22:52:24 | [diff] [blame] | 17 | #include "base/strings/string_number_conversions.h" |
[email protected] | 20305ec | 2011-01-21 04:55:52 | [diff] [blame] | 18 | #include "base/synchronization/lock.h" |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 19 | #include "base/values.h" |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 20 | #include "printing/backend/cups_helper.h" |
[email protected] | 9c6ca60 | 2010-12-16 00:55:39 | [diff] [blame] | 21 | #include "printing/backend/print_backend_consts.h" |
[email protected] | 79fe227 | 2013-07-13 20:01:40 | [diff] [blame] | 22 | #include "url/gurl.h" |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 23 | |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 24 | namespace printing { |
| 25 | |
thestig | 27bb2f80 | 2016-05-16 22:57:56 | [diff] [blame] | 26 | namespace { |
| 27 | |
| 28 | const char kCUPSPrinterInfoOpt[] = "printer-info"; |
| 29 | const char kCUPSPrinterStateOpt[] = "printer-state"; |
| 30 | const char kCUPSPrinterTypeOpt[] = "printer-type"; |
thestig | 27bb2f80 | 2016-05-16 22:57:56 | [diff] [blame] | 31 | |
| 32 | bool PrinterBasicInfoFromCUPS(const cups_dest_t& printer, |
| 33 | PrinterBasicInfo* printer_info) { |
| 34 | // CUPS can have 'printers' that are actually scanners. (not MFC) |
| 35 | // At least on Mac. Check for scanners and skip them. |
| 36 | const char* type_str = |
| 37 | cupsGetOption(kCUPSPrinterTypeOpt, printer.num_options, printer.options); |
| 38 | if (type_str) { |
| 39 | int type; |
| 40 | if (base::StringToInt(type_str, &type) && (type & CUPS_PRINTER_SCANNER)) |
| 41 | return false; |
| 42 | } |
| 43 | |
| 44 | printer_info->printer_name = printer.name; |
| 45 | printer_info->is_default = printer.is_default; |
| 46 | |
| 47 | const char* info = |
| 48 | cupsGetOption(kCUPSPrinterInfoOpt, printer.num_options, printer.options); |
| 49 | if (info) |
| 50 | printer_info->printer_description = info; |
| 51 | |
| 52 | const char* state = |
| 53 | cupsGetOption(kCUPSPrinterStateOpt, printer.num_options, printer.options); |
| 54 | if (state) |
| 55 | base::StringToInt(state, &printer_info->printer_status); |
| 56 | |
thestig | fac2cc4 | 2016-11-03 16:31:11 | [diff] [blame] | 57 | const char* drv_info = cupsGetOption(kDriverNameTagName, |
thestig | 27bb2f80 | 2016-05-16 22:57:56 | [diff] [blame] | 58 | printer.num_options, printer.options); |
| 59 | if (drv_info) |
| 60 | printer_info->options[kDriverInfoTagName] = *drv_info; |
| 61 | |
| 62 | // Store printer options. |
| 63 | for (int opt_index = 0; opt_index < printer.num_options; ++opt_index) { |
| 64 | printer_info->options[printer.options[opt_index].name] = |
| 65 | printer.options[opt_index].value; |
| 66 | } |
| 67 | return true; |
| 68 | } |
| 69 | |
| 70 | } // namespace |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 71 | |
[email protected] | ce6f591 | 2012-05-16 23:07:30 | [diff] [blame] | 72 | PrintBackendCUPS::PrintBackendCUPS(const GURL& print_server_url, |
| 73 | http_encryption_t encryption, |
| 74 | bool blocking) |
| 75 | : print_server_url_(print_server_url), |
| 76 | cups_encryption_(encryption), |
| 77 | blocking_(blocking) { |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 78 | } |
| 79 | |
[email protected] | 402197a | 2011-01-24 23:47:41 | [diff] [blame] | 80 | bool PrintBackendCUPS::EnumeratePrinters(PrinterList* printer_list) { |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 81 | DCHECK(printer_list); |
| 82 | printer_list->clear(); |
| 83 | |
thestig | 3154ea2 | 2016-05-16 21:34:03 | [diff] [blame] | 84 | cups_dest_t* destinations = nullptr; |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 85 | int num_dests = GetDests(&destinations); |
thestig | 3154ea2 | 2016-05-16 21:34:03 | [diff] [blame] | 86 | if (!num_dests && cupsLastError() > IPP_OK_EVENTS_COMPLETE) { |
[email protected] | 6075da4 | 2012-07-23 23:22:40 | [diff] [blame] | 87 | VLOG(1) << "CUPS: Error getting printers from CUPS server" |
| 88 | << ", server: " << print_server_url_ |
| 89 | << ", error: " << static_cast<int>(cupsLastError()); |
[email protected] | cfde864 | 2011-02-03 23:53:27 | [diff] [blame] | 90 | return false; |
| 91 | } |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 92 | |
[email protected] | 675a1ae | 2013-10-14 20:24:37 | [diff] [blame] | 93 | for (int printer_index = 0; printer_index < num_dests; ++printer_index) { |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 94 | const cups_dest_t& printer = destinations[printer_index]; |
| 95 | |
| 96 | PrinterBasicInfo printer_info; |
thestig | 27bb2f80 | 2016-05-16 22:57:56 | [diff] [blame] | 97 | if (PrinterBasicInfoFromCUPS(printer, &printer_info)) |
| 98 | printer_list->push_back(printer_info); |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 99 | } |
| 100 | |
| 101 | cupsFreeDests(num_dests, destinations); |
| 102 | |
thestig | 3154ea2 | 2016-05-16 21:34:03 | [diff] [blame] | 103 | VLOG(1) << "CUPS: Enumerated printers, server: " << print_server_url_ |
[email protected] | 6075da4 | 2012-07-23 23:22:40 | [diff] [blame] | 104 | << ", # of printers: " << printer_list->size(); |
[email protected] | 402197a | 2011-01-24 23:47:41 | [diff] [blame] | 105 | return true; |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 106 | } |
| 107 | |
[email protected] | ffcb1ca | 2011-05-28 21:50:29 | [diff] [blame] | 108 | std::string PrintBackendCUPS::GetDefaultPrinterName() { |
[email protected] | aa27b953 | 2011-06-08 21:06:41 | [diff] [blame] | 109 | // Not using cupsGetDefault() because it lies about the default printer. |
| 110 | cups_dest_t* dests; |
| 111 | int num_dests = GetDests(&dests); |
thestig | 3154ea2 | 2016-05-16 21:34:03 | [diff] [blame] | 112 | cups_dest_t* dest = cupsGetDest(nullptr, nullptr, num_dests, dests); |
[email protected] | 976ac95 | 2012-09-07 21:21:28 | [diff] [blame] | 113 | std::string name = dest ? std::string(dest->name) : std::string(); |
| 114 | cupsFreeDests(num_dests, dests); |
| 115 | return name; |
[email protected] | ffcb1ca | 2011-05-28 21:50:29 | [diff] [blame] | 116 | } |
| 117 | |
thestig | 27bb2f80 | 2016-05-16 22:57:56 | [diff] [blame] | 118 | bool PrintBackendCUPS::GetPrinterBasicInfo(const std::string& printer_name, |
| 119 | PrinterBasicInfo* printer_info) { |
| 120 | cups_dest_t* dest = GetNamedDest(printer_name); |
| 121 | if (!dest) |
| 122 | return false; |
| 123 | |
| 124 | DCHECK_EQ(printer_name, dest->name); |
| 125 | bool ret = PrinterBasicInfoFromCUPS(*dest, printer_info); |
| 126 | cupsFreeDests(1, dest); |
| 127 | return ret; |
| 128 | } |
| 129 | |
[email protected] | 8b6098e | 2012-09-07 04:21:21 | [diff] [blame] | 130 | bool PrintBackendCUPS::GetPrinterSemanticCapsAndDefaults( |
| 131 | const std::string& printer_name, |
| 132 | PrinterSemanticCapsAndDefaults* printer_info) { |
| 133 | PrinterCapsAndDefaults info; |
| 134 | if (!GetPrinterCapsAndDefaults(printer_name, &info) ) |
| 135 | return false; |
| 136 | |
[email protected] | 675a1ae | 2013-10-14 20:24:37 | [diff] [blame] | 137 | return ParsePpdCapabilities( |
[email protected] | 8b6098e | 2012-09-07 04:21:21 | [diff] [blame] | 138 | printer_name, info.printer_capabilities, printer_info); |
| 139 | } |
| 140 | |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 141 | bool PrintBackendCUPS::GetPrinterCapsAndDefaults( |
| 142 | const std::string& printer_name, |
| 143 | PrinterCapsAndDefaults* printer_info) { |
| 144 | DCHECK(printer_info); |
| 145 | |
thestig | 3154ea2 | 2016-05-16 21:34:03 | [diff] [blame] | 146 | VLOG(1) << "CUPS: Getting caps and defaults, printer name: " << printer_name; |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 147 | |
[email protected] | 79f6388 | 2013-02-10 05:15:45 | [diff] [blame] | 148 | base::FilePath ppd_path(GetPPD(printer_name.c_str())); |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 149 | // In some cases CUPS failed to get ppd file. |
| 150 | if (ppd_path.empty()) { |
[email protected] | 8eb2231 | 2014-06-18 10:10:59 | [diff] [blame] | 151 | LOG(ERROR) << "CUPS: Failed to get PPD, printer name: " << printer_name; |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 152 | return false; |
| 153 | } |
| 154 | |
| 155 | std::string content; |
[email protected] | 82f84b9 | 2013-08-30 18:23:50 | [diff] [blame] | 156 | bool res = base::ReadFileToString(ppd_path, &content); |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 157 | |
[email protected] | dd3aa79 | 2013-07-16 19:10:23 | [diff] [blame] | 158 | base::DeleteFile(ppd_path, false); |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 159 | |
| 160 | if (res) { |
| 161 | printer_info->printer_capabilities.swap(content); |
| 162 | printer_info->caps_mime_type = "application/pagemaker"; |
| 163 | // In CUPS, printer defaults is a part of PPD file. Nothing to upload here. |
| 164 | printer_info->printer_defaults.clear(); |
| 165 | printer_info->defaults_mime_type.clear(); |
| 166 | } |
| 167 | |
| 168 | return res; |
| 169 | } |
| 170 | |
[email protected] | 4f9dd83 | 2012-03-16 08:54:57 | [diff] [blame] | 171 | std::string PrintBackendCUPS::GetPrinterDriverInfo( |
| 172 | const std::string& printer_name) { |
[email protected] | 4f9dd83 | 2012-03-16 08:54:57 | [diff] [blame] | 173 | std::string result; |
[email protected] | 4f9dd83 | 2012-03-16 08:54:57 | [diff] [blame] | 174 | |
thestig | 3154ea2 | 2016-05-16 21:34:03 | [diff] [blame] | 175 | cups_dest_t* dest = GetNamedDest(printer_name); |
| 176 | if (!dest) |
| 177 | return result; |
| 178 | |
| 179 | DCHECK_EQ(printer_name, dest->name); |
| 180 | const char* info = |
thestig | fac2cc4 | 2016-11-03 16:31:11 | [diff] [blame] | 181 | cupsGetOption(kDriverNameTagName, dest->num_options, dest->options); |
thestig | 3154ea2 | 2016-05-16 21:34:03 | [diff] [blame] | 182 | if (info) |
| 183 | result = *info; |
| 184 | cupsFreeDests(1, dest); |
[email protected] | 4f9dd83 | 2012-03-16 08:54:57 | [diff] [blame] | 185 | return result; |
[email protected] | de6ac32 | 2012-03-05 23:17:25 | [diff] [blame] | 186 | } |
| 187 | |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 188 | bool PrintBackendCUPS::IsValidPrinter(const std::string& printer_name) { |
thestig | 3154ea2 | 2016-05-16 21:34:03 | [diff] [blame] | 189 | cups_dest_t* dest = GetNamedDest(printer_name); |
| 190 | if (!dest) |
| 191 | return false; |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 192 | |
thestig | 3154ea2 | 2016-05-16 21:34:03 | [diff] [blame] | 193 | cupsFreeDests(1, dest); |
| 194 | return true; |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 195 | } |
| 196 | |
skau | 8ffe775 | 2016-06-29 02:10:28 | [diff] [blame] | 197 | #if !defined(OS_CHROMEOS) |
skau | a1e0b7a6 | 2016-12-22 23:23:14 | [diff] [blame] | 198 | scoped_refptr<PrintBackend> PrintBackend::CreateInstanceImpl( |
[email protected] | 675a1ae | 2013-10-14 20:24:37 | [diff] [blame] | 199 | const base::DictionaryValue* print_backend_settings) { |
[email protected] | 9c6ca60 | 2010-12-16 00:55:39 | [diff] [blame] | 200 | std::string print_server_url_str, cups_blocking; |
[email protected] | ce6f591 | 2012-05-16 23:07:30 | [diff] [blame] | 201 | int encryption = HTTP_ENCRYPT_NEVER; |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 202 | if (print_backend_settings) { |
| 203 | print_backend_settings->GetString(kCUPSPrintServerURL, |
| 204 | &print_server_url_str); |
[email protected] | 9c6ca60 | 2010-12-16 00:55:39 | [diff] [blame] | 205 | |
| 206 | print_backend_settings->GetString(kCUPSBlocking, |
| 207 | &cups_blocking); |
[email protected] | ce6f591 | 2012-05-16 23:07:30 | [diff] [blame] | 208 | |
| 209 | print_backend_settings->GetInteger(kCUPSEncryption, &encryption); |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 210 | } |
iceman | 4179f17c | 2017-03-29 19:50:09 | [diff] [blame] | 211 | GURL print_server_url(print_server_url_str); |
[email protected] | ce6f591 | 2012-05-16 23:07:30 | [diff] [blame] | 212 | return new PrintBackendCUPS(print_server_url, |
| 213 | static_cast<http_encryption_t>(encryption), |
| 214 | cups_blocking == kValueTrue); |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 215 | } |
skau | 8ffe775 | 2016-06-29 02:10:28 | [diff] [blame] | 216 | #endif // !defined(OS_CHROMEOS) |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 217 | |
| 218 | int PrintBackendCUPS::GetDests(cups_dest_t** dests) { |
Lei Zhang | eee56ad6 | 2017-08-28 19:25:12 | [diff] [blame] | 219 | if (print_server_url_.is_empty()) // Use default (local) print server. |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 220 | return cupsGetDests(dests); |
thestig | fac2cc4 | 2016-11-03 16:31:11 | [diff] [blame] | 221 | |
| 222 | HttpConnectionCUPS http(print_server_url_, cups_encryption_); |
| 223 | http.SetBlocking(blocking_); |
| 224 | return cupsGetDests2(http.http(), dests); |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 225 | } |
| 226 | |
[email protected] | 023ad6ab | 2013-02-17 05:07:23 | [diff] [blame] | 227 | base::FilePath PrintBackendCUPS::GetPPD(const char* name) { |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 228 | // cupsGetPPD returns a filename stored in a static buffer in CUPS. |
| 229 | // Protect this code with lock. |
[email protected] | 26430024 | 2011-11-07 06:03:30 | [diff] [blame] | 230 | CR_DEFINE_STATIC_LOCAL(base::Lock, ppd_lock, ()); |
[email protected] | 20305ec | 2011-01-21 04:55:52 | [diff] [blame] | 231 | base::AutoLock ppd_autolock(ppd_lock); |
[email protected] | 79f6388 | 2013-02-10 05:15:45 | [diff] [blame] | 232 | base::FilePath ppd_path; |
thestig | 3154ea2 | 2016-05-16 21:34:03 | [diff] [blame] | 233 | const char* ppd_file_path = nullptr; |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 234 | if (print_server_url_.is_empty()) { // Use default (local) print server. |
| 235 | ppd_file_path = cupsGetPPD(name); |
[email protected] | 5a49cdca0 | 2010-12-06 18:17:39 | [diff] [blame] | 236 | if (ppd_file_path) |
[email protected] | 79f6388 | 2013-02-10 05:15:45 | [diff] [blame] | 237 | ppd_path = base::FilePath(ppd_file_path); |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 238 | } else { |
[email protected] | 5a49cdca0 | 2010-12-06 18:17:39 | [diff] [blame] | 239 | // cupsGetPPD2 gets stuck sometimes in an infinite time due to network |
| 240 | // configuration/issues. To prevent that, use non-blocking http connection |
| 241 | // here. |
| 242 | // Note: After looking at CUPS sources, it looks like non-blocking |
| 243 | // connection will timeout after 10 seconds of no data period. And it will |
thestig | 259626c | 2015-11-23 20:18:11 | [diff] [blame] | 244 | // return the same way as if data was completely and successfully |
| 245 | // downloaded. |
[email protected] | ce6f591 | 2012-05-16 23:07:30 | [diff] [blame] | 246 | HttpConnectionCUPS http(print_server_url_, cups_encryption_); |
[email protected] | 9c6ca60 | 2010-12-16 00:55:39 | [diff] [blame] | 247 | http.SetBlocking(blocking_); |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 248 | ppd_file_path = cupsGetPPD2(http.http(), name); |
[email protected] | 5a49cdca0 | 2010-12-06 18:17:39 | [diff] [blame] | 249 | // Check if the get full PPD, since non-blocking call may simply return |
| 250 | // normally after timeout expired. |
| 251 | if (ppd_file_path) { |
[email protected] | cfde864 | 2011-02-03 23:53:27 | [diff] [blame] | 252 | // There is no reliable way right now to detect full and complete PPD |
| 253 | // get downloaded. If we reach http timeout, it may simply return |
| 254 | // downloaded part as a full response. It might be good enough to check |
| 255 | // http->data_remaining or http->_data_remaining, unfortunately http_t |
| 256 | // is an internal structure and fields are not exposed in CUPS headers. |
| 257 | // httpGetLength or httpGetLength2 returning the full content size. |
| 258 | // Comparing file size against that content length might be unreliable |
| 259 | // since some http reponses are encoded and content_length > file size. |
| 260 | // Let's just check for the obvious CUPS and http errors here. |
[email protected] | 79f6388 | 2013-02-10 05:15:45 | [diff] [blame] | 261 | ppd_path = base::FilePath(ppd_file_path); |
[email protected] | cfde864 | 2011-02-03 23:53:27 | [diff] [blame] | 262 | ipp_status_t error_code = cupsLastError(); |
| 263 | int http_error = httpError(http.http()); |
| 264 | if (error_code > IPP_OK_EVENTS_COMPLETE || http_error != 0) { |
thestig | 3154ea2 | 2016-05-16 21:34:03 | [diff] [blame] | 265 | LOG(ERROR) << "Error downloading PPD file, name: " << name |
[email protected] | cfde864 | 2011-02-03 23:53:27 | [diff] [blame] | 266 | << ", CUPS error: " << static_cast<int>(error_code) |
| 267 | << ", HTTP error: " << http_error; |
[email protected] | dd3aa79 | 2013-07-16 19:10:23 | [diff] [blame] | 268 | base::DeleteFile(ppd_path, false); |
[email protected] | 5a49cdca0 | 2010-12-06 18:17:39 | [diff] [blame] | 269 | ppd_path.clear(); |
| 270 | } |
| 271 | } |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 272 | } |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 273 | return ppd_path; |
| 274 | } |
| 275 | |
thestig | 3154ea2 | 2016-05-16 21:34:03 | [diff] [blame] | 276 | cups_dest_t* PrintBackendCUPS::GetNamedDest(const std::string& printer_name) { |
| 277 | // Use default (local) print server. |
| 278 | if (print_server_url_.is_empty()) |
| 279 | return cupsGetNamedDest(CUPS_HTTP_DEFAULT, printer_name.c_str(), nullptr); |
| 280 | |
| 281 | HttpConnectionCUPS http(print_server_url_, cups_encryption_); |
| 282 | http.SetBlocking(blocking_); |
| 283 | return cupsGetNamedDest(http.http(), printer_name.c_str(), nullptr); |
| 284 | } |
| 285 | |
[email protected] | 8e553f49 | 2010-10-25 20:05:44 | [diff] [blame] | 286 | } // namespace printing |