910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// 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 "base/logging.h"
#include "base/time/time.h"
#include "build/build_config.h"

#if BUILDFLAG(IS_LINUX)
#include <cups/ppd.h>
#include <stddef.h>
#include <stdio.h>
#include <unistd.h>

#include <optional>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>

#include "base/compiler_specific.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "printing/backend/cups_deleters.h"
#include "printing/backend/print_backend.h"
#include "printing/backend/print_backend_consts.h"
#include "printing/mojom/print.mojom.h"
#include "printing/print_job_constants_cups.h"
#include "printing/printing_utils.h"
#include "printing/units.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "url/gurl.h"
#endif  // BUILDFLAG(IS_LINUX)

#if BUILDFLAG(IS_LINUX)
#include "printing/backend/cups_weak_functions.h"
#endif

#if BUILDFLAG(IS_LINUX)
using base::EqualsCaseInsensitiveASCII;
#endif  // BUILDFLAG(IS_LINUX)

namespace printing {

// This section contains helper code for PPD parsing for semantic capabilities.
namespace {

// Timeout for establishing a CUPS connection.  It is expected that cupsd is
// able to start and respond on all systems within this duration.
constexpr base::TimeDelta kCupsTimeout = base::Seconds(5);

#if BUILDFLAG(IS_LINUX)
// CUPS default max copies value (parsed from kCupsMaxCopies PPD attribute).
constexpr int32_t kDefaultMaxCopies = 9999;
constexpr char kCupsMaxCopies[] = "cupsMaxCopies";

constexpr char kColorDevice[] = "ColorDevice";

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";

int32_t GetCopiesMax(ppd_file_t* ppd) {
  ppd_attr_t* attr = ppdFindAttr(ppd, kCupsMaxCopies, nullptr);
  if (!attr || !attr->value) {
    return kDefaultMaxCopies;
  }

  int32_t ret;
  return base::StringToInt(attr->value, &ret) ? ret : kDefaultMaxCopies;
}

std::pair<std::vector<mojom::DuplexMode>, mojom::DuplexMode> GetDuplexSettings(
    ppd_file_t* ppd) {
  std::vector<mojom::DuplexMode> duplex_modes;
  mojom::DuplexMode duplex_default = mojom::DuplexMode::kUnknownDuplexMode;

  ppd_choice_t* duplex_choice = ppdFindMarkedChoice(ppd, kDuplex);
  ppd_option_t* option = ppdFindOption(ppd, kDuplex);
  if (!option)
    option = ppdFindOption(ppd, kBrotherDuplex);

  if (!option)
    return std::make_pair(std::move(duplex_modes), duplex_default);

  if (!duplex_choice)
    duplex_choice = ppdFindChoice(option, option->defchoice);

  if (ppdFindChoice(option, kDuplexNone))
    duplex_modes.push_back(mojom::DuplexMode::kSimplex);

  if (ppdFindChoice(option, kDuplexNoTumble))
    duplex_modes.push_back(mojom::DuplexMode::kLongEdge);

  if (ppdFindChoice(option, kDuplexTumble))
    duplex_modes.push_back(mojom::DuplexMode::kShortEdge);

  if (!duplex_choice)
    return std::make_pair(std::move(duplex_modes), duplex_default);

  const char* choice = duplex_choice->choice;
  if (EqualsCaseInsensitiveASCII(choice, kDuplexNone)) {
    duplex_default = mojom::DuplexMode::kSimplex;
  } else if (EqualsCaseInsensitiveASCII(choice, kDuplexTumble)) {
    duplex_default = mojom::DuplexMode::kShortEdge;
  } else {
    duplex_default = mojom::DuplexMode::kLongEdge;
  }
  return std::make_pair(std::move(duplex_modes), duplex_default);
}

std::optional<gfx::Size> ParseResolutionString(const char* input) {
  int len = strlen(input);
  if (len == 0) {
    VLOG(1) << "Bad PPD resolution choice: null string";
    return std::nullopt;
  }

  int n = 0;  // number of chars successfully parsed by sscanf()
  int dpi_x;
  int dpi_y;
  UNSAFE_TODO(sscanf(input, "%ddpi%n", &dpi_x, &n));
  if (n == len) {
    dpi_y = dpi_x;
  } else {
    UNSAFE_TODO(sscanf(input, "%dx%ddpi%n", &dpi_x, &dpi_y, &n));
    if (n != len) {
      VLOG(1) << "Bad PPD resolution choice: " << input;
      return std::nullopt;
    }
  }
  if (dpi_x <= 0 || dpi_y <= 0) {
    VLOG(1) << "Invalid PPD resolution dimensions: " << dpi_x << " " << dpi_y;
    return std::nullopt;
  }

  return gfx::Size(dpi_x, dpi_y);
}

std::pair<std::vector<gfx::Size>, gfx::Size> GetResolutionSettings(
    ppd_file_t* ppd) {
  static constexpr const char* kResolutions[] = {
      "Resolution",     "JCLResolution", "SetResolution", "CNRes_PGP",
      "HPPrintQuality", "LXResolution",  "BRResolution"};
  ppd_option_t* res;
  for (const char* res_name : kResolutions) {
    res = ppdFindOption(ppd, res_name);
    if (res)
      break;
  }

  // Some printers, such as Generic-CUPS-BRF-Printer, do not specify a
  // resolution in their ppd file. Provide a default DPI if no valid DPI is
  // found.
  constexpr gfx::Size kDefaultMissingDpi(kDefaultPdfDpi, kDefaultPdfDpi);

  std::vector<gfx::Size> dpis;
  gfx::Size default_dpi;
  if (res) {
    // SAFETY: Required from CUPS.
    auto choices = UNSAFE_BUFFERS(base::span<const ppd_choice_t>(
        res->choices, static_cast<size_t>(res->num_choices)));
    for (const auto& choice : choices) {
      const char* choice_str = choice.choice;
      CHECK(choice_str);
      std::optional<gfx::Size> parsed_size = ParseResolutionString(choice_str);
      if (!parsed_size.has_value()) {
        continue;
      }

      dpis.push_back(parsed_size.value());
      if (!UNSAFE_TODO(strcmp(choice_str, res->defchoice))) {
        default_dpi = dpis.back();
      }
    }
  } else {
    // If there is no resolution option, then check for a standalone
    // DefaultResolution.
    ppd_attr_t* attr = ppdFindAttr(ppd, "DefaultResolution", nullptr);
    if (attr) {
      CHECK(attr->value);
      std::optional<gfx::Size> parsed_size = ParseResolutionString(attr->value);
      if (parsed_size.has_value()) {
        dpis.push_back(parsed_size.value());
        default_dpi = parsed_size.value();
      }
    }
  }

  if (dpis.empty()) {
    dpis.push_back(kDefaultMissingDpi);
    default_dpi = kDefaultMissingDpi;
  }
  return std::make_pair(std::move(dpis), default_dpi);
}

bool GetBasicColorModelSettings(ppd_file_t* ppd,
                                mojom::ColorModel* color_model_for_black,
                                mojom::ColorModel* color_model_for_color,
                                bool* color_is_default) {
  ppd_option_t* color_model = ppdFindOption(ppd, kCUPSColorModel);
  if (!color_model)
    return false;

  if (ppdFindChoice(color_model, kBlack))
    *color_model_for_black = mojom::ColorModel::kBlack;
  else if (ppdFindChoice(color_model, kGray))
    *color_model_for_black = mojom::ColorModel::kGray;
  else if (ppdFindChoice(color_model, kGrayscale))
    *color_model_for_black = mojom::ColorModel::kGrayscale;

  if (ppdFindChoice(color_model, kColor))
    *color_model_for_color = mojom::ColorModel::kColor;
  else if (ppdFindChoice(color_model, kCMYK))
    *color_model_for_color = mojom::ColorModel::kCMYK;
  else if (ppdFindChoice(color_model, kRGB))
    *color_model_for_color = mojom::ColorModel::kRGB;
  else if (ppdFindChoice(color_model, kRGBA))
    *color_model_for_color = mojom::ColorModel::kRGBA;
  else if (ppdFindChoice(color_model, kRGB16))
    *color_model_for_color = mojom::ColorModel::kRGB16;
  else if (ppdFindChoice(color_model, kCMY))
    *color_model_for_color = mojom::ColorModel::kCMY;
  else if (ppdFindChoice(color_model, kKCMY))
    *color_model_for_color = mojom::ColorModel::kKCMY;
  else if (ppdFindChoice(color_model, kCMY_K))
    *color_model_for_color = mojom::ColorModel::kCMYPlusK;

  ppd_choice_t* marked_choice = ppdFindMarkedChoice(ppd, kCUPSColorModel);
  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,
                                  mojom::ColorModel* color_model_for_black,
                                  mojom::ColorModel* color_model_for_color,
                                  bool* color_is_default) {
  ppd_option_t* printout_mode = ppdFindOption(ppd, kCUPSPrintoutMode);
  if (!printout_mode)
    return false;

  *color_model_for_color = mojom::ColorModel::kPrintoutModeNormal;
  *color_model_for_black = mojom::ColorModel::kPrintoutModeNormal;

  // 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 = mojom::ColorModel::kPrintoutModeNormalGray;

  // Get the default marked choice to identify the default color setting
  // value.
  ppd_choice_t* printout_mode_choice =
      ppdFindMarkedChoice(ppd, kCUPSPrintoutMode);
  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 = mojom::ColorModel::kPrintoutModeNormalGray;
      *color_is_default = false;
    }
  }
  return true;
}

