// Copyright 2018 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 "components/update_client/protocol_parser_json.h" #include #include #include "base/json/json_reader.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" namespace update_client { namespace { std::string GetValueString(const base::Value& node, const char* key) { const auto* value = node.FindKey(key); return (value && value->is_string()) ? value->GetString() : std::string(); } bool ParseManifest(const base::Value& manifest_node, ProtocolParser::Result* result, std::string* error) { if (!manifest_node.is_dict()) { *error = "'manifest' is not a dictionary."; } const auto* version = manifest_node.FindKey("version"); if (!version || !version->is_string()) { *error = "Missing version for manifest."; return false; } result->manifest.version = version->GetString(); if (!base::Version(result->manifest.version).IsValid()) { *error = base::StrCat({"Invalid version: '", result->manifest.version, "'."}); return false; } // Get the optional minimum browser version. const auto* browser_min_version = manifest_node.FindKey("prodversionmin"); if (browser_min_version && browser_min_version->is_string()) { result->manifest.browser_min_version = browser_min_version->GetString(); 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 auto* packages_node = manifest_node.FindKey("packages"); if (!packages_node || !packages_node->is_dict()) { *error = "Missing packages in manifest or 'packages' is not a dictionary."; return false; } const auto* package_node = packages_node->FindKey("package"); if (!package_node || !package_node->is_list()) { *error = "Missing package in packages."; return false; } for (const auto& package : package_node->GetListDeprecated()) { if (!package.is_dict()) { *error = "'package' is not a dictionary."; return false; } ProtocolParser::Result::Manifest::Package p; const auto* name = package.FindKey("name"); if (!name || !name->is_string()) { *error = "Missing name for package."; return false; } p.name = name->GetString(); 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 auto* size = package.FindKey("size"); if (size && (size->is_int() || size->is_double())) { const auto val = size->GetDouble(); if (0 <= val && val < kProtocolMaxInt) p.size = size->GetDouble(); } const auto* sizediff = package.FindKey("sizediff"); if (sizediff && (sizediff->is_int() || sizediff->is_double())) { const auto val = sizediff->GetDouble(); if (0 <= val && val < kProtocolMaxInt) p.sizediff = sizediff->GetDouble(); } 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 auto* action_node = actions_node.FindKey("action"); if (!action_node || !action_node->is_list()) return; const auto& action_list = action_node->GetListDeprecated(); if (action_list.empty() || !action_list[0].is_dict()) return; result->action_run = GetValueString(action_list[0], "run"); } bool ParseUrls(const base::Value& urls_node, ProtocolParser::Result* result, std::string* error) { if (!urls_node.is_dict()) { *error = "'urls' is not a dictionary."; return false; } const auto* url_node = urls_node.FindKey("url"); if (!url_node || !url_node->is_list()) { *error = "Missing url on urls."; return false; } for (const auto& url : url_node->GetListDeprecated()) { if (!url.is_dict()) continue; const auto* codebase = url.FindKey("codebase"); if (codebase && codebase->is_string()) { GURL crx_url(codebase->GetString()); if (crx_url.is_valid()) result->crx_urls.push_back(std::move(crx_url)); } const auto* codebasediff = url.FindKey("codebasediff"); if (codebasediff && codebasediff->is_string()) { GURL crx_diffurl(codebasediff->GetString()); 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, ProtocolParser::Result* result) { if (!data_node.is_dict()) return; result->data.emplace_back(ProtocolParser::Result::Data( GetValueString(data_node, "status"), GetValueString(data_node, "name"), GetValueString(data_node, "index"), GetValueString(data_node, "#text"))); } bool ParseUpdateCheck(const base::Value& updatecheck_node, ProtocolParser::Result* result, std::string* error) { if (!updatecheck_node.is_dict()) { *error = "'updatecheck' is not a dictionary."; return false; } for (auto kv : updatecheck_node.DictItems()) { if (kv.first.front() == '_' && kv.second.is_string()) { result->custom_attributes[kv.first] = kv.second.GetString(); } } const auto* status = updatecheck_node.FindKey("status"); if (!status || !status->is_string()) { *error = "Missing status on updatecheck node"; return false; } result->status = status->GetString(); if (result->status == "noupdate") { const auto* actions_node = updatecheck_node.FindKey("actions"); if (actions_node) ParseActions(*actions_node, result); return true; } if (result->status == "ok") { const auto* actions_node = updatecheck_node.FindKey("actions"); if (actions_node) ParseActions(*actions_node, result); const auto* urls_node = updatecheck_node.FindKey("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.FindKey("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, ProtocolParser::Result* result, std::string* error) { if (!app_node.is_dict()) { *error = "'app' is not a dictionary."; return false; } for (const auto* cohort_key : {ProtocolParser::Result::kCohort, ProtocolParser::Result::kCohortHint, ProtocolParser::Result::kCohortName}) { const auto* cohort_value = app_node.FindKey(cohort_key); if (cohort_value && cohort_value->is_string()) result->cohort_attrs[cohort_key] = cohort_value->GetString(); } const auto* appid = app_node.FindKey("appid"); if (appid && appid->is_string()) result->extension_id = appid->GetString(); 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 auto* status = app_node.FindKey("status"); if (status && status->is_string()) { result->status = status->GetString(); 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; } } DCHECK(result->status.empty() || result->status == "ok"); if (const auto* data_node = app_node.FindKey("data")) { if (const auto* data_list = data_node->GetIfList()) { std::for_each( data_list->begin(), data_list->end(), [&result](const base::Value& data) { ParseData(data, result); }); } } const auto* updatecheck_node = app_node.FindKey("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) { DCHECK(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::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 auto* response_node = doc->FindKey("response"); if (!response_node || !response_node->is_dict()) { ParseError("Missing 'response' element or 'response' is not a dictionary."); return false; } const auto* protocol = response_node->FindKey("protocol"); if (!protocol || !protocol->is_string()) { ParseError("Missing/non-string protocol."); return false; } if (protocol->GetString() != kProtocolVersion) { ParseError("Incorrect protocol. (expected '%s', found '%s')", kProtocolVersion, protocol->GetString().c_str()); return false; } const auto* daystart_node = response_node->FindKey("daystart"); if (daystart_node && daystart_node->is_dict()) { const auto* elapsed_seconds = daystart_node->FindKey("elapsed_seconds"); if (elapsed_seconds && elapsed_seconds->is_int()) results->daystart_elapsed_seconds = elapsed_seconds->GetInt(); const auto* elapsed_days = daystart_node->FindKey("elapsed_days"); if (elapsed_days && elapsed_days->is_int()) results->daystart_elapsed_days = elapsed_days->GetInt(); } const auto* app_node = response_node->FindKey("app"); if (app_node && app_node->is_list()) { for (const auto& app : app_node->GetListDeprecated()) { 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