| // Copyright 2019 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/web_package/web_bundle_parser_factory.h" |
| |
| #include <algorithm> |
| #include <optional> |
| |
| #include "base/files/file.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/path_service.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_future.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace web_package { |
| |
| namespace { |
| |
| using testing::UnorderedElementsAreArray; |
| |
| base::FilePath GetTestFilePath(const base::FilePath& path) { |
| base::FilePath test_path; |
| base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &test_path); |
| test_path = test_path.Append( |
| base::FilePath(FILE_PATH_LITERAL("components/test/data/web_package"))); |
| return test_path.Append(path); |
| } |
| |
| } // namespace |
| |
| class WebBundleParserFactoryTest : public testing::Test { |
| public: |
| WebBundleParserFactoryTest() |
| : factory_(std::make_unique<WebBundleParserFactory>()) {} |
| |
| std::unique_ptr<mojom::BundleDataSource> CreateFileDataSource( |
| base::File file) { |
| return factory_->CreateFileDataSourceForTesting(std::move(file)); |
| } |
| |
| void GetParserForFile(mojo::PendingReceiver<mojom::WebBundleParser> receiver, |
| base::File file, |
| const std::optional<GURL>& base_url) { |
| mojom::WebBundleParserFactory* factory = factory_.get(); |
| mojo::PendingRemote<mojom::BundleDataSource> |
| file_data_source_pending_remote; |
| auto file_data_source_pending_receiver = |
| file_data_source_pending_remote.InitWithNewPipeAndPassReceiver(); |
| factory->BindFileDataSource(std::move(file_data_source_pending_receiver), |
| std::move(file)); |
| return factory->GetParserForDataSource( |
| std::move(receiver), base_url, |
| std::move(file_data_source_pending_remote)); |
| } |
| |
| private: |
| std::unique_ptr<WebBundleParserFactory> factory_; |
| base::test::TaskEnvironment task_environment_; |
| }; |
| |
| TEST_F(WebBundleParserFactoryTest, FileDataSource) { |
| base::FilePath test_file = |
| GetTestFilePath(base::FilePath(FILE_PATH_LITERAL("hello_b2.wbn"))); |
| |
| base::File file(test_file, base::File::FLAG_OPEN | base::File::FLAG_READ); |
| ASSERT_TRUE(file.IsValid()); |
| int64_t file_length = file.GetLength(); |
| constexpr int64_t test_length = 16; |
| ASSERT_LE(test_length, file_length); |
| std::vector<uint8_t> first16b(test_length); |
| ASSERT_TRUE(file.ReadAndCheck(0, first16b)); |
| std::vector<uint8_t> last16b(test_length); |
| ASSERT_TRUE(file.ReadAndCheck(file_length - test_length, last16b)); |
| |
| auto data_source = CreateFileDataSource(std::move(file)); |
| |
| { |
| base::test::TestFuture<const std::optional<std::vector<uint8_t>>&> future; |
| data_source->Read(/*offset=*/0, test_length, future.GetCallback()); |
| ASSERT_TRUE(future.Get()); |
| EXPECT_EQ(first16b, *future.Get()); |
| } |
| |
| { |
| base::test::TestFuture<const std::optional<std::vector<uint8_t>>&> future; |
| data_source->Read(file_length - test_length, test_length, |
| future.GetCallback()); |
| ASSERT_TRUE(future.Get()); |
| EXPECT_EQ(last16b, *future.Get()); |
| } |
| |
| { |
| base::test::TestFuture<const std::optional<std::vector<uint8_t>>&> future; |
| data_source->Read(file_length - test_length, test_length + 1, |
| future.GetCallback()); |
| ASSERT_TRUE(future.Get()); |
| EXPECT_EQ(last16b, *future.Get()); |
| } |
| |
| { |
| base::test::TestFuture<const std::optional<std::vector<uint8_t>>&> future; |
| data_source->Read(file_length + 1, test_length, future.GetCallback()); |
| ASSERT_FALSE(future.Get()); |
| } |
| |
| { |
| base::test::TestFuture<int64_t> future; |
| data_source->Length(future.GetCallback()); |
| EXPECT_EQ(file_length, future.Get()); |
| } |
| |
| { |
| base::test::TestFuture<bool> future; |
| data_source->IsRandomAccessContext(future.GetCallback()); |
| EXPECT_TRUE(future.Get()); |
| } |
| |
| // Close the file should just work |
| { |
| base::test::TestFuture<void> future; |
| data_source->Close(future.GetCallback()); |
| future.Get(); |
| } |
| } |
| |
| TEST_F(WebBundleParserFactoryTest, GetParserForFile) { |
| base::File file( |
| GetTestFilePath(base::FilePath(FILE_PATH_LITERAL("hello_b2.wbn"))), |
| base::File::FLAG_OPEN | base::File::FLAG_READ); |
| ASSERT_TRUE(file.IsValid()); |
| |
| mojo::Remote<mojom::WebBundleParser> parser; |
| GetParserForFile(parser.BindNewPipeAndPassReceiver(), std::move(file), |
| std::nullopt); |
| |
| mojom::BundleMetadataPtr metadata; |
| { |
| base::test::TestFuture<mojom::BundleMetadataPtr, |
| mojom::BundleMetadataParseErrorPtr> |
| future; |
| parser->ParseMetadata(/*offset=*/std::nullopt, future.GetCallback()); |
| metadata = std::get<0>(future.Take()); |
| } |
| ASSERT_TRUE(metadata); |
| ASSERT_EQ(metadata->requests.size(), 4u); |
| |
| std::map<std::string, mojom::BundleResponsePtr> responses; |
| for (const auto& item : metadata->requests) { |
| base::test::TestFuture<mojom::BundleResponsePtr, |
| mojom::BundleResponseParseErrorPtr> |
| future; |
| parser->ParseResponse(item.second->offset, item.second->length, |
| future.GetCallback()); |
| auto [response, error] = future.Take(); |
| ASSERT_TRUE(response); |
| ASSERT_FALSE(error); |
| responses[item.first.spec()] = std::move(response); |
| } |
| ASSERT_TRUE(responses["https://test.example.org/"]); |
| EXPECT_EQ(responses["https://test.example.org/"]->response_code, 200); |
| EXPECT_EQ( |
| responses["https://test.example.org/"]->response_headers["content-type"], |
| "text/html; charset=utf-8"); |
| EXPECT_TRUE(responses["https://test.example.org/index.html"]); |
| EXPECT_TRUE(responses["https://test.example.org/manifest.webmanifest"]); |
| EXPECT_TRUE(responses["https://test.example.org/script.js"]); |
| } |
| |
| TEST_F(WebBundleParserFactoryTest, GetParserForFileWithRelativeUrls) { |
| base::File file(GetTestFilePath(base::FilePath( |
| FILE_PATH_LITERAL("mixed_absolute_relative_urls.wbn"))), |
| base::File::FLAG_OPEN | base::File::FLAG_READ); |
| ASSERT_TRUE(file.IsValid()); |
| |
| mojo::Remote<mojom::WebBundleParser> parser; |
| GetParserForFile(parser.BindNewPipeAndPassReceiver(), std::move(file), |
| GURL("https://example.com/foo/")); |
| |
| mojom::BundleMetadataPtr metadata; |
| { |
| base::test::TestFuture<mojom::BundleMetadataPtr, |
| mojom::BundleMetadataParseErrorPtr> |
| future; |
| parser->ParseMetadata(/*offset=*/std::nullopt, future.GetCallback()); |
| metadata = std::get<0>(future.Take()); |
| } |
| ASSERT_TRUE(metadata); |
| |
| std::vector<GURL> requests; |
| requests.reserve(metadata->requests.size()); |
| std::ranges::transform(metadata->requests, std::back_inserter(requests), |
| [](const auto& entry) { return entry.first; }); |
| EXPECT_THAT(requests, UnorderedElementsAreArray( |
| {GURL("https://test.example.org/absolute-url"), |
| GURL("https://example.com/relative-url-1"), |
| GURL("https://example.com/foo/relative-url-2")})); |
| } |
| |
| TEST_F(WebBundleParserFactoryTest, DeleteFile) { |
| base::ScopedTempDir tmp_dir; |
| ASSERT_TRUE(tmp_dir.CreateUniqueTempDir()); |
| base::FilePath file_path = tmp_dir.GetPath(); |
| base::CreateTemporaryFile(&file_path); |
| |
| base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ); |
| ASSERT_TRUE(file.IsValid()); |
| |
| mojo::Remote<mojom::WebBundleParser> parser; |
| GetParserForFile(parser.BindNewPipeAndPassReceiver(), std::move(file), |
| std::nullopt); |
| |
| base::test::TestFuture<void> future; |
| parser->Close(future.GetCallback()); |
| future.Get(); |
| |
| EXPECT_TRUE(base::DeleteFile(file_path)); |
| } |
| |
| } // namespace web_package |