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