// 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
