chromium/chrome/services/cups_proxy/ipp_attribute_validator.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/services/cups_proxy/ipp_attribute_validator.h"

#include <cups/ipp.h>

#include <map>
#include <set>
#include <string>
#include <string_view>

#include "base/containers/fixed_flat_map.h"
#include "base/containers/fixed_flat_set.h"
#include "chrome/services/ipp_parser/public/mojom/ipp_parser.mojom.h"

namespace cups_proxy {

namespace {

using ValueType = ipp_parser::mojom::IppAttributeValue::Tag;

// Represents the type of a single attribute.
struct AttributeDefinition {
  bool multivalued;

  // Internal serialization type.
  ValueType type;
};

}  // namespace

ValidateAttributeResult ValidateAttribute(ipp_op_t ipp_oper_id,
                                          const std::string& name,
                                          ValueType type,
                                          size_t values_count) {
  // Definitions of attributes used in IPP requests.
  static constexpr auto kAttributeDefinitions =
      base::MakeFixedFlatMap<std::string_view, AttributeDefinition>(
          {{"attributes-charset", {false, ValueType::kStrings}},
           {"attributes-natural-language", {false, ValueType::kStrings}},
           {"auth-info", {true, ValueType::kStrings}},
           {"auth-info-required", {true, ValueType::kStrings}},
           {"charset-configured", {false, ValueType::kStrings}},
           {"compression", {false, ValueType::kStrings}},
           {"copies", {false, ValueType::kInts}},
           {"document-format", {false, ValueType::kStrings}},
           {"document-name", {false, ValueType::kStrings}},
           {"finishings", {true, ValueType::kInts}},
           {"ipp-attribute-fidelity", {false, ValueType::kBools}},
           {"job-cancel-after", {false, ValueType::kInts}},
           {"job-hold-until", {false, ValueType::kStrings}},
           {"job-id", {false, ValueType::kInts}},
           {"job-k-limit", {false, ValueType::kInts}},
           {"job-media-progress", {false, ValueType::kInts}},
           {"job-name", {false, ValueType::kStrings}},
           {"job-originating-host-name", {false, ValueType::kStrings}},
           {"job-originating-user-name", {false, ValueType::kStrings}},
           {"job-page-limit", {false, ValueType::kInts}},
           {"job-password", {false, ValueType::kOctets}},
           {"job-printer-state-message", {false, ValueType::kStrings}},
           {"job-printer-state-reasons", {true, ValueType::kStrings}},
           {"job-printer-up-time", {false, ValueType::kInts}},
           {"job-printer-uri", {false, ValueType::kStrings}},
           {"job-priority", {false, ValueType::kInts}},
           {"job-quota-period", {false, ValueType::kInts}},
           {"job-state", {false, ValueType::kInts}},
           {"job-state-reasons", {true, ValueType::kStrings}},
           {"job-uri", {false, ValueType::kStrings}},
           {"last-document", {false, ValueType::kBools}},
           {"limit", {false, ValueType::kInts}},
           {"marker-change-time", {false, ValueType::kInts}},
           {"marker-colors", {true, ValueType::kStrings}},
           {"marker-high-levels", {true, ValueType::kInts}},
           {"marker-levels", {true, ValueType::kInts}},
           {"marker-low-levels", {true, ValueType::kInts}},
           {"marker-message", {false, ValueType::kStrings}},
           {"marker-names", {true, ValueType::kStrings}},
           {"marker-types", {true, ValueType::kStrings}},
           {"media", {false, ValueType::kStrings}},
           {"media-ready", {true, ValueType::kStrings}},
           // This is not a PWG attribute but CUPS supports it for InputSlot
           // selection.
           {"media-source", {true, ValueType::kStrings}},
           {"member-names", {true, ValueType::kStrings}},
           {"member-uris", {true, ValueType::kStrings}},
           {"multiple-document-handling", {false, ValueType::kStrings}},
           {"my-jobs", {false, ValueType::kBools}},
           {"natural-language-configured", {false, ValueType::kStrings}},
           {"number-up", {false, ValueType::kInts}},
           {"orientation-requested", {false, ValueType::kInts}},
           {"output-bin", {false, ValueType::kStrings}},
           {"page-border", {false, ValueType::kStrings}},
           {"page-ranges", {true, ValueType::kInts}},
           {"pages-per-minute", {false, ValueType::kInts}},
           {"pages-per-minute-color", {false, ValueType::kInts}},
           {"print-color-mode", {false, ValueType::kStrings}},
           {"print-quality", {false, ValueType::kInts}},
           {"printer-alert", {true, ValueType::kStrings}},
           {"printer-alert-description", {true, ValueType::kStrings}},
           {"printer-device-id", {false, ValueType::kStrings}},
           {"printer-dns-sd-name", {false, ValueType::kStrings}},
           {"printer-id", {false, ValueType::kInts}},
           {"printer-info", {false, ValueType::kStrings}},
           {"printer-is-accepting-jobs", {false, ValueType::kBools}},
           {"printer-location", {false, ValueType::kStrings}},
           {"printer-make-and-model", {false, ValueType::kStrings}},
           {"printer-more-info", {false, ValueType::kStrings}},
           {"printer-name", {false, ValueType::kStrings}},
           {"printer-resolution", {false, ValueType::kResolutions}},
           {"printer-state", {false, ValueType::kInts}},
           {"printer-state-reasons", {true, ValueType::kStrings}},
           {"printer-type", {false, ValueType::kInts}},
           {"printer-type-mask", {false, ValueType::kInts}},
           {"printer-up-time", {false, ValueType::kInts}},
           {"printer-uri", {false, ValueType::kStrings}},
           {"queued-job-count", {false, ValueType::kInts}},
           {"requested-attributes", {true, ValueType::kStrings}},
           {"requesting-user-name", {false, ValueType::kStrings}},
           {"sides", {false, ValueType::kStrings}},
           {"status-message", {false, ValueType::kStrings}},
           {"time-at-completed", {false, ValueType::kInts}},
           {"time-at-creation", {false, ValueType::kInts}},
           {"time-at-processing", {false, ValueType::kInts}},
           {"which-jobs", {false, ValueType::kStrings}}});

  // Allowed IPP Operations.
  static constexpr auto kOperationIds = base::MakeFixedFlatSet<ipp_op_t>({
      IPP_OP_CANCEL_JOB,
      IPP_OP_CREATE_JOB,
      IPP_OP_CUPS_GET_DEFAULT,
      IPP_OP_CUPS_GET_PPD,
      IPP_OP_CUPS_GET_PRINTERS,
      IPP_OP_GET_JOBS,
      IPP_OP_GET_JOB_ATTRIBUTES,
      IPP_OP_GET_PRINTER_ATTRIBUTES,
      IPP_OP_HOLD_JOB,
      IPP_OP_PAUSE_PRINTER,
      IPP_OP_PRINT_JOB,
      IPP_OP_PRINT_URI,
      IPP_OP_RELEASE_JOB,
      IPP_OP_RESUME_PRINTER,
      IPP_OP_SEND_DOCUMENT,
      IPP_OP_SEND_URI,
      IPP_OP_VALIDATE_JOB,
  });

  // Ensure |ipp_oper_id| is an allowed operation.
  if (!kOperationIds.contains(ipp_oper_id)) {
    return ValidateAttributeResult::kFatalError;
  }

  // Ensure |name| refers to an allowed attribute.
  auto attr_def = kAttributeDefinitions.find(name);
  if (attr_def == kAttributeDefinitions.end()) {
    // TODO(crbug.com/945409): Tell caller to drop this attribute.
    return ValidateAttributeResult::kUnknownAttribute;
  }

  // Ensure every attribute has some value.
  if (values_count == 0) {
    return ValidateAttributeResult::kFatalError;
  }

  // Ensure single-valued attributes are single-valued.
  if (!attr_def->second.multivalued && values_count > 1) {
    return ValidateAttributeResult::kFatalError;
  }

  // Ensure every attribute was serialized to the correct type.
  if (attr_def->second.type != type) {
    return ValidateAttributeResult::kFatalError;
  }
  return ValidateAttributeResult::kSuccess;
}

}  // namespace cups_proxy