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