• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
22 namespace printing {
23 
24 // This section contains helper code for PPD parsing for semantic capabilities.
25 namespace {
26 
27 const char kColorDevice[] = "ColorDevice";
28 const char kColorModel[] = "ColorModel";
29 const char kColorMode[] = "ColorMode";
30 const char kProcessColorModel[] = "ProcessColorModel";
31 const char kPrintoutMode[] = "PrintoutMode";
32 const char kDraftGray[] = "Draft.Gray";
33 const char kHighGray[] = "High.Gray";
34 
35 const char kDuplex[] = "Duplex";
36 const char kDuplexNone[] = "None";
37 const char kPageSize[] = "PageSize";
38 
39 const double kMicronsPerPoint = 10.0f * kHundrethsMMPerInch / kPointsPerInch;
40 
ParseLpOptions(const base::FilePath & filepath,const std::string & printer_name,int * num_options,cups_option_t ** options)41 void 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 
MarkLpOptions(const std::string & printer_name,ppd_file_t ** ppd)97 void 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 
GetBasicColorModelSettings(ppd_file_t * ppd,ColorModel * color_model_for_black,ColorModel * color_model_for_color,bool * color_is_default)122 bool 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 
GetPrintOutModeColorSettings(ppd_file_t * ppd,ColorModel * color_model_for_black,ColorModel * color_model_for_color,bool * color_is_default)167 bool 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 
GetColorModeSettings(ppd_file_t * ppd,ColorModel * color_model_for_black,ColorModel * color_model_for_color,bool * color_is_default)204 bool 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 
GetHPColorSettings(ppd_file_t * ppd,ColorModel * color_model_for_black,ColorModel * color_model_for_color,bool * color_is_default)232 bool 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 
GetProcessColorModelSettings(ppd_file_t * ppd,ColorModel * color_model_for_black,ColorModel * color_model_for_color,bool * color_is_default)258 bool 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 
GetColorModelSettings(ppd_file_t * ppd,ColorModel * cm_black,ColorModel * cm_color,bool * is_color)288 bool 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.
307 const int kDefaultIPPServerPort = 631;
308 
309 }  // namespace
310 
311 // Helper wrapper around http_t structure, with connection and cleanup
312 // functionality.
HttpConnectionCUPS(const GURL & print_server_url,http_encryption_t encryption)313 HttpConnectionCUPS::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 
~HttpConnectionCUPS()331 HttpConnectionCUPS::~HttpConnectionCUPS() {
332   if (http_ != NULL)
333     httpClose(http_);
334 }
335 
SetBlocking(bool blocking)336 void HttpConnectionCUPS::SetBlocking(bool blocking) {
337   httpBlocking(http_, blocking ?  1 : 0);
338 }
339 
http()340 http_t* HttpConnectionCUPS::http() {
341   return http_;
342 }
343 
ParsePpdCapabilities(const std::string & printer_name,const std::string & printer_capabilities,PrinterSemanticCapsAndDefaults * printer_info)344 bool 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