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