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