// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "printing/backend/cups_helper.h" #include #include "base/base_paths.h" #include "base/files/file_util.h" #include "base/logging.h" #include "base/path_service.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/values.h" #include "printing/backend/print_backend.h" #include "printing/backend/print_backend_consts.h" #include "printing/units.h" #include "url/gurl.h" namespace printing { // This section contains helper code for PPD parsing for semantic capabilities. namespace { const char kColorDevice[] = "ColorDevice"; const char kColorModel[] = "ColorModel"; const char kColorMode[] = "ColorMode"; const char kProcessColorModel[] = "ProcessColorModel"; const char kPrintoutMode[] = "PrintoutMode"; const char kDraftGray[] = "Draft.Gray"; const char kHighGray[] = "High.Gray"; const char kDuplex[] = "Duplex"; const char kDuplexNone[] = "None"; const char kPageSize[] = "PageSize"; const double kMicronsPerPoint = 10.0f * kHundrethsMMPerInch / kPointsPerInch; void ParseLpOptions(const base::FilePath& filepath, const std::string& printer_name, int* num_options, cups_option_t** options) { std::string content; if (!base::ReadFileToString(filepath, &content)) return; const char kDest[] = "dest"; const char kDefault[] = "default"; const size_t kDestLen = sizeof(kDest) - 1; const size_t kDefaultLen = sizeof(kDefault) - 1; for (base::StringPiece line : base::SplitStringPiece(content, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) { if (base::StartsWith(line, base::StringPiece(kDefault, kDefaultLen), base::CompareCase::INSENSITIVE_ASCII) && isspace(line[kDefaultLen])) { line = line.substr(kDefaultLen); } else if (base::StartsWith(line, base::StringPiece(kDest, kDestLen), base::CompareCase::INSENSITIVE_ASCII) && isspace(line[kDestLen])) { line = line.substr(kDestLen); } else { continue; } line = base::TrimWhitespaceASCII(line, base::TRIM_ALL); if (line.empty()) continue; size_t space_found = line.find(' '); if (space_found == base::StringPiece::npos) continue; base::StringPiece name = line.substr(0, space_found); if (name.empty()) continue; if (!base::EqualsCaseInsensitiveASCII(printer_name, name)) continue; // This is not the required printer. line = line.substr(space_found + 1); // Remove extra spaces. line = base::TrimWhitespaceASCII(line, base::TRIM_ALL); if (line.empty()) continue; // Parse the selected printer custom options. Need to pass a // null-terminated string. *num_options = cupsParseOptions(line.as_string().c_str(), 0, options); } } void MarkLpOptions(const std::string& printer_name, ppd_file_t** ppd) { cups_option_t* options = NULL; int num_options = 0; const char kSystemLpOptionPath[] = "/etc/cups/lpoptions"; const char kUserLpOptionPath[] = ".cups/lpoptions"; std::vector file_locations; file_locations.push_back(base::FilePath(kSystemLpOptionPath)); base::FilePath homedir; PathService::Get(base::DIR_HOME, &homedir); file_locations.push_back(base::FilePath(homedir.Append(kUserLpOptionPath))); for (std::vector::const_iterator it = file_locations.begin(); it != file_locations.end(); ++it) { num_options = 0; options = NULL; ParseLpOptions(*it, printer_name, &num_options, &options); if (num_options > 0 && options) { cupsMarkOptions(*ppd, num_options, options); cupsFreeOptions(num_options, options); } } } bool GetBasicColorModelSettings(ppd_file_t* ppd, ColorModel* color_model_for_black, ColorModel* color_model_for_color, bool* color_is_default) { ppd_option_t* color_model = ppdFindOption(ppd, kColorModel); if (!color_model) return false; if (ppdFindChoice(color_model, printing::kBlack)) *color_model_for_black = printing::BLACK; else if (ppdFindChoice(color_model, printing::kGray)) *color_model_for_black = printing::GRAY; else if (ppdFindChoice(color_model, printing::kGrayscale)) *color_model_for_black = printing::GRAYSCALE; if (ppdFindChoice(color_model, printing::kColor)) *color_model_for_color = printing::COLOR; else if (ppdFindChoice(color_model, printing::kCMYK)) *color_model_for_color = printing::CMYK; else if (ppdFindChoice(color_model, printing::kRGB)) *color_model_for_color = printing::RGB; else if (ppdFindChoice(color_model, printing::kRGBA)) *color_model_for_color = printing::RGBA; else if (ppdFindChoice(color_model, printing::kRGB16)) *color_model_for_color = printing::RGB16; else if (ppdFindChoice(color_model, printing::kCMY)) *color_model_for_color = printing::CMY; else if (ppdFindChoice(color_model, printing::kKCMY)) *color_model_for_color = printing::KCMY; else if (ppdFindChoice(color_model, printing::kCMY_K)) *color_model_for_color = printing::CMY_K; ppd_choice_t* marked_choice = ppdFindMarkedChoice(ppd, kColorModel); if (!marked_choice) marked_choice = ppdFindChoice(color_model, color_model->defchoice); if (marked_choice) { *color_is_default = !base::EqualsCaseInsensitiveASCII(marked_choice->choice, printing::kBlack) && !base::EqualsCaseInsensitiveASCII(marked_choice->choice, printing::kGray) && !base::EqualsCaseInsensitiveASCII(marked_choice->choice, printing::kGrayscale); } return true; } bool GetPrintOutModeColorSettings(ppd_file_t* ppd, ColorModel* color_model_for_black, ColorModel* color_model_for_color, bool* color_is_default) { ppd_option_t* printout_mode = ppdFindOption(ppd, kPrintoutMode); if (!printout_mode) return false; *color_model_for_color = printing::PRINTOUTMODE_NORMAL; *color_model_for_black = printing::PRINTOUTMODE_NORMAL; // Check to see if NORMAL_GRAY value is supported by PrintoutMode. // If NORMAL_GRAY is not supported, NORMAL value is used to // represent grayscale. If NORMAL_GRAY is supported, NORMAL is used to // represent color. if (ppdFindChoice(printout_mode, printing::kNormalGray)) *color_model_for_black = printing::PRINTOUTMODE_NORMAL_GRAY; // Get the default marked choice to identify the default color setting // value. ppd_choice_t* printout_mode_choice = ppdFindMarkedChoice(ppd, kPrintoutMode); if (!printout_mode_choice) { printout_mode_choice = ppdFindChoice(printout_mode, printout_mode->defchoice); } if (printout_mode_choice) { if (base::EqualsCaseInsensitiveASCII(printout_mode_choice->choice, printing::kNormalGray) || base::EqualsCaseInsensitiveASCII(printout_mode_choice->choice, kHighGray) || base::EqualsCaseInsensitiveASCII(printout_mode_choice->choice, kDraftGray)) { *color_model_for_black = printing::PRINTOUTMODE_NORMAL_GRAY; *color_is_default = false; } } return true; } bool GetColorModeSettings(ppd_file_t* ppd, ColorModel* color_model_for_black, ColorModel* color_model_for_color, bool* color_is_default) { // Samsung printers use "ColorMode" attribute in their ppds. ppd_option_t* color_mode_option = ppdFindOption(ppd, kColorMode); if (!color_mode_option) return false; if (ppdFindChoice(color_mode_option, printing::kColor)) *color_model_for_color = printing::COLORMODE_COLOR; if (ppdFindChoice(color_mode_option, printing::kMonochrome)) *color_model_for_black = printing::COLORMODE_MONOCHROME; ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kColorMode); if (!mode_choice) { mode_choice = ppdFindChoice(color_mode_option, color_mode_option->defchoice); } if (mode_choice) { *color_is_default = base::EqualsCaseInsensitiveASCII( mode_choice->choice, printing::kColor); } return true; } bool GetHPColorSettings(ppd_file_t* ppd, ColorModel* color_model_for_black, ColorModel* color_model_for_color, bool* color_is_default) { // HP printers use "Color/Color Model" attribute in their ppds. ppd_option_t* color_mode_option = ppdFindOption(ppd, printing::kColor); if (!color_mode_option) return false; if (ppdFindChoice(color_mode_option, printing::kColor)) *color_model_for_color = printing::HP_COLOR_COLOR; if (ppdFindChoice(color_mode_option, printing::kBlack)) *color_model_for_black = printing::HP_COLOR_BLACK; ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kColorMode); if (!mode_choice) { mode_choice = ppdFindChoice(color_mode_option, color_mode_option->defchoice); } if (mode_choice) { *color_is_default = base::EqualsCaseInsensitiveASCII( mode_choice->choice, printing::kColor); } return true; } bool GetProcessColorModelSettings(ppd_file_t* ppd, ColorModel* color_model_for_black, ColorModel* color_model_for_color, bool* color_is_default) { // Canon printers use "ProcessColorModel" attribute in their ppds. ppd_option_t* color_mode_option = ppdFindOption(ppd, kProcessColorModel); if (!color_mode_option) return false; if (ppdFindChoice(color_mode_option, printing::kRGB)) *color_model_for_color = printing::PROCESSCOLORMODEL_RGB; else if (ppdFindChoice(color_mode_option, printing::kCMYK)) *color_model_for_color = printing::PROCESSCOLORMODEL_CMYK; if (ppdFindChoice(color_mode_option, printing::kGreyscale)) *color_model_for_black = printing::PROCESSCOLORMODEL_GREYSCALE; ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kProcessColorModel); if (!mode_choice) { mode_choice = ppdFindChoice(color_mode_option, color_mode_option->defchoice); } if (mode_choice) { *color_is_default = !base::EqualsCaseInsensitiveASCII( mode_choice->choice, printing::kGreyscale); } return true; } bool GetColorModelSettings(ppd_file_t* ppd, ColorModel* cm_black, ColorModel* cm_color, bool* is_color) { bool is_color_device = false; ppd_attr_t* attr = ppdFindAttr(ppd, kColorDevice, NULL); if (attr && attr->value) is_color_device = ppd->color_device; *is_color = is_color_device; return (is_color_device && GetBasicColorModelSettings(ppd, cm_black, cm_color, is_color)) || GetPrintOutModeColorSettings(ppd, cm_black, cm_color, is_color) || GetColorModeSettings(ppd, cm_black, cm_color, is_color) || GetHPColorSettings(ppd, cm_black, cm_color, is_color) || GetProcessColorModelSettings(ppd, cm_black, cm_color, is_color); } // Default port for IPP print servers. const int kDefaultIPPServerPort = 631; } // namespace // Helper wrapper around http_t structure, with connection and cleanup // functionality. HttpConnectionCUPS::HttpConnectionCUPS(const GURL& print_server_url, http_encryption_t encryption) : http_(NULL) { // If we have an empty url, use default print server. if (print_server_url.is_empty()) return; int port = print_server_url.IntPort(); if (port == url::PORT_UNSPECIFIED) port = kDefaultIPPServerPort; http_ = httpConnectEncrypt(print_server_url.host().c_str(), port, encryption); if (http_ == NULL) { LOG(ERROR) << "CP_CUPS: Failed connecting to print server: " << print_server_url; } } HttpConnectionCUPS::~HttpConnectionCUPS() { if (http_ != NULL) httpClose(http_); } void HttpConnectionCUPS::SetBlocking(bool blocking) { httpBlocking(http_, blocking ? 1 : 0); } http_t* HttpConnectionCUPS::http() { return http_; } bool ParsePpdCapabilities( const std::string& printer_name, const std::string& printer_capabilities, PrinterSemanticCapsAndDefaults* printer_info) { base::FilePath ppd_file_path; if (!base::CreateTemporaryFile(&ppd_file_path)) return false; int data_size = printer_capabilities.length(); if (data_size != base::WriteFile( ppd_file_path, printer_capabilities.data(), data_size)) { base::DeleteFile(ppd_file_path, false); return false; } ppd_file_t* ppd = ppdOpenFile(ppd_file_path.value().c_str()); if (!ppd) { int line = 0; ppd_status_t ppd_status = ppdLastError(&line); LOG(ERROR) << "Failed to open PDD file: error " << ppd_status << " at line " << line << ", " << ppdErrorString(ppd_status); return false; } ppdMarkDefaults(ppd); MarkLpOptions(printer_name, &ppd); printing::PrinterSemanticCapsAndDefaults caps; caps.collate_capable = true; caps.collate_default = true; caps.copies_capable = true; ppd_choice_t* duplex_choice = ppdFindMarkedChoice(ppd, kDuplex); if (!duplex_choice) { ppd_option_t* option = ppdFindOption(ppd, kDuplex); if (option) duplex_choice = ppdFindChoice(option, option->defchoice); } if (duplex_choice) { caps.duplex_capable = true; if (!base::EqualsCaseInsensitiveASCII(duplex_choice->choice, kDuplexNone)) caps.duplex_default = printing::LONG_EDGE; else caps.duplex_default = printing::SIMPLEX; } bool is_color = false; ColorModel cm_color = UNKNOWN_COLOR_MODEL, cm_black = UNKNOWN_COLOR_MODEL; if (!GetColorModelSettings(ppd, &cm_black, &cm_color, &is_color)) { VLOG(1) << "Unknown printer color model"; } caps.color_changeable = ((cm_color != UNKNOWN_COLOR_MODEL) && (cm_black != UNKNOWN_COLOR_MODEL) && (cm_color != cm_black)); caps.color_default = is_color; caps.color_model = cm_color; caps.bw_model = cm_black; if (ppd->num_sizes > 0 && ppd->sizes) { VLOG(1) << "Paper list size - " << ppd->num_sizes; ppd_option_t* paper_option = ppdFindOption(ppd, kPageSize); for (int i = 0; i < ppd->num_sizes; ++i) { gfx::Size paper_size_microns( static_cast(ppd->sizes[i].width * kMicronsPerPoint + 0.5), static_cast(ppd->sizes[i].length * kMicronsPerPoint + 0.5)); if (paper_size_microns.width() > 0 && paper_size_microns.height() > 0) { PrinterSemanticCapsAndDefaults::Paper paper; paper.size_um = paper_size_microns; paper.vendor_id = ppd->sizes[i].name; if (paper_option) { ppd_choice_t* paper_choice = ppdFindChoice(paper_option, ppd->sizes[i].name); // Human readable paper name should be UTF-8 encoded, but some PPDs // do not follow this standard. if (paper_choice && base::IsStringUTF8(paper_choice->text)) { paper.display_name = paper_choice->text; } } caps.papers.push_back(paper); if (i == 0 || ppd->sizes[i].marked) { caps.default_paper = paper; } } } } ppdClose(ppd); base::DeleteFile(ppd_file_path, false); *printer_info = caps; return true; } } // namespace printing