bool GetColorModeSettings(ppd_file_t* ppd,
                          mojom::ColorModel* color_model_for_black,
                          mojom::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, kCUPSColorMode);
  if (!color_mode_option)
    return false;

  if (ppdFindChoice(color_mode_option, kColor) ||
      ppdFindChoice(color_mode_option, kSamsungColorTrue)) {
    *color_model_for_color = mojom::ColorModel::kColorModeColor;
  }

  if (ppdFindChoice(color_mode_option, kMonochrome) ||
      ppdFindChoice(color_mode_option, kSamsungColorFalse)) {
    *color_model_for_black = mojom::ColorModel::kColorModeMonochrome;
  }

  ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kCUPSColorMode);
  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,
                             mojom::ColorModel* color_model_for_black,
                             mojom::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, kCUPSBrotherMonoColor);
  if (!color_mode_option)
    color_mode_option = ppdFindOption(ppd, kCUPSBrotherPrintQuality);
  if (!color_mode_option)
    return false;

  if (ppdFindChoice(color_mode_option, kFullColor))
    *color_model_for_color = mojom::ColorModel::kBrotherCUPSColor;
  else if (ppdFindChoice(color_mode_option, kColor))
    *color_model_for_color = mojom::ColorModel::kBrotherBRScript3Color;

  if (ppdFindChoice(color_mode_option, kMono))
    *color_model_for_black = mojom::ColorModel::kBrotherCUPSMono;
  else if (ppdFindChoice(color_mode_option, kBlack))
    *color_model_for_black = mojom::ColorModel::kBrotherBRScript3Black;

  ppd_choice_t* marked_choice = ppdFindMarkedChoice(ppd, kCUPSColorMode);
  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,
                        mojom::ColorModel* color_model_for_black,
                        mojom::ColorModel* color_model_for_color,
                        bool* color_is_default) {
  // Some 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 = mojom::ColorModel::kHPColorColor;
  if (ppdFindChoice(color_mode_option, kBlack))
    *color_model_for_black = mojom::ColorModel::kHPColorBlack;

  ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kCUPSColorMode);
  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 GetHPColorModeSettings(ppd_file_t* ppd,
                            mojom::ColorModel* color_model_for_black,
                            mojom::ColorModel* color_model_for_color,
                            bool* color_is_default) {
  // Some HP printers use "HPColorMode/Mode" attribute in their PPDs.
  ppd_option_t* color_mode_option = ppdFindOption(ppd, kCUPSHpColorMode);
  if (!color_mode_option)
    return false;

  if (ppdFindChoice(color_mode_option, kHpColorPrint))
    *color_model_for_color = mojom::ColorModel::kHPColorColor;
  if (ppdFindChoice(color_mode_option, kHpGrayscalePrint))
    *color_model_for_black = mojom::ColorModel::kHPColorBlack;

  ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kCUPSHpColorMode);
  if (!mode_choice) {
    mode_choice =
        ppdFindChoice(color_mode_option, color_mode_option->defchoice);
  }
  if (mode_choice) {
    *color_is_default =
        EqualsCaseInsensitiveASCII(mode_choice->choice, kHpColorPrint);
  }
  return true;
}

