blob: 8852e70e01cf5c050d6facd933a9b198bfa2e111 [file] [log] [blame]
// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/updater/safe_manifest_parser.h"
#include <optional>
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "content/public/test/browser_task_environment.h"
#include "extensions/browser/updater/extension_downloader_test_helper.h"
#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
namespace {
class ExtensionUpdateManifestTest : public testing::Test {
public:
void TestParseUpdateManifest(const std::string& xml) {
base::RunLoop run_loop;
ParseUpdateManifest(
xml,
base::BindOnce(&ExtensionUpdateManifestTest::OnUpdateManifestParsed,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
}
protected:
UpdateManifestResults* results() const { return results_.get(); }
const std::optional<ManifestParseFailure>& parse_error() const {
return parse_error_;
}
void ExpectNoError() {
EXPECT_FALSE(parse_error_)
<< "Unexpected error: '" << parse_error_.value().error_detail;
}
private:
void OnUpdateManifestParsed(
base::OnceClosure quit_loop,
std::unique_ptr<UpdateManifestResults> results,
const std::optional<ManifestParseFailure>& failure) {
results_ = std::move(results);
parse_error_ = failure;
std::move(quit_loop).Run();
}
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<UpdateManifestResults> results_;
std::optional<ManifestParseFailure> parse_error_;
data_decoder::test::InProcessDataDecoder in_process_data_decoder_;
};
} // namespace
TEST_F(ExtensionUpdateManifestTest, InvalidXml) {
TestParseUpdateManifest(std::string());
EXPECT_FALSE(results());
EXPECT_TRUE(parse_error());
EXPECT_EQ(parse_error().value().error,
ManifestInvalidError::XML_PARSING_FAILED);
}
TEST_F(ExtensionUpdateManifestTest, MissingAppId) {
TestParseUpdateManifest(
"<?xml version='1.0'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <app>"
" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' />"
" </app>"
"</gupdate>");
ASSERT_EQ(1u, results()->update_list.size());
EXPECT_TRUE(results()->update_list.at(0).parse_error);
EXPECT_EQ(results()->update_list.at(0).parse_error.value().error,
ManifestInvalidError::MISSING_APP_ID);
EXPECT_FALSE(parse_error());
}
TEST_F(ExtensionUpdateManifestTest, InvalidCodebase) {
TestParseUpdateManifest(CreateUpdateManifest(
{UpdateManifestItem("12345").version("1.2.3.4").codebase(
"example.com/extension_1.2.3.4.crx")}));
ASSERT_EQ(1u, results()->update_list.size());
EXPECT_TRUE(results()->update_list.at(0).parse_error);
EXPECT_EQ(results()->update_list.at(0).parse_error.value().error,
ManifestInvalidError::EMPTY_CODEBASE_URL);
EXPECT_FALSE(parse_error());
}
TEST_F(ExtensionUpdateManifestTest, MissingVersion) {
TestParseUpdateManifest(
CreateUpdateManifest({UpdateManifestItem("12345").codebase(
"http://example.com/extension_1.2.3.4.crx")}));
ASSERT_EQ(1u, results()->update_list.size());
EXPECT_TRUE(results()->update_list.at(0).parse_error);
EXPECT_EQ(results()->update_list.at(0).parse_error.value().error,
ManifestInvalidError::MISSING_VERSION_FOR_UPDATE_CHECK);
EXPECT_FALSE(parse_error());
}
TEST_F(ExtensionUpdateManifestTest, InvalidVersion) {
TestParseUpdateManifest(CreateUpdateManifest(
{UpdateManifestItem("12345").version("1.2.3.a").codebase(
"http://example.com/extension_1.2.3.4.crx")}));
ASSERT_EQ(1u, results()->update_list.size());
EXPECT_TRUE(results()->update_list.at(0).parse_error);
EXPECT_EQ(results()->update_list.at(0).parse_error.value().error,
ManifestInvalidError::INVALID_VERSION);
EXPECT_FALSE(parse_error());
}
TEST_F(ExtensionUpdateManifestTest, ValidXml) {
TestParseUpdateManifest(CreateUpdateManifest(
{UpdateManifestItem("12345")
.version("1.2.3.4")
.prodversionmin("2.0.143.0")
.codebase("http://example.com/extension_1.2.3.4.crx")}));
ExpectNoError();
ASSERT_TRUE(results());
EXPECT_EQ(1U, results()->update_list.size());
const UpdateManifestResult& first_result = results()->update_list.at(0);
EXPECT_FALSE(first_result.parse_error);
EXPECT_EQ(GURL("http://example.com/extension_1.2.3.4.crx"),
first_result.crx_url);
EXPECT_EQ("1.2.3.4", first_result.version);
EXPECT_EQ("2.0.143.0", first_result.browser_min_version);
}
TEST_F(ExtensionUpdateManifestTest, ValidXmlWithNamespacePrefix) {
TestParseUpdateManifest(
"<?xml version='1.0' encoding='UTF-8'?>"
"<g:gupdate xmlns:g='http://www.google.com/update2/response'"
" protocol='2.0'>"
" <g:app appid='12345'>"
" <g:updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' prodversionmin='2.0.143.0' />"
" </g:app>"
"</g:gupdate>");
ExpectNoError();
ASSERT_TRUE(results());
EXPECT_EQ(1U, results()->update_list.size());
const UpdateManifestResult& first_result = results()->update_list.at(0);
EXPECT_FALSE(first_result.parse_error);
EXPECT_EQ(GURL("http://example.com/extension_1.2.3.4.crx"),
first_result.crx_url);
EXPECT_EQ("1.2.3.4", first_result.version);
EXPECT_EQ("2.0.143.0", first_result.browser_min_version);
}
TEST_F(ExtensionUpdateManifestTest, SimilarTagnames) {
// Includes unrelated <app> tags from other xml namespaces.
// This should not cause problems, the unrelated app tags should be ignored.
TestParseUpdateManifest(
"<?xml version='1.0' encoding='UTF-8'?>"
"<gupdate xmlns='http://www.google.com/update2/response'"
" xmlns:a='http://a' protocol='2.0'>"
" <a:app/>"
" <b:app xmlns:b='http://b' />"
" <app appid='12345'>"
" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' prodversionmin='2.0.143.0' />"
" </app>"
" <a:app appid='12345'>"
" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' prodversionmin='2.0.143.0' />"
" </a:app>"
"</gupdate>");
ExpectNoError();
ASSERT_TRUE(results());
// We should still have parsed the gupdate app tag.
EXPECT_EQ(1U, results()->update_list.size());
EXPECT_FALSE(results()->update_list.at(0).parse_error);
EXPECT_EQ(GURL("http://example.com/extension_1.2.3.4.crx"),
results()->update_list.at(0).crx_url);
}
TEST_F(ExtensionUpdateManifestTest, XmlWithHash) {
TestParseUpdateManifest(CreateUpdateManifest(
{UpdateManifestItem("12345")
.version("1.2.3.4")
.prodversionmin("2.0.143.0")
.codebase("http://example.com/extension_1.2.3.4.crx")
.hash_sha256("1234")}));
ExpectNoError();
ASSERT_TRUE(results());
EXPECT_EQ(1U, results()->update_list.size());
const UpdateManifestResult& first_result = results()->update_list.at(0);
EXPECT_FALSE(first_result.parse_error);
EXPECT_EQ("1234", first_result.package_hash);
}
TEST_F(ExtensionUpdateManifestTest, XmlWithDaystart) {
TestParseUpdateManifest(
"<?xml version='1.0' encoding='UTF-8'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <daystart elapsed_seconds='456' />"
" <app appid='12345'>"
" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
" version='1.2.3.4' prodversionmin='2.0.143.0' />"
" </app>"
"</gupdate>");
ExpectNoError();
ASSERT_TRUE(results());
EXPECT_EQ(results()->daystart_elapsed_seconds, 456);
}
TEST_F(ExtensionUpdateManifestTest, NoUpdateResponse) {
TestParseUpdateManifest(
CreateUpdateManifest({UpdateManifestItem("12345").status("noupdate")}));
ExpectNoError();
ASSERT_TRUE(results());
ASSERT_FALSE(results()->update_list.empty());
const UpdateManifestResult& first_result = results()->update_list.at(0);
EXPECT_FALSE(first_result.parse_error);
EXPECT_EQ(first_result.extension_id, "12345");
EXPECT_TRUE(first_result.version.empty());
}
TEST_F(ExtensionUpdateManifestTest, TwoAppsOneError) {
TestParseUpdateManifest(
"<?xml version='1.0' encoding='UTF-8'?>"
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
" <app appid='aaaaaaaa' status='error-unknownApplication'>"
" <updatecheck status='error-unknownapplication'/>"
" </app>"
" <app appid='bbbbbbbb'>"
" <updatecheck codebase='http://example.com/b_3.1.crx' version='3.1'/>"
" </app>"
"</gupdate>");
EXPECT_FALSE(parse_error());
ASSERT_TRUE(results());
EXPECT_EQ(2U, results()->update_list.size());
EXPECT_FALSE(results()->update_list.at(1).parse_error);
const UpdateManifestResult& second_result = results()->update_list.at(1);
EXPECT_EQ(second_result.extension_id, "bbbbbbbb");
EXPECT_TRUE(results()->update_list.at(0).parse_error);
}
TEST_F(ExtensionUpdateManifestTest, Duplicates) {
TestParseUpdateManifest(
CreateUpdateManifest({UpdateManifestItem("aaaaaaaa").status("noupdate"),
UpdateManifestItem("bbbbbbbb")
.version("3.1")
.codebase("http://example.com/b_3.1.crx"),
UpdateManifestItem("aaaaaaaa").status("noupdate"),
UpdateManifestItem("aaaaaaaa")
.version("2.0")
.codebase("http://example.com/a_2.0.crx")}));
ExpectNoError();
ASSERT_TRUE(results());
const auto& list = results()->update_list;
ASSERT_EQ(4u, list.size());
EXPECT_FALSE(list[0].parse_error);
EXPECT_EQ("aaaaaaaa", list[0].extension_id);
EXPECT_TRUE(list[0].version.empty());
EXPECT_FALSE(list[1].parse_error);
EXPECT_EQ("bbbbbbbb", list[1].extension_id);
EXPECT_EQ("3.1", list[1].version);
EXPECT_EQ(GURL("http://example.com/b_3.1.crx"), list[1].crx_url);
EXPECT_FALSE(list[2].parse_error);
EXPECT_EQ("aaaaaaaa", list[2].extension_id);
EXPECT_TRUE(list[2].version.empty());
EXPECT_FALSE(list[3].parse_error);
EXPECT_EQ("aaaaaaaa", list[3].extension_id);
EXPECT_EQ("2.0", list[3].version);
EXPECT_EQ(GURL("http://example.com/a_2.0.crx"), list[3].crx_url);
}
TEST_F(ExtensionUpdateManifestTest, GroupByID) {
TestParseUpdateManifest(CreateUpdateManifest({
UpdateManifestItem("aaaaaaaa").status("noupdate"),
UpdateManifestItem("bbbbbbbb").status("noupdate"),
UpdateManifestItem("aaaaaaaa").status("noupdate"),
UpdateManifestItem("bbbbbbbb").status("noupdate"),
UpdateManifestItem("cccccccc").status("noupdate"),
UpdateManifestItem("aaaaaaaa").status("noupdate"),
}));
ExpectNoError();
ASSERT_TRUE(results());
ASSERT_EQ(6u, results()->update_list.size());
const auto groups = results()->GroupSuccessfulByID();
ASSERT_EQ(3u, groups.size());
EXPECT_EQ(3u, groups.at("aaaaaaaa").size());
EXPECT_EQ(2u, groups.at("bbbbbbbb").size());
EXPECT_EQ(1u, groups.at("cccccccc").size());
}
} // namespace extensions