| // 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 <string> |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/json/json_reader.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_util.h" |
| #include "base/values.h" |
| #include "base/version.h" |
| #include "components/update_client/protocol_definition.h" |
| #include "third_party/abseil-cpp/absl/types/optional.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(); |
| } |
| |
| bool ParseManifest(const base::Value& manifest_node_val, |
| ProtocolParser::Result* result, |
| std::string* error) { |
| if (!manifest_node_val.is_dict()) { |
| *error = "'manifest' is not a dictionary."; |
| return false; |
| } |
| const base::Value::Dict& manifest_node = manifest_node_val.GetDict(); |
| const std::string* version = manifest_node.FindString("version"); |
| if (!version) { |
| *error = "Missing version for manifest."; |
| return false; |
| } |
| |
| result->manifest.version = *version; |
| if (!base::Version(result->manifest.version).IsValid()) { |
| *error = |
| base::StrCat({"Invalid version: '", result->manifest.version, "'."}); |
| return false; |
| } |
| |
| // Get the optional minimum browser version. |
| const std::string* browser_min_version = |
| manifest_node.FindString("prodversionmin"); |
| if (browser_min_version) { |
| result->manifest.browser_min_version = *browser_min_version; |
| if (!base::Version(result->manifest.browser_min_version).IsValid()) { |
| *error = base::StrCat({"Invalid prodversionmin: '", |
| result->manifest.browser_min_version, "'."}); |
| return false; |
| } |
| } |
| |
| result->manifest.run = GetValueString(manifest_node, "run"); |
| result->manifest.arguments = GetValueString(manifest_node, "arguments"); |
| |
| const base::Value::Dict* packages_node = manifest_node.FindDict("packages"); |
| if (!packages_node) { |
| *error = "Missing packages in manifest or 'packages' is not a dictionary."; |
| return false; |
| } |
| const base::Value::List* package_node = packages_node->FindList("package"); |
| if (!package_node) { |
| *error = "Missing package in packages."; |
| return false; |
| } |
| |
| for (const auto& package_val : *package_node) { |
| if (!package_val.is_dict()) { |
| *error = "'package' is not a dictionary."; |
| return false; |
| } |
| const base::Value::Dict& package = package_val.GetDict(); |
| ProtocolParser::Result::Manifest::Package p; |
| const std::string* name = package.FindString("name"); |
| if (!name) { |
| *error = "Missing name for package."; |
| return false; |
| } |
| p.name = *name; |
| |
| p.namediff = GetValueString(package, "namediff"); |
| p.fingerprint = GetValueString(package, "fp"); |
| p.hash_sha256 = GetValueString(package, "hash_sha256"); |
| p.hashdiff_sha256 = GetValueString(package, "hashdiff_sha256"); |
| |
| const absl::optional<double> size = package.FindDouble("size"); |
| if (size) { |
| const double val = size.value(); |
| if (0 <= val && val < kProtocolMaxInt) |
| p.size = val; |
| } |
| |
| const absl::optional<double> sizediff = package.FindDouble("sizediff"); |
| if (sizediff) { |
| const double val = sizediff.value(); |
| if (0 <= val && val < kProtocolMaxInt) |
| p.sizediff = val; |
| } |
| |
| result->manifest.packages.push_back(std::move(p)); |
| } |
| |
| return true; |
| } |
| |
| void ParseActions(const base::Value& actions_node, |
| ProtocolParser::Result* result) { |
| if (!actions_node.is_dict()) |
| return; |
| |
| const base::Value::List* action_node = |
| actions_node.GetDict().FindList("action"); |
| if (!action_node) { |
| return; |
| } |
| |
| const base::Value::List& action_list = *action_node; |
| if (action_list.empty() || !action_list[0].is_dict()) |
| return; |
| |
| result->action_run = GetValueString(action_list[0].GetDict(), "run"); |
| } |
| |
| bool ParseUrls(const base::Value& urls_node_val, |
| ProtocolParser::Result* result, |
| std::string* error) { |
| if (!urls_node_val.is_dict()) { |
| *error = "'urls' is not a dictionary."; |
| return false; |
| } |
| const base::Value::Dict& urls_node = urls_node_val.GetDict(); |
| const base::Value::List* url_node = urls_node.FindList("url"); |
| if (!url_node) { |
| *error = "Missing url on urls."; |
| return false; |
| } |
| |
| for (const base::Value& url_val : *url_node) { |
| if (!url_val.is_dict()) { |
| continue; |
| } |
| const base::Value::Dict& url = url_val.GetDict(); |
| const std::string* codebase = url.FindString("codebase"); |
| if (codebase) { |
| GURL crx_url(*codebase); |
| if (crx_url.is_valid()) { |
| result->crx_urls.push_back(std::move(crx_url)); |
| } |
| } |
| const std::string* codebasediff = url.FindString("codebasediff"); |
| if (codebasediff) { |
| GURL crx_diffurl(*codebasediff); |
| if (crx_diffurl.is_valid()) { |
| result->crx_diffurls.push_back(std::move(crx_diffurl)); |
| } |
| } |
| } |
| |
| // Expect at least one url for full update. |
| if (result->crx_urls.empty()) { |
| *error = "Missing valid url for full update."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void ParseData(const base::Value& data_node_val, |
| ProtocolParser::Result* 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, "status"), GetValueString(data_node, "name"), |
| GetValueString(data_node, "index"), GetValueString(data_node, "#text")); |
| } |
| |
| bool ParseUpdateCheck(const base::Value& updatecheck_node_val, |
| ProtocolParser::Result* result, |
| std::string* error) { |
| if (!updatecheck_node_val.is_dict()) { |
| *error = "'updatecheck' is not a dictionary."; |
| return false; |
| } |
| const base::Value::Dict& updatecheck_node = updatecheck_node_val.GetDict(); |
| |
| for (auto kv : updatecheck_node) { |
| if (kv.first.front() == '_' && kv.second.is_string()) { |
| result->custom_attributes[kv.first] = kv.second.GetString(); |
| } |
| } |
| |
| const std::string* status = updatecheck_node.FindString("status"); |
| if (!status) { |
| *error = "Missing status on updatecheck node"; |
| return false; |
| } |
| |
| result->status = *status; |
| if (result->status == "noupdate") { |
| const auto* actions_node = updatecheck_node.Find("actions"); |
| if (actions_node) |
| ParseActions(*actions_node, result); |
| return true; |
| } |
| |
| if (result->status == "ok") { |
| const auto* actions_node = updatecheck_node.Find("actions"); |
| if (actions_node) |
| ParseActions(*actions_node, result); |
| |
| const auto* urls_node = updatecheck_node.Find("urls"); |
| if (!urls_node) { |
| *error = "Missing urls on updatecheck."; |
| return false; |
| } |
| |
| if (!ParseUrls(*urls_node, result, error)) |
| return false; |
| |
| const auto* manifest_node = updatecheck_node.Find("manifest"); |
| if (!manifest_node) { |
| *error = "Missing manifest on updatecheck."; |
| return false; |
| } |
| return ParseManifest(*manifest_node, result, error); |
| } |
| |
| // Return the |updatecheck| element status as a parsing error. |
| *error = result->status; |
| return false; |
| } |
| |
| bool ParseApp(const base::Value& app_node_val, |
| ProtocolParser::Result* result, |
| std::string* error) { |
| if (!app_node_val.is_dict()) { |
| *error = "'app' is not a dictionary."; |
| return false; |
| } |
| const base::Value::Dict& app_node = app_node_val.GetDict(); |
| for (const auto* cohort_key : |
| {ProtocolParser::Result::kCohort, ProtocolParser::Result::kCohortHint, |
| ProtocolParser::Result::kCohortName}) { |
| const std::string* cohort_value = app_node.FindString(cohort_key); |
| if (cohort_value) { |
| result->cohort_attrs[cohort_key] = *cohort_value; |
| } |
| } |
| const std::string* appid = app_node.FindString("appid"); |
| if (appid) { |
| result->extension_id = *appid; |
| } |
| if (result->extension_id.empty()) { |
| *error = "Missing appid on app node"; |
| return false; |
| } |
| |
| // Read the |status| attribute for the app. |
| // If the status is one of the defined app status error literals, then return |
| // it in the result as if it were an updatecheck status, then stop parsing, |
| // and return success. |
| const std::string* status = app_node.FindString("status"); |
| if (status) { |
| result->status = *status; |
| if (result->status == "restricted" || |
| result->status == "error-unknownApplication" || |
| result->status == "error-invalidAppId") |
| return true; |
| |
| // If the status was not handled above and the status is not "ok", then |
| // this must be a status literal that that the parser does not know about. |
| if (!result->status.empty() && result->status != "ok") { |
| *error = "Unknown app status"; |
| return false; |
| } |
| } |
| |
| CHECK(result->status.empty() || result->status == "ok"); |
| |
| if (const base::Value::List* data_node = app_node.FindList("data")) { |
| base::ranges::for_each(*data_node, [&result](const base::Value& data) { |
| ParseData(data, result); |
| }); |
| } |
| |
| const auto* updatecheck_node = app_node.Find("updatecheck"); |
| if (!updatecheck_node) { |
| *error = "Missing updatecheck on app."; |
| return false; |
| } |
| |
| return ParseUpdateCheck(*updatecheck_node, result, error); |
| } |
| |
| } // 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::Read(base::MakeStringPiece( |
| response_json.begin() + std::char_traits<char>::length(kJSONPrefix), |
| response_json.end())); |
| if (!doc) { |
| ParseError("JSON read error."); |
| return false; |
| } |
| if (!doc->is_dict()) { |
| ParseError("JSON document is not a dictionary."); |
| return false; |
| } |
| const base::Value::Dict* response_node = doc->GetDict().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 != kProtocolVersion) { |
| ParseError("Incorrect protocol. (expected '%s', found '%s')", |
| kProtocolVersion, protocol->c_str()); |
| return false; |
| } |
| |
| const base::Value::Dict* daystart_node = response_node->FindDict("daystart"); |
| if (daystart_node) { |
| const absl::optional<int> elapsed_seconds = |
| daystart_node->FindInt("elapsed_seconds"); |
| if (elapsed_seconds) { |
| results->daystart_elapsed_seconds = elapsed_seconds.value(); |
| } |
| const absl::optional<int> elapsed_days = |
| daystart_node->FindInt("elapsed_days"); |
| if (elapsed_days) { |
| results->daystart_elapsed_days = elapsed_days.value(); |
| } |
| } |
| |
| const base::Value::Dict* systemrequirements_node = |
| response_node->FindDict("systemrequirements"); |
| if (systemrequirements_node) { |
| const std::string* platform = |
| systemrequirements_node->FindString("platform"); |
| if (platform) { |
| results->system_requirements.platform = *platform; |
| } |
| const std::string* arch = systemrequirements_node->FindString("arch"); |
| if (arch) { |
| results->system_requirements.arch = *arch; |
| } |
| const std::string* min_os_version = |
| systemrequirements_node->FindString("min_os_version"); |
| if (min_os_version) { |
| results->system_requirements.min_os_version = *min_os_version; |
| } |
| } |
| |
| const base::Value::List* app_node = response_node->FindList("app"); |
| if (app_node) { |
| for (const auto& app : *app_node) { |
| Result result; |
| std::string error; |
| if (ParseApp(app, &result, &error)) |
| results->list.push_back(result); |
| else |
| ParseError("%s", error.c_str()); |
| } |
| } |
| |
| return true; |
| } |
| |
| } // namespace update_client |