bool GetHpPjlColorAsGrayModeSettings(ppd_file_t* ppd,
                                     mojom::ColorModel* color_model_for_black,
                                     mojom::ColorModel* color_model_for_color,
                                     bool* color_is_default) {
  // Some HP printers use "HPPJLColorAsGray" attribute in their PPDs.
  ppd_option_t* color_mode_option = ppdFindOption(ppd, kCUPSHpPjlColorAsGray);
  if (!color_mode_option) {
    return false;
  }

  if (ppdFindChoice(color_mode_option, kHpPjlColorAsGrayYes)) {
    *color_model_for_black = mojom::ColorModel::kHpPjlColorAsGrayYes;
  }

  if (ppdFindChoice(color_mode_option, kHpPjlColorAsGrayNo)) {
    *color_model_for_color = mojom::ColorModel::kHpPjlColorAsGrayNo;
  }

  ppd_choice_t* marked_choice = ppdFindMarkedChoice(ppd, kCUPSHpPjlColorAsGray);
  if (!marked_choice) {
    marked_choice =
        ppdFindChoice(color_mode_option, color_mode_option->defchoice);
  }
  if (marked_choice) {
    *color_is_default =
        EqualsCaseInsensitiveASCII(marked_choice->choice, kHpPjlColorAsGrayNo);
  }
  return true;
}

