| // 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 <cups/ppd.h> |
| #include <stddef.h> |
| |
| #include <string> |
| #include <vector> |
| |
| #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" |
| |
| using base::EqualsCaseInsensitiveASCII; |
| |
| 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"; |
| |
| constexpr char kDuplex[] = "Duplex"; |
| constexpr char kDuplexNone[] = "None"; |
| constexpr char kDuplexNoTumble[] = "DuplexNoTumble"; |
| constexpr char kDuplexTumble[] = "DuplexTumble"; |
| constexpr char kPageSize[] = "PageSize"; |
| |
| // Brother printer specific options. |
| constexpr char kBrotherDuplex[] = "BRDuplex"; |
| constexpr char kBrotherMonoColor[] = "BRMonoColor"; |
| constexpr char kBrotherPrintQuality[] = "BRPrintQuality"; |
| |
| // Samsung printer specific options. |
| constexpr char kSamsungColorTrue[] = "True"; |
| constexpr char kSamsungColorFalse[] = "False"; |
| |
| void ParseLpOptions(const base::FilePath& filepath, |
| base::StringPiece 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 (!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(base::StringPiece printer_name, ppd_file_t** ppd) { |
| cups_option_t* options = nullptr; |
| int num_options = 0; |
| |
| const char kSystemLpOptionPath[] = "/etc/cups/lpoptions"; |
| const char kUserLpOptionPath[] = ".cups/lpoptions"; |
| |
| std::vector<base::FilePath> file_locations; |
| file_locations.push_back(base::FilePath(kSystemLpOptionPath)); |
| base::FilePath homedir; |
| base::PathService::Get(base::DIR_HOME, &homedir); |
| file_locations.push_back(base::FilePath(homedir.Append(kUserLpOptionPath))); |
| |
| for (const base::FilePath& location : file_locations) { |
| num_options = 0; |
| options = nullptr; |
| ParseLpOptions(location, printer_name, &num_options, &options); |
| if (num_options > 0 && options) { |
| cupsMarkOptions(*ppd, num_options, options); |
| cupsFreeOptions(num_options, options); |
| } |
| } |
| } |
| |
| void GetDuplexSettings(ppd_file_t* ppd, |
| std::vector<DuplexMode>* duplex_modes, |
| DuplexMode* duplex_default) { |
| ppd_choice_t* duplex_choice = ppdFindMarkedChoice(ppd, kDuplex); |
| ppd_option_t* option = ppdFindOption(ppd, kDuplex); |
| if (!option) |
| option = ppdFindOption(ppd, kBrotherDuplex); |
| |
| if (!option) |
| return; |
| |
| if (!duplex_choice) |
| duplex_choice = ppdFindChoice(option, option->defchoice); |
| |
| if (ppdFindChoice(option, kDuplexNone)) |
| duplex_modes->push_back(SIMPLEX); |
| |
| if (ppdFindChoice(option, kDuplexNoTumble)) |
| duplex_modes->push_back(LONG_EDGE); |
| |
| if (ppdFindChoice(option, kDuplexTumble)) |
| duplex_modes->push_back(SHORT_EDGE); |
| |
| if (!duplex_choice) |
| return; |
| |
| const char* choice = duplex_choice->choice; |
| if (EqualsCaseInsensitiveASCII(choice, kDuplexNone)) { |
| *duplex_default = SIMPLEX; |
| } else if (EqualsCaseInsensitiveASCII(choice, kDuplexTumble)) { |
| *duplex_default = SHORT_EDGE; |
| } else { |
| *duplex_default = LONG_EDGE; |
| } |
| } |
| |
| 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, kBlack)) |
| *color_model_for_black = BLACK; |
| else if (ppdFindChoice(color_model, kGray)) |
| *color_model_for_black = GRAY; |
| else if (ppdFindChoice(color_model, kGrayscale)) |
| *color_model_for_black = GRAYSCALE; |
| |
| if (ppdFindChoice(color_model, kColor)) |
| *color_model_for_color = COLOR; |
| else if (ppdFindChoice(color_model, kCMYK)) |
| *color_model_for_color = CMYK; |
| else if (ppdFindChoice(color_model, kRGB)) |
| *color_model_for_color = RGB; |
| else if (ppdFindChoice(color_model, kRGBA)) |
| *color_model_for_color = RGBA; |
| else if (ppdFindChoice(color_model, kRGB16)) |
| *color_model_for_color = RGB16; |
| else if (ppdFindChoice(color_model, kCMY)) |
| *color_model_for_color = CMY; |
| else if (ppdFindChoice(color_model, kKCMY)) |
| *color_model_for_color = KCMY; |
| else if (ppdFindChoice(color_model, kCMY_K)) |
| *color_model_for_color = 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 = |
| !EqualsCaseInsensitiveASCII(marked_choice->choice, kBlack) && |
| !EqualsCaseInsensitiveASCII(marked_choice->choice, kGray) && |
| !EqualsCaseInsensitiveASCII(marked_choice->choice, 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 = PRINTOUTMODE_NORMAL; |
| *color_model_for_black = 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, kNormalGray)) |
| *color_model_for_black = 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 (EqualsCaseInsensitiveASCII(printout_mode_choice->choice, kNormalGray) || |
| EqualsCaseInsensitiveASCII(printout_mode_choice->choice, kHighGray) || |
| EqualsCaseInsensitiveASCII(printout_mode_choice->choice, kDraftGray)) { |
| *color_model_for_black = 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, kColor) || |
| ppdFindChoice(color_mode_option, kSamsungColorTrue)) { |
| *color_model_for_color = COLORMODE_COLOR; |
| } |
| |
| if (ppdFindChoice(color_mode_option, kMonochrome) || |
| ppdFindChoice(color_mode_option, kSamsungColorFalse)) { |
| *color_model_for_black = 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 = |
| EqualsCaseInsensitiveASCII(mode_choice->choice, kColor) || |
| EqualsCaseInsensitiveASCII(mode_choice->choice, kSamsungColorTrue); |
| } |
| return true; |
| } |
| |
| bool GetBrotherColorSettings(ppd_file_t* ppd, |
| ColorModel* color_model_for_black, |
| ColorModel* color_model_for_color, |
| bool* color_is_default) { |
| // Some Brother printers use "BRMonoColor" attribute in their PPDs. |
| // Some Brother printers use "BRPrintQuality" attribute in their PPDs. |
| ppd_option_t* color_mode_option = ppdFindOption(ppd, kBrotherMonoColor); |
| if (!color_mode_option) |
| color_mode_option = ppdFindOption(ppd, kBrotherPrintQuality); |
| if (!color_mode_option) |
| return false; |
| |
| if (ppdFindChoice(color_mode_option, kFullColor)) |
| *color_model_for_color = BROTHER_CUPS_COLOR; |
| else if (ppdFindChoice(color_mode_option, kColor)) |
| *color_model_for_color = BROTHER_BRSCRIPT3_COLOR; |
| |
| if (ppdFindChoice(color_mode_option, kMono)) |
| *color_model_for_black = BROTHER_CUPS_MONO; |
| else if (ppdFindChoice(color_mode_option, kBlack)) |
| *color_model_for_black = BROTHER_BRSCRIPT3_BLACK; |
| |
| ppd_choice_t* marked_choice = ppdFindMarkedChoice(ppd, kColorMode); |
| if (!marked_choice) { |
| marked_choice = |
| ppdFindChoice(color_mode_option, color_mode_option->defchoice); |
| } |
| if (marked_choice) { |
| *color_is_default = |
| !EqualsCaseInsensitiveASCII(marked_choice->choice, kBlack) && |
| !EqualsCaseInsensitiveASCII(marked_choice->choice, kMono); |
| } |
| 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, kColor); |
| if (!color_mode_option) |
| return false; |
| |
| if (ppdFindChoice(color_mode_option, kColor)) |
| *color_model_for_color = HP_COLOR_COLOR; |
| if (ppdFindChoice(color_mode_option, kBlack)) |
| *color_model_for_black = 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 = EqualsCaseInsensitiveASCII(mode_choice->choice, 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, kRGB)) |
| *color_model_for_color = PROCESSCOLORMODEL_RGB; |
| else if (ppdFindChoice(color_mode_option, kCMYK)) |
| *color_model_for_color = PROCESSCOLORMODEL_CMYK; |
| |
| if (ppdFindChoice(color_mode_option, kGreyscale)) |
| *color_model_for_black = 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 = |
| !EqualsCaseInsensitiveASCII(mode_choice->choice, 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, nullptr); |
| 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) || |
| GetBrotherColorSettings(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_(nullptr) { |
| // 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_) { |
| LOG(ERROR) << "CP_CUPS: Failed connecting to print server: " |
| << print_server_url; |
| } |
| } |
| |
| HttpConnectionCUPS::~HttpConnectionCUPS() { |
| if (http_) |
| httpClose(http_); |
| } |
| |
| void HttpConnectionCUPS::SetBlocking(bool blocking) { |
| httpBlocking(http_, blocking ? 1 : 0); |
| } |
| |
| http_t* HttpConnectionCUPS::http() { |
| return http_; |
| } |
| |
| bool ParsePpdCapabilities(base::StringPiece printer_name, |
| base::StringPiece 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); |
| |
| PrinterSemanticCapsAndDefaults caps; |
| caps.collate_capable = true; |
| caps.collate_default = true; |
| caps.copies_capable = true; |
| |
| GetDuplexSettings(ppd, &caps.duplex_modes, &caps.duplex_default); |
| |
| 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( |
| ConvertUnit(ppd->sizes[i].width, kPointsPerInch, kMicronsPerInch), |
| ConvertUnit(ppd->sizes[i].length, kPointsPerInch, kMicronsPerInch)); |
| 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 |