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/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 #if !defined(OS_MACOSX) 42 void ParseLpOptions(const base::FilePath& filepath, 43 const std::string& printer_name, 44 int* num_options, cups_option_t** options) { 45 std::string content; 46 if (!base::ReadFileToString(filepath, &content)) 47 return; 48 49 const char kDest[] = "dest"; 50 const char kDefault[] = "default"; 51 const size_t kDestLen = sizeof(kDest) - 1; 52 const size_t kDefaultLen = sizeof(kDefault) - 1; 53 std::vector<std::string> lines; 54 base::SplitString(content, '\n', &lines); 55 56 for (size_t i = 0; i < lines.size(); ++i) { 57 std::string line = lines[i]; 58 if (line.empty()) 59 continue; 60 61 if (base::strncasecmp (line.c_str(), kDefault, kDefaultLen) == 0 && 62 isspace(line[kDefaultLen])) { 63 line = line.substr(kDefaultLen); 64 } else if (base::strncasecmp (line.c_str(), kDest, kDestLen) == 0 && 65 isspace(line[kDestLen])) { 66 line = line.substr(kDestLen); 67 } else { 68 continue; 69 } 70 71 base::TrimWhitespaceASCII(line, base::TRIM_ALL, &line); 72 if (line.empty()) 73 continue; 74 75 size_t space_found = line.find(' '); 76 if (space_found == std::string::npos) 77 continue; 78 79 std::string name = line.substr(0, space_found); 80 if (name.empty()) 81 continue; 82 83 if (base::strncasecmp(printer_name.c_str(), name.c_str(), 84 name.length()) != 0) { 85 continue; // This is not the required printer. 86 } 87 88 line = line.substr(space_found + 1); 89 // Remove extra spaces. 90 base::TrimWhitespaceASCII(line, base::TRIM_ALL, &line); 91 if (line.empty()) 92 continue; 93 // Parse the selected printer custom options. 94 *num_options = cupsParseOptions(line.c_str(), 0, options); 95 } 96 } 97 98 void MarkLpOptions(const std::string& printer_name, ppd_file_t** ppd) { 99 cups_option_t* options = NULL; 100 int num_options = 0; 101 ppdMarkDefaults(*ppd); 102 103 const char kSystemLpOptionPath[] = "/etc/cups/lpoptions"; 104 const char kUserLpOptionPath[] = ".cups/lpoptions"; 105 106 std::vector<base::FilePath> file_locations; 107 file_locations.push_back(base::FilePath(kSystemLpOptionPath)); 108 base::FilePath homedir; 109 PathService::Get(base::DIR_HOME, &homedir); 110 file_locations.push_back(base::FilePath(homedir.Append(kUserLpOptionPath))); 111 112 for (std::vector<base::FilePath>::const_iterator it = file_locations.begin(); 113 it != file_locations.end(); ++it) { 114 num_options = 0; 115 options = NULL; 116 ParseLpOptions(*it, printer_name, &num_options, &options); 117 if (num_options > 0 && options) { 118 cupsMarkOptions(*ppd, num_options, options); 119 cupsFreeOptions(num_options, options); 120 } 121 } 122 } 123 #endif // !defined(OS_MACOSX) 124 125 bool GetBasicColorModelSettings(ppd_file_t* ppd, 126 ColorModel* color_model_for_black, 127 ColorModel* color_model_for_color, 128 bool* color_is_default) { 129 ppd_option_t* color_model = ppdFindOption(ppd, kColorModel); 130 if (!color_model) 131 return false; 132 133 if (ppdFindChoice(color_model, printing::kBlack)) 134 *color_model_for_black = printing::BLACK; 135 else if (ppdFindChoice(color_model, printing::kGray)) 136 *color_model_for_black = printing::GRAY; 137 else if (ppdFindChoice(color_model, printing::kGrayscale)) 138 *color_model_for_black = printing::GRAYSCALE; 139 140 if (ppdFindChoice(color_model, printing::kColor)) 141 *color_model_for_color = printing::COLOR; 142 else if (ppdFindChoice(color_model, printing::kCMYK)) 143 *color_model_for_color = printing::CMYK; 144 else if (ppdFindChoice(color_model, printing::kRGB)) 145 *color_model_for_color = printing::RGB; 146 else if (ppdFindChoice(color_model, printing::kRGBA)) 147 *color_model_for_color = printing::RGBA; 148 else if (ppdFindChoice(color_model, printing::kRGB16)) 149 *color_model_for_color = printing::RGB16; 150 else if (ppdFindChoice(color_model, printing::kCMY)) 151 *color_model_for_color = printing::CMY; 152 else if (ppdFindChoice(color_model, printing::kKCMY)) 153 *color_model_for_color = printing::KCMY; 154 else if (ppdFindChoice(color_model, printing::kCMY_K)) 155 *color_model_for_color = printing::CMY_K; 156 157 ppd_choice_t* marked_choice = ppdFindMarkedChoice(ppd, kColorModel); 158 if (!marked_choice) 159 marked_choice = ppdFindChoice(color_model, color_model->defchoice); 160 161 if (marked_choice) { 162 *color_is_default = 163 (base::strcasecmp(marked_choice->choice, printing::kBlack) != 0) && 164 (base::strcasecmp(marked_choice->choice, printing::kGray) != 0) && 165 (base::strcasecmp(marked_choice->choice, printing::kGrayscale) != 0); 166 } 167 return true; 168 } 169 170 bool GetPrintOutModeColorSettings(ppd_file_t* ppd, 171 ColorModel* color_model_for_black, 172 ColorModel* color_model_for_color, 173 bool* color_is_default) { 174 ppd_option_t* printout_mode = ppdFindOption(ppd, kPrintoutMode); 175 if (!printout_mode) 176 return false; 177 178 *color_model_for_color = printing::PRINTOUTMODE_NORMAL; 179 *color_model_for_black = printing::PRINTOUTMODE_NORMAL; 180 181 // Check to see if NORMAL_GRAY value is supported by PrintoutMode. 182 // If NORMAL_GRAY is not supported, NORMAL value is used to 183 // represent grayscale. If NORMAL_GRAY is supported, NORMAL is used to 184 // represent color. 185 if (ppdFindChoice(printout_mode, printing::kNormalGray)) 186 *color_model_for_black = printing::PRINTOUTMODE_NORMAL_GRAY; 187 188 // Get the default marked choice to identify the default color setting 189 // value. 190 ppd_choice_t* printout_mode_choice = ppdFindMarkedChoice(ppd, kPrintoutMode); 191 if (!printout_mode_choice) { 192 printout_mode_choice = ppdFindChoice(printout_mode, 193 printout_mode->defchoice); 194 } 195 if (printout_mode_choice) { 196 if ((base::strcasecmp(printout_mode_choice->choice, 197 printing::kNormalGray) == 0) || 198 (base::strcasecmp(printout_mode_choice->choice, kHighGray) == 0) || 199 (base::strcasecmp(printout_mode_choice->choice, kDraftGray) == 0)) { 200 *color_model_for_black = printing::PRINTOUTMODE_NORMAL_GRAY; 201 *color_is_default = false; 202 } 203 } 204 return true; 205 } 206 207 bool GetColorModeSettings(ppd_file_t* ppd, 208 ColorModel* color_model_for_black, 209 ColorModel* color_model_for_color, 210 bool* color_is_default) { 211 // Samsung printers use "ColorMode" attribute in their ppds. 212 ppd_option_t* color_mode_option = ppdFindOption(ppd, kColorMode); 213 if (!color_mode_option) 214 return false; 215 216 if (ppdFindChoice(color_mode_option, printing::kColor)) 217 *color_model_for_color = printing::COLORMODE_COLOR; 218 219 if (ppdFindChoice(color_mode_option, printing::kMonochrome)) 220 *color_model_for_black = printing::COLORMODE_MONOCHROME; 221 222 ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kColorMode); 223 if (!mode_choice) { 224 mode_choice = ppdFindChoice(color_mode_option, 225 color_mode_option->defchoice); 226 } 227 228 if (mode_choice) { 229 *color_is_default = 230 (base::strcasecmp(mode_choice->choice, printing::kColor) == 0); 231 } 232 return true; 233 } 234 235 bool GetHPColorSettings(ppd_file_t* ppd, 236 ColorModel* color_model_for_black, 237 ColorModel* color_model_for_color, 238 bool* color_is_default) { 239 // HP printers use "Color/Color Model" attribute in their ppds. 240 ppd_option_t* color_mode_option = ppdFindOption(ppd, printing::kColor); 241 if (!color_mode_option) 242 return false; 243 244 if (ppdFindChoice(color_mode_option, printing::kColor)) 245 *color_model_for_color = printing::HP_COLOR_COLOR; 246 if (ppdFindChoice(color_mode_option, printing::kBlack)) 247 *color_model_for_black = printing::HP_COLOR_BLACK; 248 249 ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kColorMode); 250 if (!mode_choice) { 251 mode_choice = ppdFindChoice(color_mode_option, 252 color_mode_option->defchoice); 253 } 254 if (mode_choice) { 255 *color_is_default = 256 (base::strcasecmp(mode_choice->choice, printing::kColor) == 0); 257 } 258 return true; 259 } 260 261 bool GetProcessColorModelSettings(ppd_file_t* ppd, 262 ColorModel* color_model_for_black, 263 ColorModel* color_model_for_color, 264 bool* color_is_default) { 265 // Canon printers use "ProcessColorModel" attribute in their ppds. 266 ppd_option_t* color_mode_option = ppdFindOption(ppd, kProcessColorModel); 267 if (!color_mode_option) 268 return false; 269 270 if (ppdFindChoice(color_mode_option, printing::kRGB)) 271 *color_model_for_color = printing::PROCESSCOLORMODEL_RGB; 272 else if (ppdFindChoice(color_mode_option, printing::kCMYK)) 273 *color_model_for_color = printing::PROCESSCOLORMODEL_CMYK; 274 275 if (ppdFindChoice(color_mode_option, printing::kGreyscale)) 276 *color_model_for_black = printing::PROCESSCOLORMODEL_GREYSCALE; 277 278 ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kProcessColorModel); 279 if (!mode_choice) { 280 mode_choice = ppdFindChoice(color_mode_option, 281 color_mode_option->defchoice); 282 } 283 284 if (mode_choice) { 285 *color_is_default = 286 (base::strcasecmp(mode_choice->choice, printing::kGreyscale) != 0); 287 } 288 return true; 289 } 290 291 bool GetColorModelSettings(ppd_file_t* ppd, 292 ColorModel* cm_black, 293 ColorModel* cm_color, 294 bool* is_color) { 295 bool is_color_device = false; 296 ppd_attr_t* attr = ppdFindAttr(ppd, kColorDevice, NULL); 297 if (attr && attr->value) 298 is_color_device = ppd->color_device; 299 300 *is_color = is_color_device; 301 return (is_color_device && 302 GetBasicColorModelSettings(ppd, cm_black, cm_color, is_color)) || 303 GetPrintOutModeColorSettings(ppd, cm_black, cm_color, is_color) || 304 GetColorModeSettings(ppd, cm_black, cm_color, is_color) || 305 GetHPColorSettings(ppd, cm_black, cm_color, is_color) || 306 GetProcessColorModelSettings(ppd, cm_black, cm_color, is_color); 307 } 308 309 // Default port for IPP print servers. 310 const int kDefaultIPPServerPort = 631; 311 312 } // namespace 313 314 // Helper wrapper around http_t structure, with connection and cleanup 315 // functionality. 316 HttpConnectionCUPS::HttpConnectionCUPS(const GURL& print_server_url, 317 http_encryption_t encryption) 318 : http_(NULL) { 319 // If we have an empty url, use default print server. 320 if (print_server_url.is_empty()) 321 return; 322 323 int port = print_server_url.IntPort(); 324 if (port == url::PORT_UNSPECIFIED) 325 port = kDefaultIPPServerPort; 326 327 http_ = httpConnectEncrypt(print_server_url.host().c_str(), port, encryption); 328 if (http_ == NULL) { 329 LOG(ERROR) << "CP_CUPS: Failed connecting to print server: " 330 << print_server_url; 331 } 332 } 333 334 HttpConnectionCUPS::~HttpConnectionCUPS() { 335 if (http_ != NULL) 336 httpClose(http_); 337 } 338 339 void HttpConnectionCUPS::SetBlocking(bool blocking) { 340 httpBlocking(http_, blocking ? 1 : 0); 341 } 342 343 http_t* HttpConnectionCUPS::http() { 344 return http_; 345 } 346 347 bool ParsePpdCapabilities( 348 const std::string& printer_name, 349 const std::string& printer_capabilities, 350 PrinterSemanticCapsAndDefaults* printer_info) { 351 base::FilePath ppd_file_path; 352 if (!base::CreateTemporaryFile(&ppd_file_path)) 353 return false; 354 355 int data_size = printer_capabilities.length(); 356 if (data_size != base::WriteFile( 357 ppd_file_path, 358 printer_capabilities.data(), 359 data_size)) { 360 base::DeleteFile(ppd_file_path, false); 361 return false; 362 } 363 364 ppd_file_t* ppd = ppdOpenFile(ppd_file_path.value().c_str()); 365 if (!ppd) { 366 int line = 0; 367 ppd_status_t ppd_status = ppdLastError(&line); 368 LOG(ERROR) << "Failed to open PDD file: error " << ppd_status << " at line " 369 << line << ", " << ppdErrorString(ppd_status); 370 return false; 371 } 372 373 printing::PrinterSemanticCapsAndDefaults caps; 374 #if !defined(OS_MACOSX) 375 MarkLpOptions(printer_name, &ppd); 376 #endif 377 caps.collate_capable = true; 378 caps.collate_default = true; 379 caps.copies_capable = true; 380 381 ppd_choice_t* duplex_choice = ppdFindMarkedChoice(ppd, kDuplex); 382 if (!duplex_choice) { 383 ppd_option_t* option = ppdFindOption(ppd, kDuplex); 384 if (option) 385 duplex_choice = ppdFindChoice(option, option->defchoice); 386 } 387 388 if (duplex_choice) { 389 caps.duplex_capable = true; 390 if (base::strcasecmp(duplex_choice->choice, kDuplexNone) != 0) 391 caps.duplex_default = printing::LONG_EDGE; 392 else 393 caps.duplex_default = printing::SIMPLEX; 394 } 395 396 bool is_color = false; 397 ColorModel cm_color = UNKNOWN_COLOR_MODEL, cm_black = UNKNOWN_COLOR_MODEL; 398 if (!GetColorModelSettings(ppd, &cm_black, &cm_color, &is_color)) { 399 VLOG(1) << "Unknown printer color model"; 400 } 401 402 caps.color_changeable = ((cm_color != UNKNOWN_COLOR_MODEL) && 403 (cm_black != UNKNOWN_COLOR_MODEL) && 404 (cm_color != cm_black)); 405 caps.color_default = is_color; 406 caps.color_model = cm_color; 407 caps.bw_model = cm_black; 408 409 if (ppd->num_sizes > 0 && ppd->sizes) { 410 VLOG(1) << "Paper list size - " << ppd->num_sizes; 411 ppd_option_t* paper_option = ppdFindOption(ppd, kPageSize); 412 for (int i = 0; i < ppd->num_sizes; ++i) { 413 gfx::Size paper_size_microns( 414 static_cast<int>(ppd->sizes[i].width * kMicronsPerPoint + 0.5), 415 static_cast<int>(ppd->sizes[i].length * kMicronsPerPoint + 0.5)); 416 if (paper_size_microns.width() > 0 && paper_size_microns.height() > 0) { 417 PrinterSemanticCapsAndDefaults::Paper paper; 418 paper.size_um = paper_size_microns; 419 paper.vendor_id = ppd->sizes[i].name; 420 if (paper_option) { 421 ppd_choice_t* paper_choice = 422 ppdFindChoice(paper_option, ppd->sizes[i].name); 423 // Human readable paper name should be UTF-8 encoded, but some PPDs 424 // do not follow this standard. 425 if (paper_choice && base::IsStringUTF8(paper_choice->text)) { 426 paper.display_name = paper_choice->text; 427 } 428 } 429 caps.papers.push_back(paper); 430 if (i == 0 || ppd->sizes[i].marked) { 431 caps.default_paper = paper; 432 } 433 } 434 } 435 } 436 437 ppdClose(ppd); 438 base::DeleteFile(ppd_file_path, false); 439 440 *printer_info = caps; 441 return true; 442 } 443 444 } // namespace printing 445