bool GetCanonCNColorModeSettings(ppd_file_t* ppd,
                                 mojom::ColorModel* color_model_for_black,
                                 mojom::ColorModel* color_model_for_color,
                                 bool* color_is_default) {
  // Some Canon printers use "CNColorMode" attribute in their PPDs.
  ppd_option_t* color_mode_option = ppdFindOption(ppd, kCUPSCanonCNColorMode);
  if (!color_mode_option) {
    return false;
  }

  if (ppdFindChoice(color_mode_option, kColor)) {
    *color_model_for_color = mojom::ColorModel::kCanonCNColorModeColor;
  }

  if (ppdFindChoice(color_mode_option, kMono)) {
    *color_model_for_black = mojom::ColorModel::kCanonCNColorModeMono;
  }

  ppd_choice_t* marked_choice = ppdFindMarkedChoice(ppd, kCUPSCanonCNColorMode);
  if (!marked_choice) {
    marked_choice =
        ppdFindChoice(color_mode_option, color_mode_option->defchoice);
  }
  if (marked_choice) {
    *color_is_default =
        EqualsCaseInsensitiveASCII(marked_choice->choice, kColor);
  }
  return true;
}

bool GetCanonCNIJGrayscaleSettings(ppd_file_t* ppd,
                                   mojom::ColorModel* color_model_for_black,
                                   mojom::ColorModel* color_model_for_color,
                                   bool* color_is_default) {
  // Some Canon printers use "CNIJGrayScale" attribute in their PPDs.
  ppd_option_t* color_mode_option = ppdFindOption(ppd, kCUPSCanonCNIJGrayScale);
  if (!color_mode_option) {
    return false;
  }

  if (ppdFindChoice(color_mode_option, kZero)) {
    *color_model_for_color = mojom::ColorModel::kCanonCNIJGrayScaleZero;
  }
  if (ppdFindChoice(color_mode_option, kOne)) {
    *color_model_for_black = mojom::ColorModel::kCanonCNIJGrayScaleOne;
  }

  ppd_choice_t* marked_choice =
      ppdFindMarkedChoice(ppd, kCUPSCanonCNIJGrayScale);
  if (marked_choice) {
    *color_is_default =
        !EqualsCaseInsensitiveASCII(marked_choice->choice, kOne);
  }
  return true;
}

