blob: 9fff0c5b57e65efdbea028ad10575de215abcd02 [file] [log] [blame]
// 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