| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "printing/backend/print_backend.h" |
| |
| #include "build/build_config.h" |
| |
| #include <dlfcn.h> |
| #include <errno.h> |
| #include <pthread.h> |
| |
| #if defined(OS_MACOSX) |
| #include <AvailabilityMacros.h> |
| #else |
| #include <gcrypt.h> |
| #endif |
| |
| #include "base/file_util.h" |
| #include "base/lazy_instance.h" |
| #include "base/logging.h" |
| #include "base/string_number_conversions.h" |
| #include "base/synchronization/lock.h" |
| #include "base/values.h" |
| #include "googleurl/src/gurl.h" |
| #include "printing/backend/cups_helper.h" |
| #include "printing/backend/print_backend_consts.h" |
| |
| #if (CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 4) |
| const int CUPS_PRINTER_SCANNER = 0x2000000; // Scanner-only device |
| #endif |
| |
| #if !defined(OS_MACOSX) |
| GCRY_THREAD_OPTION_PTHREAD_IMPL; |
| |
| namespace { |
| |
| // Init GCrypt library (needed for CUPS) using pthreads. |
| // There exists a bug in CUPS library, where it crashed with: "ath.c:184: |
| // _gcry_ath_mutex_lock: Assertion `*lock == ((ath_mutex_t) 0)' failed." |
| // It happened when multiple threads tried printing simultaneously. |
| // Google search for 'gnutls thread safety' provided a solution that |
| // initialized gcrypt and gnutls. |
| |
| // Initially, we linked with -lgnutls and simply called gnutls_global_init(), |
| // but this did not work well since we build one binary on Ubuntu Hardy and |
| // expect it to run on many Linux distros. (See http://crbug.com/46954) |
| // So instead we use dlopen() and dlsym() to dynamically load and call |
| // gnutls_global_init(). |
| |
| class GcryptInitializer { |
| public: |
| GcryptInitializer() { |
| Init(); |
| } |
| |
| private: |
| void Init() { |
| const char* kGnuTlsFiles[] = { |
| "libgnutls.so.28", |
| "libgnutls.so.26", |
| "libgnutls.so", |
| }; |
| gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); |
| for (size_t i = 0; i < arraysize(kGnuTlsFiles); ++i) { |
| void* gnutls_lib = dlopen(kGnuTlsFiles[i], RTLD_NOW); |
| if (!gnutls_lib) { |
| VLOG(1) << "Cannot load " << kGnuTlsFiles[i]; |
| continue; |
| } |
| const char* kGnuTlsInitFuncName = "gnutls_global_init"; |
| int (*pgnutls_global_init)(void) = reinterpret_cast<int(*)()>( |
| dlsym(gnutls_lib, kGnuTlsInitFuncName)); |
| if (!pgnutls_global_init) { |
| VLOG(1) << "Could not find " << kGnuTlsInitFuncName |
| << " in " << kGnuTlsFiles[i]; |
| continue; |
| } |
| if ((*pgnutls_global_init)() != 0) |
| LOG(ERROR) << "gnutls_global_init() failed"; |
| return; |
| } |
| LOG(ERROR) << "Cannot find libgnutls"; |
| } |
| }; |
| |
| base::LazyInstance<GcryptInitializer> g_gcrypt_initializer = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| } // namespace |
| #endif // !defined(OS_MACOSX) |
| |
| namespace printing { |
| |
| static const char kCUPSPrinterInfoOpt[] = "printer-info"; |
| static const char kCUPSPrinterStateOpt[] = "printer-state"; |
| static const char kCUPSPrinterTypeOpt[] = "printer-type"; |
| static const char kCUPSPrinterMakeModelOpt[] = "printer-make-and-model"; |
| |
| class PrintBackendCUPS : public PrintBackend { |
| public: |
| PrintBackendCUPS(const GURL& print_server_url, |
| http_encryption_t encryption, bool blocking); |
| |
| // PrintBackend implementation. |
| virtual bool EnumeratePrinters(PrinterList* printer_list) OVERRIDE; |
| virtual std::string GetDefaultPrinterName() OVERRIDE; |
| virtual bool GetPrinterSemanticCapsAndDefaults( |
| const std::string& printer_name, |
| PrinterSemanticCapsAndDefaults* printer_info) OVERRIDE; |
| virtual bool GetPrinterCapsAndDefaults( |
| const std::string& printer_name, |
| PrinterCapsAndDefaults* printer_info) OVERRIDE; |
| virtual std::string GetPrinterDriverInfo( |
| const std::string& printer_name) OVERRIDE; |
| virtual bool IsValidPrinter(const std::string& printer_name) OVERRIDE; |
| |
| protected: |
| virtual ~PrintBackendCUPS() {} |
| |
| private: |
| // Following functions are wrappers around corresponding CUPS functions. |
| // <functions>2() are called when print server is specified, and plain |
| // version in another case. There is an issue specifing CUPS_HTTP_DEFAULT |
| // in the <functions>2(), it does not work in CUPS prior to 1.4. |
| int GetDests(cups_dest_t** dests); |
| FilePath GetPPD(const char* name); |
| |
| GURL print_server_url_; |
| http_encryption_t cups_encryption_; |
| bool blocking_; |
| }; |
| |
| PrintBackendCUPS::PrintBackendCUPS(const GURL& print_server_url, |
| http_encryption_t encryption, |
| bool blocking) |
| : print_server_url_(print_server_url), |
| cups_encryption_(encryption), |
| blocking_(blocking) { |
| } |
| |
| bool PrintBackendCUPS::EnumeratePrinters(PrinterList* printer_list) { |
| DCHECK(printer_list); |
| printer_list->clear(); |
| |
| cups_dest_t* destinations = NULL; |
| int num_dests = GetDests(&destinations); |
| if ((num_dests == 0) && (cupsLastError() > IPP_OK_EVENTS_COMPLETE)) { |
| VLOG(1) << "CUPS: Error getting printers from CUPS server" |
| << ", server: " << print_server_url_ |
| << ", error: " << static_cast<int>(cupsLastError()); |
| return false; |
| } |
| |
| for (int printer_index = 0; printer_index < num_dests; printer_index++) { |
| const cups_dest_t& printer = destinations[printer_index]; |
| |
| // CUPS can have 'printers' that are actually scanners. (not MFC) |
| // At least on Mac. Check for scanners and skip them. |
| const char* type_str = cupsGetOption(kCUPSPrinterTypeOpt, |
| printer.num_options, printer.options); |
| if (type_str != NULL) { |
| int type; |
| if (base::StringToInt(type_str, &type) && (type & CUPS_PRINTER_SCANNER)) |
| continue; |
| } |
| |
| PrinterBasicInfo printer_info; |
| printer_info.printer_name = printer.name; |
| printer_info.is_default = printer.is_default; |
| |
| const char* info = cupsGetOption(kCUPSPrinterInfoOpt, |
| printer.num_options, printer.options); |
| if (info != NULL) |
| printer_info.printer_description = info; |
| |
| const char* state = cupsGetOption(kCUPSPrinterStateOpt, |
| printer.num_options, printer.options); |
| if (state != NULL) |
| base::StringToInt(state, &printer_info.printer_status); |
| |
| const char* drv_info = cupsGetOption(kCUPSPrinterMakeModelOpt, |
| printer.num_options, |
| printer.options); |
| if (drv_info) |
| printer_info.options[kDriverInfoTagName] = *drv_info; |
| |
| // Store printer options. |
| for (int opt_index = 0; opt_index < printer.num_options; opt_index++) { |
| printer_info.options[printer.options[opt_index].name] = |
| printer.options[opt_index].value; |
| } |
| |
| printer_list->push_back(printer_info); |
| } |
| |
| cupsFreeDests(num_dests, destinations); |
| |
| VLOG(1) << "CUPS: Enumerated printers" |
| << ", server: " << print_server_url_ |
| << ", # of printers: " << printer_list->size(); |
| return true; |
| } |
| |
| std::string PrintBackendCUPS::GetDefaultPrinterName() { |
| // Not using cupsGetDefault() because it lies about the default printer. |
| cups_dest_t* dests; |
| int num_dests = GetDests(&dests); |
| cups_dest_t* dest = cupsGetDest(NULL, NULL, num_dests, dests); |
| std::string name = dest ? std::string(dest->name) : std::string(); |
| cupsFreeDests(num_dests, dests); |
| return name; |
| } |
| |
| bool PrintBackendCUPS::GetPrinterSemanticCapsAndDefaults( |
| const std::string& printer_name, |
| PrinterSemanticCapsAndDefaults* printer_info) { |
| PrinterCapsAndDefaults info; |
| if (!GetPrinterCapsAndDefaults(printer_name, &info) ) |
| return false; |
| |
| return parsePpdCapabilities( |
| printer_name, info.printer_capabilities, printer_info); |
| } |
| |
| bool PrintBackendCUPS::GetPrinterCapsAndDefaults( |
| const std::string& printer_name, |
| PrinterCapsAndDefaults* printer_info) { |
| DCHECK(printer_info); |
| |
| VLOG(1) << "CUPS: Getting caps and defaults" |
| << ", printer name: " << printer_name; |
| |
| FilePath ppd_path(GetPPD(printer_name.c_str())); |
| // In some cases CUPS failed to get ppd file. |
| if (ppd_path.empty()) { |
| LOG(ERROR) << "CUPS: Failed to get PPD" |
| << ", printer name: " << printer_name; |
| return false; |
| } |
| |
| std::string content; |
| bool res = file_util::ReadFileToString(ppd_path, &content); |
| |
| file_util::Delete(ppd_path, false); |
| |
| if (res) { |
| printer_info->printer_capabilities.swap(content); |
| printer_info->caps_mime_type = "application/pagemaker"; |
| // In CUPS, printer defaults is a part of PPD file. Nothing to upload here. |
| printer_info->printer_defaults.clear(); |
| printer_info->defaults_mime_type.clear(); |
| } |
| |
| return res; |
| } |
| |
| std::string PrintBackendCUPS::GetPrinterDriverInfo( |
| const std::string& printer_name) { |
| cups_dest_t* destinations = NULL; |
| int num_dests = GetDests(&destinations); |
| std::string result; |
| for (int printer_index = 0; printer_index < num_dests; printer_index++) { |
| const cups_dest_t& printer = destinations[printer_index]; |
| if (printer_name == printer.name) { |
| const char* info = cupsGetOption(kCUPSPrinterMakeModelOpt, |
| printer.num_options, |
| printer.options); |
| if (info) |
| result = *info; |
| } |
| } |
| |
| cupsFreeDests(num_dests, destinations); |
| return result; |
| } |
| |
| bool PrintBackendCUPS::IsValidPrinter(const std::string& printer_name) { |
| // This is not very efficient way to get specific printer info. CUPS 1.4 |
| // supports cupsGetNamedDest() function. However, CUPS 1.4 is not available |
| // everywhere (for example, it supported from Mac OS 10.6 only). |
| PrinterList printer_list; |
| EnumeratePrinters(&printer_list); |
| |
| PrinterList::iterator it; |
| for (it = printer_list.begin(); it != printer_list.end(); ++it) |
| if (it->printer_name == printer_name) |
| return true; |
| return false; |
| } |
| |
| scoped_refptr<PrintBackend> PrintBackend::CreateInstance( |
| const DictionaryValue* print_backend_settings) { |
| #if !defined(OS_MACOSX) |
| // Initialize gcrypt library. |
| g_gcrypt_initializer.Get(); |
| #endif |
| |
| std::string print_server_url_str, cups_blocking; |
| int encryption = HTTP_ENCRYPT_NEVER; |
| if (print_backend_settings) { |
| print_backend_settings->GetString(kCUPSPrintServerURL, |
| &print_server_url_str); |
| |
| print_backend_settings->GetString(kCUPSBlocking, |
| &cups_blocking); |
| |
| print_backend_settings->GetInteger(kCUPSEncryption, &encryption); |
| } |
| GURL print_server_url(print_server_url_str.c_str()); |
| return new PrintBackendCUPS(print_server_url, |
| static_cast<http_encryption_t>(encryption), |
| cups_blocking == kValueTrue); |
| } |
| |
| int PrintBackendCUPS::GetDests(cups_dest_t** dests) { |
| if (print_server_url_.is_empty()) { // Use default (local) print server. |
| return cupsGetDests(dests); |
| } else { |
| HttpConnectionCUPS http(print_server_url_, cups_encryption_); |
| http.SetBlocking(blocking_); |
| return cupsGetDests2(http.http(), dests); |
| } |
| } |
| |
| FilePath PrintBackendCUPS::GetPPD(const char* name) { |
| // cupsGetPPD returns a filename stored in a static buffer in CUPS. |
| // Protect this code with lock. |
| CR_DEFINE_STATIC_LOCAL(base::Lock, ppd_lock, ()); |
| base::AutoLock ppd_autolock(ppd_lock); |
| FilePath ppd_path; |
| const char* ppd_file_path = NULL; |
| if (print_server_url_.is_empty()) { // Use default (local) print server. |
| ppd_file_path = cupsGetPPD(name); |
| if (ppd_file_path) |
| ppd_path = FilePath(ppd_file_path); |
| } else { |
| // cupsGetPPD2 gets stuck sometimes in an infinite time due to network |
| // configuration/issues. To prevent that, use non-blocking http connection |
| // here. |
| // Note: After looking at CUPS sources, it looks like non-blocking |
| // connection will timeout after 10 seconds of no data period. And it will |
| // return the same way as if data was completely and sucessfully downloaded. |
| HttpConnectionCUPS http(print_server_url_, cups_encryption_); |
| http.SetBlocking(blocking_); |
| ppd_file_path = cupsGetPPD2(http.http(), name); |
| // Check if the get full PPD, since non-blocking call may simply return |
| // normally after timeout expired. |
| if (ppd_file_path) { |
| // There is no reliable way right now to detect full and complete PPD |
| // get downloaded. If we reach http timeout, it may simply return |
| // downloaded part as a full response. It might be good enough to check |
| // http->data_remaining or http->_data_remaining, unfortunately http_t |
| // is an internal structure and fields are not exposed in CUPS headers. |
| // httpGetLength or httpGetLength2 returning the full content size. |
| // Comparing file size against that content length might be unreliable |
| // since some http reponses are encoded and content_length > file size. |
| // Let's just check for the obvious CUPS and http errors here. |
| ppd_path = FilePath(ppd_file_path); |
| ipp_status_t error_code = cupsLastError(); |
| int http_error = httpError(http.http()); |
| if (error_code > IPP_OK_EVENTS_COMPLETE || http_error != 0) { |
| LOG(ERROR) << "Error downloading PPD file" |
| << ", name: " << name |
| << ", CUPS error: " << static_cast<int>(error_code) |
| << ", HTTP error: " << http_error; |
| file_util::Delete(ppd_path, false); |
| ppd_path.clear(); |
| } |
| } |
| } |
| return ppd_path; |
| } |
| |
| } // namespace printing |