bool GetEpsonInkSettings(ppd_file_t* ppd,
                         mojom::ColorModel* color_model_for_black,
                         mojom::ColorModel* color_model_for_color,
                         bool* color_is_default) {
  // Epson printers use "Ink" attribute in their PPDs.
  ppd_option_t* color_mode_option = ppdFindOption(ppd, kCUPSEpsonInk);
  if (!color_mode_option)
    return false;

  if (ppdFindChoice(color_mode_option, kEpsonColor))
    *color_model_for_color = mojom::ColorModel::kEpsonInkColor;
  if (ppdFindChoice(color_mode_option, kEpsonMono))
    *color_model_for_black = mojom::ColorModel::kEpsonInkMono;

  ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kCUPSEpsonInk);
  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 GetKonicaMinoltaSelectColorSettings(
    ppd_file_t* ppd,
    mojom::ColorModel* color_model_for_black,
    mojom::ColorModel* color_model_for_color,
    bool* color_is_default) {
  ppd_option_t* color_mode_option =
      ppdFindOption(ppd, kCUPSKonicaMinoltaSelectColor);
  if (!color_mode_option) {
    return false;
  }

  if (ppdFindChoice(color_mode_option, kColor)) {
    *color_model_for_color = mojom::ColorModel::kColor;
  }
  if (ppdFindChoice(color_mode_option, kGrayscale)) {
    *color_model_for_black = mojom::ColorModel::kGrayscale;
  }

  ppd_choice_t* mode_choice =
      ppdFindMarkedChoice(ppd, kCUPSKonicaMinoltaSelectColor);
  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 GetLexmarkBLWSettings(ppd_file_t* ppd,
                           mojom::ColorModel* color_model_for_black,
                           mojom::ColorModel* color_model_for_color,
                           bool* color_is_default) {
  // Lexmark printers use "BLW" attribute in their PPDs.
  ppd_option_t* color_mode_option = ppdFindOption(ppd, kCUPSLexmarkBLW);
  if (!color_mode_option) {
    return false;
  }

  if (ppdFindChoice(color_mode_option, kLexmarkBLWFalse)) {
    *color_model_for_color = mojom::ColorModel::kColor;
  }
  if (ppdFindChoice(color_mode_option, kLexmarkBLWTrue)) {
    *color_model_for_black = mojom::ColorModel::kGray;
  }

  ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kCUPSLexmarkBLW);
  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 GetOkiSettings(ppd_file_t* ppd,
                    mojom::ColorModel* color_model_for_black,
                    mojom::ColorModel* color_model_for_color,
                    bool* color_is_default) {
  // Oki printers use "OKControl" attribute in their PPDs.
  ppd_option_t* color_mode_option = ppdFindOption(ppd, kCUPSOkiControl);
  if (!color_mode_option) {
    return false;
  }

  if (ppdFindChoice(color_mode_option, kAuto)) {
    *color_model_for_color = mojom::ColorModel::kOkiOKControlColor;
  }
  if (ppdFindChoice(color_mode_option, kGray)) {
    *color_model_for_black = mojom::ColorModel::kOkiOKControlGray;
  }

  ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kCUPSOkiControl);
  if (!mode_choice) {
    mode_choice =
        ppdFindChoice(color_mode_option, color_mode_option->defchoice);
  }

  if (mode_choice) {
    *color_is_default = EqualsCaseInsensitiveASCII(mode_choice->choice, kAuto);
  }
  return true;
}

bool GetSharpARCModeSettings(ppd_file_t* ppd,
                             mojom::ColorModel* color_model_for_black,
                             mojom::ColorModel* color_model_for_color,
                             bool* color_is_default) {
  // Sharp printers use "ARCMode" attribute in their PPDs.
  ppd_option_t* color_mode_option = ppdFindOption(ppd, kCUPSSharpARCMode);
  if (!color_mode_option)
    return false;

  if (ppdFindChoice(color_mode_option, kSharpCMColor))
    *color_model_for_color = mojom::ColorModel::kSharpARCModeCMColor;
  if (ppdFindChoice(color_mode_option, kSharpCMBW))
    *color_model_for_black = mojom::ColorModel::kSharpARCModeCMBW;

  ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kCUPSSharpARCMode);
  if (!mode_choice) {
    mode_choice =
        ppdFindChoice(color_mode_option, color_mode_option->defchoice);
  }

  if (mode_choice) {
    // Many Sharp printers use "CMAuto" as the default color mode.
    *color_is_default =
        !EqualsCaseInsensitiveASCII(mode_choice->choice, kSharpCMBW);
  }
  return true;
}

