blob: e14fb2f391b9229c59365e1f604ae84583406e46 [file] [log] [blame]
// Copyright 2020 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 "chromeos/printing/ppd_metadata_parser.h"
#include <string>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/json/json_reader.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/values.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace chromeos {
namespace {
// Attempts to
// 1. parse |input| as a Value having Type::DICTIONARY and
// 2. return Value of |key| having a given |target_type| from the same.
//
// Additionally,
// * this function never returns empty Value objects and
// * |target_type| must appear in the switch statement below.
absl::optional<base::Value> ParseJsonAndUnnestKey(
base::StringPiece input,
base::StringPiece key,
base::Value::Type target_type) {
absl::optional<base::Value> parsed = base::JSONReader::Read(input);
if (!parsed || !parsed->is_dict()) {
return absl::nullopt;
}
absl::optional<base::Value> unnested = parsed->ExtractKey(key);
if (!unnested || unnested->type() != target_type) {
return absl::nullopt;
}
bool unnested_is_empty = true;
switch (target_type) {
case base::Value::Type::LIST:
unnested_is_empty = unnested->GetListDeprecated().empty();
break;
case base::Value::Type::DICTIONARY:
unnested_is_empty = unnested->DictEmpty();
break;
default:
NOTREACHED();
break;
}
if (unnested_is_empty) {
return absl::nullopt;
}
return unnested;
}
// Returns a Restrictions struct from a dictionary |value|.
Restrictions ParseRestrictionsFromValue(const base::Value& value) {
Restrictions restrictions;
auto min_as_double = value.FindDoubleKey("minMilestone");
auto max_as_double = value.FindDoubleKey("maxMilestone");
if (min_as_double.has_value()) {
base::Version min_milestone = base::Version(
base::NumberToString(static_cast<int>(min_as_double.value())));
if (min_milestone.IsValid()) {
restrictions.min_milestone = min_milestone;
}
}
if (max_as_double.has_value()) {
base::Version max_milestone = base::Version(
base::NumberToString(static_cast<int>(max_as_double.value())));
if (max_milestone.IsValid()) {
restrictions.max_milestone = max_milestone;
}
}
return restrictions;
}
// Returns a ParsedPrinter from a leaf |value| from Printers metadata.
absl::optional<ParsedPrinter> ParsePrinterFromValue(const base::Value& value) {
const std::string* const effective_make_and_model =
value.FindStringKey("emm");
const std::string* const name = value.FindStringKey("name");
if (!effective_make_and_model || effective_make_and_model->empty() || !name ||
name->empty()) {
return absl::nullopt;
}
ParsedPrinter printer;
printer.effective_make_and_model = *effective_make_and_model;
printer.user_visible_printer_name = *name;
const base::Value* const restrictions_value =
value.FindDictKey("restriction");
if (restrictions_value) {
printer.restrictions = ParseRestrictionsFromValue(*restrictions_value);
}
return printer;
}
// Returns a ParsedIndexLeaf from |value|.
absl::optional<ParsedIndexLeaf> ParsedIndexLeafFrom(const base::Value& value) {
if (!value.is_dict()) {
return absl::nullopt;
}
ParsedIndexLeaf leaf;
const std::string* const ppd_basename = value.FindStringKey("name");
if (!ppd_basename) {
return absl::nullopt;
}
leaf.ppd_basename = *ppd_basename;
const base::Value* const restrictions_value =
value.FindDictKey("restriction");
if (restrictions_value) {
leaf.restrictions = ParseRestrictionsFromValue(*restrictions_value);
}
const std::string* const ppd_license = value.FindStringKey("license");
if (ppd_license && !ppd_license->empty()) {
leaf.license = *ppd_license;
}
return leaf;
}
// Returns a ParsedIndexValues from a |value| extracted from a forward
// index.
absl::optional<ParsedIndexValues> UnnestPpdMetadata(const base::Value& value) {
if (!value.is_dict()) {
return absl::nullopt;
}
const base::Value* const ppd_metadata_list = value.FindListKey("ppdMetadata");
if (!ppd_metadata_list ||
ppd_metadata_list->GetListDeprecated().size() == 0) {
return absl::nullopt;
}
ParsedIndexValues parsed_index_values;
for (const base::Value& v : ppd_metadata_list->GetListDeprecated()) {
absl::optional<ParsedIndexLeaf> parsed_index_leaf = ParsedIndexLeafFrom(v);
if (parsed_index_leaf.has_value()) {
parsed_index_values.values.push_back(parsed_index_leaf.value());
}
}
if (parsed_index_values.values.empty()) {
return absl::nullopt;
}
return parsed_index_values;
}
} // namespace
Restrictions::Restrictions() = default;
Restrictions::~Restrictions() = default;
Restrictions::Restrictions(const Restrictions&) = default;
Restrictions& Restrictions::operator=(const Restrictions&) = default;
ParsedPrinter::ParsedPrinter() = default;
ParsedPrinter::~ParsedPrinter() = default;
ParsedPrinter::ParsedPrinter(const ParsedPrinter&) = default;
ParsedPrinter& ParsedPrinter::operator=(const ParsedPrinter&) = default;
ParsedIndexLeaf::ParsedIndexLeaf() = default;
ParsedIndexLeaf::~ParsedIndexLeaf() = default;
ParsedIndexLeaf::ParsedIndexLeaf(const ParsedIndexLeaf&) = default;
ParsedIndexLeaf& ParsedIndexLeaf::operator=(const ParsedIndexLeaf&) = default;
ParsedIndexValues::ParsedIndexValues() = default;
ParsedIndexValues::~ParsedIndexValues() = default;
ParsedIndexValues::ParsedIndexValues(const ParsedIndexValues&) = default;
ParsedIndexValues& ParsedIndexValues::operator=(const ParsedIndexValues&) =
default;
absl::optional<std::vector<std::string>> ParseLocales(
base::StringPiece locales_json) {
const auto as_value =
ParseJsonAndUnnestKey(locales_json, "locales", base::Value::Type::LIST);
if (!as_value.has_value()) {
return absl::nullopt;
}
std::vector<std::string> locales;
for (const auto& iter : as_value.value().GetListDeprecated()) {
if (!iter.is_string())
continue;
locales.push_back(iter.GetString());
}
if (locales.empty()) {
return absl::nullopt;
}
return locales;
}
absl::optional<ParsedManufacturers> ParseManufacturers(
base::StringPiece manufacturers_json) {
const auto as_value = ParseJsonAndUnnestKey(manufacturers_json, "filesMap",
base::Value::Type::DICTIONARY);
if (!as_value.has_value()) {
return absl::nullopt;
}
ParsedManufacturers manufacturers;
for (const auto iter : as_value.value().DictItems()) {
if (!iter.second.is_string())
continue;
manufacturers[iter.first] = iter.second.GetString();
}
return manufacturers.empty() ? absl::nullopt
: absl::make_optional(manufacturers);
}
absl::optional<ParsedIndex> ParseForwardIndex(
base::StringPiece forward_index_json) {
// Firstly, we unnest the dictionary keyed by "ppdIndex."
absl::optional<base::Value> ppd_index = ParseJsonAndUnnestKey(
forward_index_json, "ppdIndex", base::Value::Type::DICTIONARY);
if (!ppd_index.has_value()) {
return absl::nullopt;
}
ParsedIndex parsed_index;
// Secondly, we iterate on the key-value pairs of the ppdIndex.
// This yields a list of leaf values (dictionaries).
for (const auto kv : ppd_index->DictItems()) {
absl::optional<ParsedIndexValues> values = UnnestPpdMetadata(kv.second);
if (values.has_value()) {
parsed_index.insert_or_assign(kv.first, values.value());
}
}
if (parsed_index.empty()) {
return absl::nullopt;
}
return parsed_index;
}
absl::optional<ParsedUsbIndex> ParseUsbIndex(base::StringPiece usb_index_json) {
absl::optional<base::Value> usb_index = ParseJsonAndUnnestKey(
usb_index_json, "usbIndex", base::Value::Type::DICTIONARY);
if (!usb_index.has_value()) {
return absl::nullopt;
}
ParsedUsbIndex parsed_usb_index;
for (const auto kv : usb_index->DictItems()) {
int product_id;
if (!base::StringToInt(kv.first, &product_id)) {
continue;
}
const std::string* effective_make_and_model =
kv.second.FindStringKey("effectiveMakeAndModel");
if (!effective_make_and_model || effective_make_and_model->empty()) {
continue;
}
parsed_usb_index.insert_or_assign(product_id, *effective_make_and_model);
}
if (parsed_usb_index.empty()) {
return absl::nullopt;
}
return parsed_usb_index;
}
absl::optional<ParsedUsbVendorIdMap> ParseUsbVendorIdMap(
base::StringPiece usb_vendor_id_map_json) {
absl::optional<base::Value> as_value = ParseJsonAndUnnestKey(
usb_vendor_id_map_json, "entries", base::Value::Type::LIST);
if (!as_value.has_value()) {
return absl::nullopt;
}
ParsedUsbVendorIdMap usb_vendor_ids;
for (const auto& usb_vendor_description : as_value->GetListDeprecated()) {
if (!usb_vendor_description.is_dict()) {
continue;
}
absl::optional<int> vendor_id =
usb_vendor_description.FindIntKey("vendorId");
const std::string* const vendor_name =
usb_vendor_description.FindStringKey("vendorName");
if (!vendor_id.has_value() || !vendor_name || vendor_name->empty()) {
continue;
}
usb_vendor_ids.insert_or_assign(vendor_id.value(), *vendor_name);
}
if (usb_vendor_ids.empty()) {
return absl::nullopt;
}
return usb_vendor_ids;
}
absl::optional<ParsedPrinters> ParsePrinters(base::StringPiece printers_json) {
const auto as_value =
ParseJsonAndUnnestKey(printers_json, "printers", base::Value::Type::LIST);
if (!as_value.has_value()) {
return absl::nullopt;
}
ParsedPrinters printers;
for (const auto& printer_value : as_value->GetListDeprecated()) {
if (!printer_value.is_dict()) {
continue;
}
absl::optional<ParsedPrinter> printer =
ParsePrinterFromValue(printer_value);
if (!printer.has_value()) {
continue;
}
printers.push_back(printer.value());
}
if (printers.empty()) {
return absl::nullopt;
}
return printers;
}
absl::optional<ParsedReverseIndex> ParseReverseIndex(
base::StringPiece reverse_index_json) {
const absl::optional<base::Value> makes_and_models = ParseJsonAndUnnestKey(
reverse_index_json, "reverseIndex", base::Value::Type::DICTIONARY);
if (!makes_and_models.has_value()) {
return absl::nullopt;
}
ParsedReverseIndex parsed;
for (const auto kv : makes_and_models->DictItems()) {
if (!kv.second.is_dict()) {
continue;
}
const std::string* manufacturer = kv.second.FindStringKey("manufacturer");
const std::string* model = kv.second.FindStringKey("model");
if (manufacturer && model && !manufacturer->empty() && !model->empty()) {
parsed.insert_or_assign(kv.first,
ReverseIndexLeaf{*manufacturer, *model});
}
}
if (parsed.empty()) {
return absl::nullopt;
}
return parsed;
}
} // namespace chromeos