Add integration browser tests for settings hardening.
[chromium-blink-merge.git] / printing / backend / print_backend_cups.cc
blob0f2f0e791338866d22d440efac2938c415f326a6
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.
5 #include "printing/backend/print_backend.h"
7 #include "build/build_config.h"
9 #include <dlfcn.h>
10 #include <errno.h>
11 #include <pthread.h>
13 #include "base/debug/leak_annotations.h"
14 #include "base/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"
24 namespace printing {
26 static const char kCUPSPrinterInfoOpt[] = "printer-info";
27 static const char kCUPSPrinterStateOpt[] = "printer-state";
28 static const char kCUPSPrinterTypeOpt[] = "printer-type";
29 static const char kCUPSPrinterMakeModelOpt[] = "printer-make-and-model";
31 class PrintBackendCUPS : public PrintBackend {
32 public:
33 PrintBackendCUPS(const GURL& print_server_url,
34 http_encryption_t encryption, bool blocking);
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;
49 protected:
50 virtual ~PrintBackendCUPS() {}
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);
60 GURL print_server_url_;
61 http_encryption_t cups_encryption_;
62 bool blocking_;
65 PrintBackendCUPS::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) {
73 bool PrintBackendCUPS::EnumeratePrinters(PrinterList* printer_list) {
74 DCHECK(printer_list);
75 printer_list->clear();
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;
86 for (int printer_index = 0; printer_index < num_dests; ++printer_index) {
87 const cups_dest_t& printer = destinations[printer_index];
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;
99 PrinterBasicInfo printer_info;
100 printer_info.printer_name = printer.name;
101 printer_info.is_default = printer.is_default;
103 const char* info = cupsGetOption(kCUPSPrinterInfoOpt,
104 printer.num_options, printer.options);
105 if (info != NULL)
106 printer_info.printer_description = info;
108 const char* state = cupsGetOption(kCUPSPrinterStateOpt,
109 printer.num_options, printer.options);
110 if (state != NULL)
111 base::StringToInt(state, &printer_info.printer_status);
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;
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;
125 printer_list->push_back(printer_info);
128 cupsFreeDests(num_dests, destinations);
130 VLOG(1) << "CUPS: Enumerated printers"
131 << ", server: " << print_server_url_
132 << ", # of printers: " << printer_list->size();
133 return true;
136 std::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;
146 bool PrintBackendCUPS::GetPrinterSemanticCapsAndDefaults(
147 const std::string& printer_name,
148 PrinterSemanticCapsAndDefaults* printer_info) {
149 PrinterCapsAndDefaults info;
150 if (!GetPrinterCapsAndDefaults(printer_name, &info) )
151 return false;
153 return ParsePpdCapabilities(
154 printer_name, info.printer_capabilities, printer_info);
157 bool PrintBackendCUPS::GetPrinterCapsAndDefaults(
158 const std::string& printer_name,
159 PrinterCapsAndDefaults* printer_info) {
160 DCHECK(printer_info);
162 VLOG(1) << "CUPS: Getting caps and defaults"
163 << ", printer name: " << printer_name;
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;
172 std::string content;
173 bool res = base::ReadFileToString(ppd_path, &content);
175 base::DeleteFile(ppd_path, false);
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();
185 return res;
188 std::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;
204 cupsFreeDests(num_dests, destinations);
205 return result;
208 bool 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);
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;
222 scoped_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);
230 print_backend_settings->GetString(kCUPSBlocking,
231 &cups_blocking);
233 print_backend_settings->GetInteger(kCUPSEncryption, &encryption);
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);
241 int 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);
257 base::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();
303 return ppd_path;
306 } // namespace printing