bool GetXeroxColorSettings(ppd_file_t* ppd,
                           mojom::ColorModel* color_model_for_black,
                           mojom::ColorModel* color_model_for_color,
                           bool* color_is_default) {
  // Some Xerox printers use "XRXColor" attribute in their PPDs.
  ppd_option_t* color_mode_option = ppdFindOption(ppd, kCUPSXeroxXRXColor);
  if (!color_mode_option)
    return false;

  if (ppdFindChoice(color_mode_option, kXeroxAutomatic))
    *color_model_for_color = mojom::ColorModel::kXeroxXRXColorAutomatic;
  if (ppdFindChoice(color_mode_option, kXeroxBW))
    *color_model_for_black = mojom::ColorModel::kXeroxXRXColorBW;

  ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kCUPSXeroxXRXColor);
  if (!mode_choice) {
    mode_choice =
        ppdFindChoice(color_mode_option, color_mode_option->defchoice);
  }

  if (mode_choice) {
    // Many Xerox printers use "Automatic" as the default color mode.
    *color_is_default =
        !EqualsCaseInsensitiveASCII(mode_choice->choice, kXeroxBW);
  }
  return true;
}

bool GetXeroxOutputColorSettings(ppd_file_t* ppd,
                                 mojom::ColorModel* color_model_for_black,
                                 mojom::ColorModel* color_model_for_color,
                                 bool* color_is_default) {
  // Some Xerox printers use "XROutputColor" attribute in their PPDs.
  ppd_option_t* color_mode_option = ppdFindOption(ppd, kCUPSXeroxXROutputColor);
  if (!color_mode_option) {
    return false;
  }

  if (ppdFindChoice(color_mode_option, kPrintAsColor)) {
    *color_model_for_color = mojom::ColorModel::kXeroxXROutputColorPrintAsColor;
  }
  if (ppdFindChoice(color_mode_option, kPrintAsGrayscale)) {
    *color_model_for_black =
        mojom::ColorModel::kXeroxXROutputColorPrintAsGrayscale;
  }

  ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kCUPSXeroxXROutputColor);
  if (!mode_choice) {
    mode_choice =
        ppdFindChoice(color_mode_option, color_mode_option->defchoice);
  }

  if (mode_choice) {
    *color_is_default =
        EqualsCaseInsensitiveASCII(mode_choice->choice, kPrintAsColor);
  }
  return true;
}

bool GetProcessColorModelSettings(ppd_file_t* ppd,
                                  mojom::ColorModel* color_model_for_black,
                                  mojom::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, kCUPSProcessColorModel);
  if (!color_mode_option)
    return false;

  if (ppdFindChoice(color_mode_option, kRGB))
    *color_model_for_color = mojom::ColorModel::kProcessColorModelRGB;
  else if (ppdFindChoice(color_mode_option, kCMYK))
    *color_model_for_color = mojom::ColorModel::kProcessColorModelCMYK;

  if (ppdFindChoice(color_mode_option, kGreyscale))
    *color_model_for_black = mojom::ColorModel::kProcessColorModelGreyscale;

  ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kCUPSProcessColorModel);
  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,
                           mojom::ColorModel* cm_black,
                           mojom::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) ||
         GetHPColorModeSettings(ppd, cm_black, cm_color, is_color) ||
         GetHpPjlColorAsGrayModeSettings(ppd, cm_black, cm_color, is_color) ||
         GetBrotherColorSettings(ppd, cm_black, cm_color, is_color) ||
         GetCanonCNColorModeSettings(ppd, cm_black, cm_color, is_color) ||
         GetCanonCNIJGrayscaleSettings(ppd, cm_black, cm_color, is_color) ||
         GetEpsonInkSettings(ppd, cm_black, cm_color, is_color) ||
         GetKonicaMinoltaSelectColorSettings(ppd, cm_black, cm_color,
                                             is_color) ||
         GetLexmarkBLWSettings(ppd, cm_black, cm_color, is_color) ||
         GetOkiSettings(ppd, cm_black, cm_color, is_color) ||
         GetSharpARCModeSettings(ppd, cm_black, cm_color, is_color) ||
         GetXeroxColorSettings(ppd, cm_black, cm_color, is_color) ||
         GetXeroxOutputColorSettings(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;
#endif  // BUILDFLAG(IS_LINUX)

}  // namespace

#if BUILDFLAG(IS_LINUX)
// Helper wrapper around http_t structure, with connection and cleanup
// functionality.
HttpConnectionCUPS::HttpConnectionCUPS(const GURL& print_server_url,
                                       http_encryption_t encryption,
                                       bool blocking) {
  // 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_ = HttpConnect2(print_server_url.GetHost().c_str(), port,
                       /*addrlist=*/nullptr, AF_UNSPEC, encryption,
                       blocking ? 1 : 0, kCupsTimeout.InMilliseconds(),
                       /*cancel=*/nullptr);
}

