Home | History | Annotate | Download | only in backend
      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 
     19 // This section contains helper code for PPD parsing for semantic capabilities.
     20 namespace {
     21 
     22 const char kColorDevice[] = "ColorDevice";
     23 const char kColorModel[] = "ColorModel";
     24 const char kColorMode[] = "ColorMode";
     25 const char kProcessColorModel[] = "ProcessColorModel";
     26 const char kPrintoutMode[] = "PrintoutMode";
     27 const char kDraftGray[] = "Draft.Gray";
     28 const char kHighGray[] = "High.Gray";
     29 
     30 const char kDuplex[] = "Duplex";
     31 const char kDuplexNone[] = "None";
     32 
     33 #if !defined(OS_MACOSX)
     34 void 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 
     89 void 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 
    115 bool 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 
    160 bool 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 
    197 bool 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 
    225 bool 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 
    251 bool 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 
    281 bool 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 
    301 namespace printing {
    302 
    303 // Default port for IPP print servers.
    304 static const int kDefaultIPPServerPort = 631;
    305 
    306 // Helper wrapper around http_t structure, with connection and cleanup
    307 // functionality.
    308 HttpConnectionCUPS::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 
    327 HttpConnectionCUPS::~HttpConnectionCUPS() {
    328   if (http_ != NULL)
    329     httpClose(http_);
    330 }
    331 
    332 void HttpConnectionCUPS::SetBlocking(bool blocking) {
    333   httpBlocking(http_, blocking ?  1 : 0);
    334 }
    335 
    336 http_t* HttpConnectionCUPS::http() {
    337   return http_;
    338 }
    339 
    340 bool 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::DeleteFile(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::DeleteFile(ppd_file_path, false);
    391 
    392   *printer_info = caps;
    393   return true;
    394 }
    395 
    396 }  // namespace printing
    397