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/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 
     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 
     41 #if !defined(OS_MACOSX)
     42 void 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 
     98 void 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 
    125 bool 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 
    170 bool 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 
    207 bool 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 
    235 bool 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 
    261 bool 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 
    291 bool 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.
    310 const int kDefaultIPPServerPort = 631;
    311 
    312 }  // namespace
    313 
    314 // Helper wrapper around http_t structure, with connection and cleanup
    315 // functionality.
    316 HttpConnectionCUPS::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 
    334 HttpConnectionCUPS::~HttpConnectionCUPS() {
    335   if (http_ != NULL)
    336     httpClose(http_);
    337 }
    338 
    339 void HttpConnectionCUPS::SetBlocking(bool blocking) {
    340   httpBlocking(http_, blocking ?  1 : 0);
    341 }
    342 
    343 http_t* HttpConnectionCUPS::http() {
    344   return http_;
    345 }
    346 
    347 bool 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