HttpConnectionCUPS::~HttpConnectionCUPS() = default;

http_t* HttpConnectionCUPS::http() {
  return http_.get();
}

bool ParsePpdCapabilities(cups_dest_t* dest,
                          std::string_view locale,
                          std::string_view printer_capabilities,
                          PrinterSemanticCapsAndDefaults* printer_info) {
  // A file created while in a sandbox will be automatically deleted once all
  // handles to it have been closed.  This precludes the use of multiple
  // operations against a file path.
  //
  // Underlying CUPS libraries process the PPD using standard I/O file
  // descriptors, so `FILE` stream APIs that don't support that are not an
  // option (e.g., can't use fmemopen()).
  //
  // Previous attempts to just read & write with a single disk `FILE` stream
  // demonstrated occasional data corruption in the wild, so resort to working
  // directly with lower-level file descriptors.
  base::FilePath temp_dir;
  if (!base::GetTempDir(&temp_dir))
    return false;

  base::FilePath ppd_file_path;
  base::ScopedFD ppd_fd =
      base::CreateAndOpenFdForTemporaryFileInDir(temp_dir, &ppd_file_path);
  if (!ppd_fd.is_valid())
    return false;

  // Unlike Windows, POSIX platforms do not have the ability to mark files as
  // "delete on close". So just delete `ppd_file_path` here. The file is still
  // accessible via `ppd_fd`.
  if (!base::DeleteFile(ppd_file_path))
    return false;

  if (!base::WriteFileDescriptor(ppd_fd.get(), printer_capabilities) ||
      lseek(ppd_fd.get(), 0, SEEK_SET) == -1) {
    return false;
  }

  // We release ownership of `ppd_fd` here because ppdOpenFd() assumes ownership
  // of it in all but one case (see below).
  int unowned_ppd_fd = ppd_fd.release();
  ppd_file_t* ppd = ppdOpenFd(unowned_ppd_fd);
  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);
    if (ppd_status == PPD_FILE_OPEN_ERROR) {
      // Normally ppdOpenFd assumes ownership of the file descriptor we give it,
      // regardless of success or failure. The one exception is when it fails
      // with PPD_FILE_OPEN_ERROR. In that case ownership is retained by the
      // caller, so we must explicitly close it.
      close(unowned_ppd_fd);
    }
    return false;
  }

  ppdMarkDefaults(ppd);
  if (dest)
    cupsMarkOptions(ppd, dest->num_options, dest->options);

  PrinterSemanticCapsAndDefaults caps;
  caps.collate_capable = true;
  caps.collate_default = true;
  caps.copies_max = GetCopiesMax(ppd);

  std::tie(caps.duplex_modes, caps.duplex_default) = GetDuplexSettings(ppd);
  std::tie(caps.dpis, caps.default_dpi) = GetResolutionSettings(ppd);

  mojom::ColorModel cm_black = mojom::ColorModel::kUnknownColorModel;
  mojom::ColorModel cm_color = mojom::ColorModel::kUnknownColorModel;
  bool is_color = false;
  if (!GetColorModelSettings(ppd, &cm_black, &cm_color, &is_color)) {
    VLOG(1) << "Unknown printer color model";
  }

  caps.color_changeable =
      ((cm_color != mojom::ColorModel::kUnknownColorModel) &&
       (cm_black != mojom::ColorModel::kUnknownColorModel) &&
       (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);
    bool is_default_found = false;
    // SAFETY: Required from CUPS.
    auto sizes = UNSAFE_BUFFERS(base::span<const ppd_size_t>(
        ppd->sizes, static_cast<size_t>(ppd->num_sizes)));
    for (const auto& size : sizes) {
      const gfx::Size paper_size_um(
          ConvertUnit(size.width, kPointsPerInch, kMicronsPerInch),
          ConvertUnit(size.length, kPointsPerInch, kMicronsPerInch));
      if (!paper_size_um.IsEmpty()) {
        std::string display_name;
        if (paper_option) {
          ppd_choice_t* paper_choice = ppdFindChoice(paper_option, size.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)) {
            display_name = paper_choice->text;
          }
        }
        int printable_area_left_um =
            ConvertUnit(size.left, kPointsPerInch, kMicronsPerInch);
        int printable_area_bottom_um =
            ConvertUnit(size.bottom, kPointsPerInch, kMicronsPerInch);
        // `size.right` is the horizontal distance from the left of the paper to
        // the right of the printable area.
        int printable_area_right_um =
            ConvertUnit(size.right, kPointsPerInch, kMicronsPerInch);
        // `size.top` is the vertical distance from the bottom of the paper to
        // the top of the printable area.
        int printable_area_top_um =
            ConvertUnit(size.top, kPointsPerInch, kMicronsPerInch);

        gfx::Rect printable_area_um(
            printable_area_left_um, printable_area_bottom_um,
            /*width=*/printable_area_right_um - printable_area_left_um,
            /*height=*/printable_area_top_um - printable_area_bottom_um);

        // Default to the paper size if printable area is empty.
        // We've seen some drivers have a printable area that goes out of
        // bounds of the paper size. In those cases, set the printable area to
        // be the size. (See crbug.com/1412305.)
        const gfx::Rect size_um_rect = gfx::Rect(paper_size_um);
        if (printable_area_um.IsEmpty() ||
            !size_um_rect.Contains(printable_area_um)) {
          printable_area_um = size_um_rect;
        }

        PrinterSemanticCapsAndDefaults::Paper paper(
            display_name,
            /*vendor_id=*/size.name, paper_size_um, printable_area_um);

        caps.papers.push_back(paper);
        if (size.marked) {
          caps.default_paper = paper;
          is_default_found = true;
        }
      }
    }
    if (!is_default_found) {
      gfx::Size locale_paper_um = GetDefaultPaperSizeFromLocaleMicrons(locale);
      for (const PrinterSemanticCapsAndDefaults::Paper& paper : caps.papers) {
        // Set epsilon to 500 microns to allow tolerance of rounded paper sizes.
        // While the above utility function returns paper sizes in microns, they
        // are still rounded to the nearest millimeter (1000 microns).
        constexpr int kSizeEpsilon = 500;
        if (SizesEqualWithinEpsilon(paper.size_um(), locale_paper_um,
                                    kSizeEpsilon)) {
          caps.default_paper = paper;
          is_default_found = true;
          break;
        }
      }

      // If no default was set in the PPD or if the locale default is not within
      // the printer's capabilities, select the first on the list.
      if (!is_default_found)
        caps.default_paper = caps.papers[0];
    }
  }

  ppdClose(ppd);

  *printer_info = caps;
  return true;
}
#endif  // BUILDFLAG(IS_LINUX)

