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/cups_helper.h"
6
7 #include <cups/ppd.h>
8
9 #include "base/file_util.h"
10 #include "base/logging.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_split.h"
13 #include "base/strings/string_util.h"
14 #include "base/values.h"
15 #include "printing/backend/print_backend.h"
16 #include "printing/backend/print_backend_consts.h"
17 #include "url/gurl.h"
18
19 namespace printing {
20
21 // This section contains helper code for PPD parsing for semantic capabilities.
22 namespace {
23
24 const char kColorDevice[] = "ColorDevice";
25 const char kColorModel[] = "ColorModel";
26 const char kColorMode[] = "ColorMode";
27 const char kProcessColorModel[] = "ProcessColorModel";
28 const char kPrintoutMode[] = "PrintoutMode";
29 const char kDraftGray[] = "Draft.Gray";
30 const char kHighGray[] = "High.Gray";
31
32 const char kDuplex[] = "Duplex";
33 const char kDuplexNone[] = "None";
34
35 #if !defined(OS_MACOSX)
ParseLpOptions(const base::FilePath & filepath,const std::string & printer_name,int * num_options,cups_option_t ** options)36 void ParseLpOptions(const base::FilePath& filepath,
37 const std::string& printer_name,
38 int* num_options, cups_option_t** options) {
39 std::string content;
40 if (!base::ReadFileToString(filepath, &content))
41 return;
42
43 const char kDest[] = "dest";
44 const char kDefault[] = "default";
45 const size_t kDestLen = sizeof(kDest) - 1;
46 const size_t kDefaultLen = sizeof(kDefault) - 1;
47 std::vector<std::string> lines;
48 base::SplitString(content, '\n', &lines);
49
50 for (size_t i = 0; i < lines.size(); ++i) {
51 std::string line = lines[i];
52 if (line.empty())
53 continue;
54
55 if (base::strncasecmp (line.c_str(), kDefault, kDefaultLen) == 0 &&
56 isspace(line[kDefaultLen])) {
57 line = line.substr(kDefaultLen);
58 } else if (base::strncasecmp (line.c_str(), kDest, kDestLen) == 0 &&
59 isspace(line[kDestLen])) {
60 line = line.substr(kDestLen);
61 } else {
62 continue;
63 }
64
65 TrimWhitespaceASCII(line, TRIM_ALL, &line);
66 if (line.empty())
67 continue;
68
69 size_t space_found = line.find(' ');
70 if (space_found == std::string::npos)
71 continue;
72
73 std::string name = line.substr(0, space_found);
74 if (name.empty())
75 continue;
76
77 if (base::strncasecmp(printer_name.c_str(), name.c_str(),
78 name.length()) != 0) {
79 continue; // This is not the required printer.
80 }
81
82 line = line.substr(space_found + 1);
83 TrimWhitespaceASCII(line, TRIM_ALL, &line); // Remove extra spaces.
84 if (line.empty())
85 continue;
86 // Parse the selected printer custom options.
87 *num_options = cupsParseOptions(line.c_str(), 0, options);
88 }
89 }
90
MarkLpOptions(const std::string & printer_name,ppd_file_t ** ppd)91 void MarkLpOptions(const std::string& printer_name, ppd_file_t** ppd) {
92 cups_option_t* options = NULL;
93 int num_options = 0;
94 ppdMarkDefaults(*ppd);
95
96 const char kSystemLpOptionPath[] = "/etc/cups/lpoptions";
97 const char kUserLpOptionPath[] = ".cups/lpoptions";
98
99 std::vector<base::FilePath> file_locations;
100 file_locations.push_back(base::FilePath(kSystemLpOptionPath));
101 file_locations.push_back(base::FilePath(
102 base::GetHomeDir().Append(kUserLpOptionPath)));
103
104 for (std::vector<base::FilePath>::const_iterator it = file_locations.begin();
105 it != file_locations.end(); ++it) {
106 num_options = 0;
107 options = NULL;
108 ParseLpOptions(*it, printer_name, &num_options, &options);
109 if (num_options > 0 && options) {
110 cupsMarkOptions(*ppd, num_options, options);
111 cupsFreeOptions(num_options, options);
112 }
113 }
114 }
115 #endif // !defined(OS_MACOSX)
116
GetBasicColorModelSettings(ppd_file_t * ppd,ColorModel * color_model_for_black,ColorModel * color_model_for_color,bool * color_is_default)117 bool GetBasicColorModelSettings(ppd_file_t* ppd,
118 ColorModel* color_model_for_black,
119 ColorModel* color_model_for_color,
120 bool* color_is_default) {
121 ppd_option_t* color_model = ppdFindOption(ppd, kColorModel);
122 if (!color_model)
123 return false;
124
125 if (ppdFindChoice(color_model, printing::kBlack))
126 *color_model_for_black = printing::BLACK;
127 else if (ppdFindChoice(color_model, printing::kGray))
128 *color_model_for_black = printing::GRAY;
129 else if (ppdFindChoice(color_model, printing::kGrayscale))
130 *color_model_for_black = printing::GRAYSCALE;
131
132 if (ppdFindChoice(color_model, printing::kColor))
133 *color_model_for_color = printing::COLOR;
134 else if (ppdFindChoice(color_model, printing::kCMYK))
135 *color_model_for_color = printing::CMYK;
136 else if (ppdFindChoice(color_model, printing::kRGB))
137 *color_model_for_color = printing::RGB;
138 else if (ppdFindChoice(color_model, printing::kRGBA))
139 *color_model_for_color = printing::RGBA;
140 else if (ppdFindChoice(color_model, printing::kRGB16))
141 *color_model_for_color = printing::RGB16;
142 else if (ppdFindChoice(color_model, printing::kCMY))
143 *color_model_for_color = printing::CMY;
144 else if (ppdFindChoice(color_model, printing::kKCMY))
145 *color_model_for_color = printing::KCMY;
146 else if (ppdFindChoice(color_model, printing::kCMY_K))
147 *color_model_for_color = printing::CMY_K;
148
149 ppd_choice_t* marked_choice = ppdFindMarkedChoice(ppd, kColorModel);
150 if (!marked_choice)
151 marked_choice = ppdFindChoice(color_model, color_model->defchoice);
152
153 if (marked_choice) {
154 *color_is_default =
155 (base::strcasecmp(marked_choice->choice, printing::kBlack) != 0) &&
156 (base::strcasecmp(marked_choice->choice, printing::kGray) != 0) &&
157 (base::strcasecmp(marked_choice->choice, printing::kGrayscale) != 0);
158 }
159 return true;
160 }
161
GetPrintOutModeColorSettings(ppd_file_t * ppd,ColorModel * color_model_for_black,ColorModel * color_model_for_color,bool * color_is_default)162 bool GetPrintOutModeColorSettings(ppd_file_t* ppd,
163 ColorModel* color_model_for_black,
164 ColorModel* color_model_for_color,
165 bool* color_is_default) {
166 ppd_option_t* printout_mode = ppdFindOption(ppd, kPrintoutMode);
167 if (!printout_mode)
168 return false;
169
170 *color_model_for_color = printing::PRINTOUTMODE_NORMAL;
171 *color_model_for_black = printing::PRINTOUTMODE_NORMAL;
172
173 // Check to see if NORMAL_GRAY value is supported by PrintoutMode.
174 // If NORMAL_GRAY is not supported, NORMAL value is used to
175 // represent grayscale. If NORMAL_GRAY is supported, NORMAL is used to
176 // represent color.
177 if (ppdFindChoice(printout_mode, printing::kNormalGray))
178 *color_model_for_black = printing::PRINTOUTMODE_NORMAL_GRAY;
179
180 // Get the default marked choice to identify the default color setting
181 // value.
182 ppd_choice_t* printout_mode_choice = ppdFindMarkedChoice(ppd, kPrintoutMode);
183 if (!printout_mode_choice) {
184 printout_mode_choice = ppdFindChoice(printout_mode,
185 printout_mode->defchoice);
186 }
187 if (printout_mode_choice) {
188 if ((base::strcasecmp(printout_mode_choice->choice,
189 printing::kNormalGray) == 0) ||
190 (base::strcasecmp(printout_mode_choice->choice, kHighGray) == 0) ||
191 (base::strcasecmp(printout_mode_choice->choice, kDraftGray) == 0)) {
192 *color_model_for_black = printing::PRINTOUTMODE_NORMAL_GRAY;
193 *color_is_default = false;
194 }
195 }
196 return true;
197 }
198
GetColorModeSettings(ppd_file_t * ppd,ColorModel * color_model_for_black,ColorModel * color_model_for_color,bool * color_is_default)199 bool GetColorModeSettings(ppd_file_t* ppd,
200 ColorModel* color_model_for_black,
201 ColorModel* color_model_for_color,
202 bool* color_is_default) {
203 // Samsung printers use "ColorMode" attribute in their ppds.
204 ppd_option_t* color_mode_option = ppdFindOption(ppd, kColorMode);
205 if (!color_mode_option)
206 return false;
207
208 if (ppdFindChoice(color_mode_option, printing::kColor))
209 *color_model_for_color = printing::COLORMODE_COLOR;
210
211 if (ppdFindChoice(color_mode_option, printing::kMonochrome))
212 *color_model_for_black = printing::COLORMODE_MONOCHROME;
213
214 ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kColorMode);
215 if (!mode_choice) {
216 mode_choice = ppdFindChoice(color_mode_option,
217 color_mode_option->defchoice);
218 }
219
220 if (mode_choice) {
221 *color_is_default =
222 (base::strcasecmp(mode_choice->choice, printing::kColor) == 0);
223 }
224 return true;
225 }
226
GetHPColorSettings(ppd_file_t * ppd,ColorModel * color_model_for_black,ColorModel * color_model_for_color,bool * color_is_default)227 bool GetHPColorSettings(ppd_file_t* ppd,
228 ColorModel* color_model_for_black,
229 ColorModel* color_model_for_color,
230 bool* color_is_default) {
231 // HP printers use "Color/Color Model" attribute in their ppds.
232 ppd_option_t* color_mode_option = ppdFindOption(ppd, printing::kColor);
233 if (!color_mode_option)
234 return false;
235
236 if (ppdFindChoice(color_mode_option, printing::kColor))
237 *color_model_for_color = printing::HP_COLOR_COLOR;
238 if (ppdFindChoice(color_mode_option, printing::kBlack))
239 *color_model_for_black = printing::HP_COLOR_BLACK;
240
241 ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kColorMode);
242 if (!mode_choice) {
243 mode_choice = ppdFindChoice(color_mode_option,
244 color_mode_option->defchoice);
245 }
246 if (mode_choice) {
247 *color_is_default =
248 (base::strcasecmp(mode_choice->choice, printing::kColor) == 0);
249 }
250 return true;
251 }
252
GetProcessColorModelSettings(ppd_file_t * ppd,ColorModel * color_model_for_black,ColorModel * color_model_for_color,bool * color_is_default)253 bool GetProcessColorModelSettings(ppd_file_t* ppd,
254 ColorModel* color_model_for_black,
255 ColorModel* color_model_for_color,
256 bool* color_is_default) {
257 // Canon printers use "ProcessColorModel" attribute in their ppds.
258 ppd_option_t* color_mode_option = ppdFindOption(ppd, kProcessColorModel);
259 if (!color_mode_option)
260 return false;
261
262 if (ppdFindChoice(color_mode_option, printing::kRGB))
263 *color_model_for_color = printing::PROCESSCOLORMODEL_RGB;
264 else if (ppdFindChoice(color_mode_option, printing::kCMYK))
265 *color_model_for_color = printing::PROCESSCOLORMODEL_CMYK;
266
267 if (ppdFindChoice(color_mode_option, printing::kGreyscale))
268 *color_model_for_black = printing::PROCESSCOLORMODEL_GREYSCALE;
269
270 ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kProcessColorModel);
271 if (!mode_choice) {
272 mode_choice = ppdFindChoice(color_mode_option,
273 color_mode_option->defchoice);
274 }
275
276 if (mode_choice) {
277 *color_is_default =
278 (base::strcasecmp(mode_choice->choice, printing::kGreyscale) != 0);
279 }
280 return true;
281 }
282
GetColorModelSettings(ppd_file_t * ppd,ColorModel * cm_black,ColorModel * cm_color,bool * is_color)283 bool GetColorModelSettings(ppd_file_t* ppd,
284 ColorModel* cm_black,
285 ColorModel* cm_color,
286 bool* is_color) {
287 bool is_color_device = false;
288 ppd_attr_t* attr = ppdFindAttr(ppd, kColorDevice, NULL);
289 if (attr && attr->value)
290 is_color_device = ppd->color_device;
291
292 *is_color = is_color_device;
293 return (is_color_device &&
294 GetBasicColorModelSettings(ppd, cm_black, cm_color, is_color)) ||
295 GetPrintOutModeColorSettings(ppd, cm_black, cm_color, is_color) ||
296 GetColorModeSettings(ppd, cm_black, cm_color, is_color) ||
297 GetHPColorSettings(ppd, cm_black, cm_color, is_color) ||
298 GetProcessColorModelSettings(ppd, cm_black, cm_color, is_color);
299 }
300
301 // Default port for IPP print servers.
302 const int kDefaultIPPServerPort = 631;
303
304 } // namespace
305
306 // Helper wrapper around http_t structure, with connection and cleanup
307 // functionality.
HttpConnectionCUPS(const GURL & print_server_url,http_encryption_t encryption)308 HttpConnectionCUPS::HttpConnectionCUPS(const GURL& print_server_url,
309 http_encryption_t encryption)
310 : http_(NULL) {
311 // If we have an empty url, use default print server.
312 if (print_server_url.is_empty())
313 return;
314
315 int port = print_server_url.IntPort();
316 if (port == url_parse::PORT_UNSPECIFIED)
317 port = kDefaultIPPServerPort;
318
319 http_ = httpConnectEncrypt(print_server_url.host().c_str(), port, encryption);
320 if (http_ == NULL) {
321 LOG(ERROR) << "CP_CUPS: Failed connecting to print server: "
322 << print_server_url;
323 }
324 }
325
~HttpConnectionCUPS()326 HttpConnectionCUPS::~HttpConnectionCUPS() {
327 if (http_ != NULL)
328 httpClose(http_);
329 }
330
SetBlocking(bool blocking)331 void HttpConnectionCUPS::SetBlocking(bool blocking) {
332 httpBlocking(http_, blocking ? 1 : 0);
333 }
334
http()335 http_t* HttpConnectionCUPS::http() {
336 return http_;
337 }
338
ParsePpdCapabilities(const std::string & printer_name,const std::string & printer_capabilities,PrinterSemanticCapsAndDefaults * printer_info)339 bool ParsePpdCapabilities(
340 const std::string& printer_name,
341 const std::string& printer_capabilities,
342 PrinterSemanticCapsAndDefaults* printer_info) {
343 base::FilePath ppd_file_path;
344 if (!base::CreateTemporaryFile(&ppd_file_path))
345 return false;
346
347 int data_size = printer_capabilities.length();
348 if (data_size != file_util::WriteFile(
349 ppd_file_path,
350 printer_capabilities.data(),
351 data_size)) {
352 base::DeleteFile(ppd_file_path, false);
353 return false;
354 }
355
356 ppd_file_t* ppd = ppdOpenFile(ppd_file_path.value().c_str());
357 if (!ppd)
358 return false;
359
360 printing::PrinterSemanticCapsAndDefaults caps;
361 #if !defined(OS_MACOSX)
362 MarkLpOptions(printer_name, &ppd);
363 #endif
364 ppd_choice_t* duplex_choice = ppdFindMarkedChoice(ppd, kDuplex);
365 if (!duplex_choice) {
366 ppd_option_t* option = ppdFindOption(ppd, kDuplex);
367 if (option)
368 duplex_choice = ppdFindChoice(option, option->defchoice);
369 }
370
371 if (duplex_choice) {
372 caps.duplex_capable = true;
373 if (base::strcasecmp(duplex_choice->choice, kDuplexNone) != 0)
374 caps.duplex_default = printing::LONG_EDGE;
375 else
376 caps.duplex_default = printing::SIMPLEX;
377 }
378
379 bool is_color = false;
380 ColorModel cm_color = UNKNOWN_COLOR_MODEL, cm_black = UNKNOWN_COLOR_MODEL;
381 if (!GetColorModelSettings(ppd, &cm_black, &cm_color, &is_color)) {
382 VLOG(1) << "Unknown printer color model";
383 }
384
385 caps.color_changeable = ((cm_color != UNKNOWN_COLOR_MODEL) &&
386 (cm_black != UNKNOWN_COLOR_MODEL) &&
387 (cm_color != cm_black));
388 caps.color_default = is_color;
389 caps.color_model = cm_color;
390 caps.bw_model = cm_black;
391
392 ppdClose(ppd);
393 base::DeleteFile(ppd_file_path, false);
394
395 *printer_info = caps;
396 return true;
397 }
398
399 } // namespace printing
400