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 22namespace printing { 23 24// This section contains helper code for PPD parsing for semantic capabilities. 25namespace { 26 27const char kColorDevice[] = "ColorDevice"; 28const char kColorModel[] = "ColorModel"; 29const char kColorMode[] = "ColorMode"; 30const char kProcessColorModel[] = "ProcessColorModel"; 31const char kPrintoutMode[] = "PrintoutMode"; 32const char kDraftGray[] = "Draft.Gray"; 33const char kHighGray[] = "High.Gray"; 34 35const char kDuplex[] = "Duplex"; 36const char kDuplexNone[] = "None"; 37const char kPageSize[] = "PageSize"; 38 39const double kMicronsPerPoint = 10.0f * kHundrethsMMPerInch / kPointsPerInch; 40 41void 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 97void 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 122bool 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 167bool 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 204bool 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 232bool 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 258bool 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 288bool 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. 307const int kDefaultIPPServerPort = 631; 308 309} // namespace 310 311// Helper wrapper around http_t structure, with connection and cleanup 312// functionality. 313HttpConnectionCUPS::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 331HttpConnectionCUPS::~HttpConnectionCUPS() { 332 if (http_ != NULL) 333 httpClose(http_); 334} 335 336void HttpConnectionCUPS::SetBlocking(bool blocking) { 337 httpBlocking(http_, blocking ? 1 : 0); 338} 339 340http_t* HttpConnectionCUPS::http() { 341 return http_; 342} 343 344bool 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