blob: 7294c1cc962fe1cec51a54c983fc93f3c703dd38 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/update_client/protocol_parser_json.h"
#include <algorithm>
#include <cstdint>
#include <optional>
#include <string>
#include <utility>
#include "base/check.h"
#include "base/json/json_reader.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/types/expected.h"
#include "base/values.h"
#include "base/version.h"
#include "components/update_client/protocol_definition.h"
namespace update_client {
namespace {
std::string GetValueString(const base::Value::Dict& node, const char* key) {
const std::string* value = node.FindString(key);
return value ? *value : std::string();
}
base::expected<std::string, std::string> Parse(const base::Value::Dict& node,
const std::string& key) {
const std::string* value = node.FindString(key);
if (!value) {
return base::unexpected(base::StrCat({"Missing ", key}));
}
return base::expected<std::string, std::string>(*value);
}
base::expected<base::Version, std::string> ParseVersion(
const base::Value::Dict& node,
const std::string& key) {
base::expected<std::string, std::string> value = Parse(node, key);
if (!value.has_value()) {
return base::unexpected(value.error());
}
base::Version version(value.value());
if (!version.IsValid()) {
return base::unexpected(
base::StrCat({"Invalid version: '", value.value(), "'."}));
}
return version;
}
std::optional<std::string> ParseOptional(const base::Value::Dict& node,
const std::string& key) {
const std::string* value = node.FindString(key);
if (value) {
return *value;
}
return std::nullopt;
}
int64_t ParseNumberWithDefault(const base::Value::Dict& node,
const std::string& key,
int64_t def) {
const std::optional<double> value = node.FindDouble(key);
if (value) {
const double val = value.value();
if (0 <= val && val < protocol_request::kProtocolMaxInt) {
return static_cast<int64_t>(val);
}
}
return def;
}
std::string ParseWithDefault(const base::Value::Dict& node,
const std::string& key,
const std::string& def) {
const std::string* value = node.FindString(key);
if (value) {
return *value;
}
return def;
}
std::string ParseWithDefault(const base::Value::Dict& node,
const std::string& outer_key,
const std::string& inner_key,
const std::string& def) {
const base::Value::Dict* outer = node.FindDict(outer_key);
return outer ? ParseWithDefault(*outer, inner_key, def) : def;
}
base::expected<ProtocolParser::Operation, std::string> ParseOperation(
const base::Value& node_val) {
if (!node_val.is_dict()) {
return base::unexpected("'operation' contains a non-dictionary.");
}
const base::Value::Dict& node = node_val.GetDict();
ProtocolParser::Operation op;
base::expected<std::string, std::string> type = Parse(node, "type");
if (!type.has_value()) {
return base::unexpected(type.error());
}
op.type = type.value();
op.sha256_out = ParseWithDefault(node, "out", "sha256", {});
op.sha256_in = ParseWithDefault(node, "in", "sha256", {});
op.sha256_previous = ParseWithDefault(node, "previous", "sha256", {});
op.path = ParseWithDefault(node, "path", {});
op.arguments = ParseWithDefault(node, "arguments", {});
op.size = ParseNumberWithDefault(node, "size", 0);
if (const base::Value::List* list = node.FindList("urls")) {
for (const base::Value& url_node : *list) {
if (!url_node.is_dict()) {
return base::unexpected("url node is not a dict");
}
base::expected<std::string, std::string> url =
Parse(url_node.GetDict(), "url");
if (!url.has_value()) {
return base::unexpected(url.error());
}
GURL gurl(url.value());
if (!gurl.is_valid()) {
return base::unexpected("operation contains a malformed url");
}
op.urls.push_back(gurl);
}
}
return op;
}
base::expected<ProtocolParser::Pipeline, std::string> ParsePipeline(
const base::Value& node_val) {
if (!node_val.is_dict()) {
return base::unexpected("'pipeline' contains a non-dictionary.");
}
ProtocolParser::Pipeline pipeline;
pipeline.pipeline_id =
ParseWithDefault(node_val.GetDict(), "pipeline_id", {});
if (const base::Value::List* node =
node_val.GetDict().FindList("operations")) {
for (const base::Value& operation_node : *node) {
base::expected<ProtocolParser::Operation, std::string> operation =
ParseOperation(operation_node);
if (!operation.has_value()) {
return base::unexpected(operation.error());
}
pipeline.operations.push_back(operation.value());
}
}
return pipeline;
}
void ParseData(const base::Value& data_node_val, ProtocolParser::App* result) {
if (!data_node_val.is_dict()) {
return;
}
const base::Value::Dict& data_node = data_node_val.GetDict();
result->data.emplace_back(
GetValueString(data_node, "index"), GetValueString(data_node, "#text"));
}
bool ParseUpdateCheck(const base::Value* node_val,
ProtocolParser::App* result,
std::string* error) {
if (!node_val || !node_val->is_dict()) {
*error = "'updatecheck' node is missing or not a dictionary.";
return false;
}
const base::Value::Dict& node = node_val->GetDict();
for (auto [k, v] : node) {
if (!k.empty() && k.front() == '_' && v.is_string()) {
result->custom_attributes[k] = v.GetString();
}
}
// result->status was set to "ok" when parsing the app node; overwrite it with
// the updatecheck status.
base::expected<std::string, std::string> status = Parse(node, "status");
if (!status.has_value()) {
*error = status.error();
return false;
}
result->status = status.value();
if (result->status == "noupdate") {
return true;
}
if (result->status == "ok") {
base::expected<base::Version, std::string> nextversion =
ParseVersion(node, "nextversion");
if (nextversion.has_value()) {
result->nextversion = nextversion.value();
} else {
*error = nextversion.error();
return false;
}
if (const base::Value::List* list = node.FindList("pipelines")) {
for (const base::Value& pipeline_node : *list) {
base::expected<ProtocolParser::Pipeline, std::string> pipeline =
ParsePipeline(pipeline_node);
if (!pipeline.has_value()) {
*error = pipeline.error();
return false;
}
result->pipelines.push_back(pipeline.value());
}
}
return true;
}
// Return the |updatecheck| element status as a parsing error.
*error = result->status;
return true;
}
bool ParseApp(const base::Value& node_value,
ProtocolParser::App* result,
std::string* error) {
if (!node_value.is_dict()) {
*error = "'app' is not a dictionary.";
return false;
}
const base::Value::Dict& node = node_value.GetDict();
result->cohort = ParseOptional(node, "cohort");
result->cohort_name = ParseOptional(node, "cohortname");
result->cohort_hint = ParseOptional(node, "cohorthint");
base::expected<std::string, std::string> appid = Parse(node, "appid");
if (!appid.has_value()) {
*error = appid.error();
return false;
}
result->app_id = appid.value();
base::expected<std::string, std::string> status = Parse(node, "status");
if (!status.has_value()) {
*error = status.error();
return false;
}
result->status = status.value();
if (result->status == "ok") {
if (const base::Value::List* data_node = node.FindList("data")) {
std::ranges::for_each(*data_node, [&result](const base::Value& data) {
ParseData(data, result);
});
}
return ParseUpdateCheck(node.Find("updatecheck"), result, error);
}
return true;
}
} // namespace
bool ProtocolParserJSON::DoParse(const std::string& response_json,
Results* results) {
CHECK(results);
if (response_json.empty()) {
ParseError("Empty JSON.");
return false;
}
// The JSON response contains a prefix to prevent XSSI.
constexpr char kJSONPrefix[] = ")]}'";
if (!base::StartsWith(response_json, kJSONPrefix,
base::CompareCase::SENSITIVE)) {
ParseError("Missing secure JSON prefix.");
return false;
}
const auto doc = base::JSONReader::ReadDict(base::MakeStringPiece(
response_json.begin() + std::char_traits<char>::length(kJSONPrefix),
response_json.end()));
if (!doc) {
ParseError("JSON read error.");
return false;
}
const base::Value::Dict* response_node = doc->FindDict("response");
if (!response_node) {
ParseError("Missing 'response' element or 'response' is not a dictionary.");
return false;
}
const std::string* protocol = response_node->FindString("protocol");
if (!protocol) {
ParseError("Missing/non-string protocol.");
return false;
}
if (*protocol != protocol_request::kProtocolVersion) {
ParseError("Incorrect protocol. (expected '%s', found '%s')",
protocol_request::kProtocolVersion, protocol->c_str());
return false;
}
const base::Value::Dict* daystart_node = response_node->FindDict("daystart");
if (daystart_node) {
const std::optional<int> elapsed_days =
daystart_node->FindInt("elapsed_days");
if (elapsed_days) {
results->daystart_elapsed_days = *elapsed_days;
}
}
const base::Value::List* app_node = response_node->FindList("apps");
if (app_node) {
for (const auto& app : *app_node) {
App result;
std::string error;
if (ParseApp(app, &result, &error)) {
results->apps.push_back(result);
} else {
ParseError("%s", error.c_str());
}
}
}
return true;
}
base::expected<ProtocolParser::Results, std::string>
ProtocolParserJSON::ParseJSON(const std::string& json) {
ProtocolParserJSON parser;
if (parser.Parse(json)) {
return parser.results();
}
return base::unexpected(parser.errors());
}
} // namespace update_client