ScopedHttpPtr HttpConnect2(const char* host,
                           int port,
                           http_addrlist_t* addrlist,
                           int family,
                           http_encryption_t encryption,
                           int blocking,
                           int msec,
                           int* cancel) {
#if BUILDFLAG(IS_LINUX)
  ScopedHttpPtr http;
  if (httpConnect2) {
    http.reset(httpConnect2(host, port,
                            /*addrlist=*/nullptr, AF_UNSPEC, encryption,
                            blocking ? 1 : 0, kCupsTimeout.InMilliseconds(),
                            /*cancel=*/nullptr));
  } else {
    // Continue to use deprecated CUPS calls because because older Linux
    // distribution such as RHEL/CentOS 7 are shipped with CUPS 1.6.
    http.reset(httpConnectEncrypt(host, port, encryption));
  }

  if (!http) {
    LOG(ERROR) << "CP_CUPS: Failed connecting to print server: " << host;
    return nullptr;
  }

  if (!httpConnect2) {
    httpBlocking(http.get(), blocking ? 1 : 0);
  }

  return http;
#else
  ScopedHttpPtr http(httpConnect2(
      host, port, /*addrlist=*/nullptr, AF_UNSPEC, encryption, blocking ? 1 : 0,
      kCupsTimeout.InMilliseconds(), /*cancel=*/nullptr));
  if (!http) {
    LOG(ERROR) << "CP_CUPS: Failed connecting to print server: " << host;
  }
  return http;
#endif  // BUILDFLAG(IS_LINUX)
}

}  // namespace printing