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
19namespace printing {
20
21// This section contains helper code for PPD parsing for semantic capabilities.
22namespace {
23
24const char kColorDevice[] = "ColorDevice";
25const char kColorModel[] = "ColorModel";
26const char kColorMode[] = "ColorMode";
27const char kProcessColorModel[] = "ProcessColorModel";
28const char kPrintoutMode[] = "PrintoutMode";
29const char kDraftGray[] = "Draft.Gray";
30const char kHighGray[] = "High.Gray";
31
32const char kDuplex[] = "Duplex";
33const char kDuplexNone[] = "None";
34
35#if !defined(OS_MACOSX)
36void 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
91void 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
117bool 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
162bool 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
199bool 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
227bool 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
253bool 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
283bool 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.
302const int kDefaultIPPServerPort = 631;
303
304}  // namespace
305
306// Helper wrapper around http_t structure, with connection and cleanup
307// functionality.
308HttpConnectionCUPS::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
326HttpConnectionCUPS::~HttpConnectionCUPS() {
327  if (http_ != NULL)
328    httpClose(http_);
329}
330
331void HttpConnectionCUPS::SetBlocking(bool blocking) {
332  httpBlocking(http_, blocking ?  1 : 0);
333}
334
335http_t* HttpConnectionCUPS::http() {
336  return http_;
337}
338
339bool 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