| // 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 <utility> |
| |
| #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 { |
| |
| 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; |
| } |
| } |
| |
| 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->GetList()) { |
| 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(); |
| |
| const auto* namediff = package.FindKey("namediff"); |
| if (namediff && namediff->is_string()) |
| p.namediff = namediff->GetString(); |
| |
| const auto* fingerprint = package.FindKey("fp"); |
| if (fingerprint && fingerprint->is_string()) |
| p.fingerprint = fingerprint->GetString(); |
| |
| const auto* hash_sha256 = package.FindKey("hash_sha256"); |
| if (hash_sha256 && hash_sha256->is_string()) |
| p.hash_sha256 = hash_sha256->GetString(); |
| |
| 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* hashdiff_sha256 = package.FindKey("hashdiff_sha256"); |
| if (hashdiff_sha256 && hashdiff_sha256->is_string()) |
| p.hashdiff_sha256 = hashdiff_sha256->GetString(); |
| |
| 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->GetList(); |
| if (action_list.empty() || !action_list[0].is_dict()) |
| return; |
| |
| const auto* run = action_list[0].FindKey("run"); |
| if (run && run->is_string()) |
| result->action_run = run->GetString(); |
| } |
| |
| 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->GetList()) { |
| 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; |
| } |
| |
| 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; |
| } |
| 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"); |
| 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( |
| {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 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->GetList()) { |
| 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 |