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/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 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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.
    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 
    331 HttpConnectionCUPS::~HttpConnectionCUPS() {
    332   if (http_ != NULL)
    333     httpClose(http_);
    334 }
    335 
    336 void HttpConnectionCUPS::SetBlocking(bool blocking) {
    337   httpBlocking(http_, blocking ?  1 : 0);
    338 }
    339 
    340 http_t* HttpConnectionCUPS::http() {
    341   return http_;
    342 }
    343 
    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