// Copyright 2020 The Chromium Authors
// 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 <optional>
#include <string>
#include <string_view>
#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/values.h"
namespace chromeos {
namespace {
// Attempts to
// 1. parse |input| as a Value having Type::DICT 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.
std::optional<base::Value> ParseJsonAndUnnestKey(
std::string_view input,
std::string_view key,
base::Value::Type target_type) {
std::optional<base::Value> parsed = base::JSONReader::Read(input);
if (!parsed || !parsed->is_dict()) {
return std::nullopt;
}
std::optional<base::Value> unnested = parsed->GetDict().Extract(key);
if (!unnested || unnested->type() != target_type) {
return std::nullopt;
}
bool unnested_is_empty = true;
switch (target_type) {
case base::Value::Type::LIST:
unnested_is_empty = unnested->GetList().empty();
break;
case base::Value::Type::DICT:
unnested_is_empty = unnested->GetDict().empty();
break;
default:
NOTREACHED_IN_MIGRATION();
break;
}
if (unnested_is_empty) {
return std::nullopt;
}
return unnested;
}
// Returns a Restrictions struct from a dictionary `dict`.
Restrictions ParseRestrictionsFromDict(const base::Value::Dict& dict) {
Restrictions restrictions;
auto min_as_double = dict.FindDouble("minMilestone");
auto max_as_double = dict.FindDouble("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 `dict` from Printers metadata.
std::optional<ParsedPrinter> ParsePrinterFromDict(
const base::Value::Dict& dict) {
const std::string* const effective_make_and_model = dict.FindString("emm");
const std::string* const name = dict.FindString("name");
if (!effective_make_and_model || effective_make_and_model->empty() || !name ||
name->empty()) {
return std::nullopt;
}
ParsedPrinter printer;
printer.effective_make_and_model = *effective_make_and_model;
printer.user_visible_printer_name = *name;
const base::Value::Dict* const restrictions_dict =
dict.FindDict("restriction");
if (restrictions_dict) {
printer.restrictions = ParseRestrictionsFromDict(*restrictions_dict);
}
return printer;
}
// Returns a ParsedIndexLeaf from |value|.
std::optional<ParsedIndexLeaf> ParsedIndexLeafFrom(const base::Value& value) {
if (!value.is_dict()) {
return std::nullopt;
}
const base::Value::Dict& dict = value.GetDict();
ParsedIndexLeaf leaf;
const std::string* const ppd_basename = dict.FindString("name");
if (!ppd_basename) {
return std::nullopt;
}
leaf.ppd_basename = *ppd_basename;
const base::Value::Dict* const restrictions_dict =
dict.FindDict("restriction");
if (restrictions_dict) {
leaf.restrictions = ParseRestrictionsFromDict(*restrictions_dict);
}
const std::string* const ppd_license = dict.FindString("license");
if (ppd_license && !ppd_license->empty()) {
leaf.license = *ppd_license;
}
return leaf;
}
// Returns a ParsedIndexValues from a |value| extracted from a forward
// index.
std::optional<ParsedIndexValues> UnnestPpdMetadata(const base::Value& value) {
if (!value.is_dict()) {
return std::nullopt;
}
const base::Value::List* const ppd_metadata_list =
value.GetDict().FindList("ppdMetadata");
if (!ppd_metadata_list || ppd_metadata_list->empty()) {
return std::nullopt;
}
ParsedIndexValues parsed_index_values;
for (const base::Value& v : *ppd_metadata_list) {
std::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 std::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;
std::optional<std::vector<std::string>> ParseLocales(
std::string_view locales_json) {
const auto as_value =
ParseJsonAndUnnestKey(locales_json, "locales", base::Value::Type::LIST);
if (!as_value.has_value()) {
return std::nullopt;
}
std::vector<std::string> locales;
for (const auto& iter : as_value->GetList()) {
if (!iter.is_string())
continue;
locales.push_back(iter.GetString());
}
if (locales.empty()) {
return std::nullopt;
}
return locales;
}
std::optional<ParsedManufacturers> ParseManufacturers(
std::string_view manufacturers_json) {
const auto as_value = ParseJsonAndUnnestKey(manufacturers_json, "filesMap",
base::Value::Type::DICT);
if (!as_value.has_value()) {
return std::nullopt;
}
ParsedManufacturers manufacturers;
for (const auto [key, value] : as_value->GetDict()) {
if (!value.is_string()) {
continue;
}
manufacturers[key] = value.GetString();
}
return manufacturers.empty() ? std::nullopt
: std::make_optional(manufacturers);
}
std::optional<ParsedIndex> ParseForwardIndex(
std::string_view forward_index_json) {
// Firstly, we unnest the dictionary keyed by "ppdIndex."
std::optional<base::Value> ppd_index = ParseJsonAndUnnestKey(
forward_index_json, "ppdIndex", base::Value::Type::DICT);
if (!ppd_index.has_value()) {
return std::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 [key, value] : ppd_index->GetDict()) {
std::optional<ParsedIndexValues> values = UnnestPpdMetadata(value);
if (values.has_value()) {
parsed_index.insert_or_assign(key, values.value());
}
}
if (parsed_index.empty()) {
return std::nullopt;
}
return parsed_index;
}
std::optional<ParsedUsbIndex> ParseUsbIndex(std::string_view usb_index_json) {
std::optional<base::Value> usb_index = ParseJsonAndUnnestKey(
usb_index_json, "usbIndex", base::Value::Type::DICT);
if (!usb_index.has_value()) {
return std::nullopt;
}
ParsedUsbIndex parsed_usb_index;
for (const auto [key, value] : usb_index->GetDict()) {
int product_id;
if (!base::StringToInt(key, &product_id)) {
continue;
}
if (!value.is_dict()) {
continue;
}
const std::string* effective_make_and_model =
value.GetDict().FindString("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 std::nullopt;
}
return parsed_usb_index;
}
std::optional<ParsedUsbVendorIdMap> ParseUsbVendorIdMap(
std::string_view usb_vendor_id_map_json) {
std::optional<base::Value> as_value = ParseJsonAndUnnestKey(
usb_vendor_id_map_json, "entries", base::Value::Type::LIST);
if (!as_value.has_value()) {
return std::nullopt;
}
ParsedUsbVendorIdMap usb_vendor_ids;
for (const auto& usb_vendor_description : as_value->GetList()) {
if (!usb_vendor_description.is_dict()) {
continue;
}
std::optional<int> vendor_id =
usb_vendor_description.GetDict().FindInt("vendorId");
const std::string* const vendor_name =
usb_vendor_description.GetDict().FindString("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 std::nullopt;
}
return usb_vendor_ids;
}
std::optional<ParsedPrinters> ParsePrinters(std::string_view printers_json) {
const auto as_value =
ParseJsonAndUnnestKey(printers_json, "printers", base::Value::Type::LIST);
if (!as_value.has_value()) {
return std::nullopt;
}
ParsedPrinters printers;
for (const auto& printer_value : as_value->GetList()) {
const base::Value::Dict* printer_dict = printer_value.GetIfDict();
if (!printer_dict) {
continue;
}
std::optional<ParsedPrinter> printer = ParsePrinterFromDict(*printer_dict);
if (!printer.has_value()) {
continue;
}
printers.push_back(printer.value());
}
if (printers.empty()) {
return std::nullopt;
}
return printers;
}
std::optional<ParsedReverseIndex> ParseReverseIndex(
std::string_view reverse_index_json) {
const std::optional<base::Value> makes_and_models = ParseJsonAndUnnestKey(
reverse_index_json, "reverseIndex", base::Value::Type::DICT);
if (!makes_and_models.has_value()) {
return std::nullopt;
}
ParsedReverseIndex parsed;
for (const auto [key, value] : makes_and_models->GetDict()) {
const base::Value::Dict* value_dict = value.GetIfDict();
if (!value_dict) {
continue;
}
const std::string* manufacturer = value_dict->FindString("manufacturer");
const std::string* model = value_dict->FindString("model");
if (manufacturer && model && !manufacturer->empty() && !model->empty()) {
parsed.insert_or_assign(key, ReverseIndexLeaf{*manufacturer, *model});
}
}
if (parsed.empty()) {
return std::nullopt;
}
return parsed;
}
} // namespace chromeos