| // Copyright 2026 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| // | |
| #include "formats/zip/writer.h" | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <utility> | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_matchers.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "formats/byte_range.h" | |
| #include "formats/zip/extractor.h" | |
| #include "generator/generator.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "proto/generator.pb.h" | |
| #include "proto/manifest.pb.h" | |
| #include "proto/validation_status.pb.h" | |
| #include "riegeli/bytes/cfile_reader.h" | |
| #include "riegeli/bytes/cfile_writer.h" | |
| #include "riegeli/bytes/string_reader.h" | |
| #include "riegeli/bytes/string_writer.h" | |
| #include "testing/nonprod_generator.h" | |
| #include "testing/nonprod_validator.h" | |
| #include "testing/test_file_utils.h" | |
| #include "validator/asset_validator.h" | |
| #include "validator/result.h" | |
| namespace credentio { | |
| namespace { | |
| using ::absl_testing::IsOkAndHolds; | |
| using ::absl_testing::StatusIs; | |
| using ::testing::AllOf; | |
| using ::testing::Field; | |
| using ::testing::Gt; | |
| using ::testing::HasSubstr; | |
| using ::testing::IsEmpty; | |
| using ::testing::Optional; | |
| class ZipWriterTest : public testing::Test { | |
| protected: | |
| void SetUp() override { | |
| auto generator = CreateNonProdManifestStoreGenerator({}); | |
| ABSL_ASSERT_OK(generator); | |
| generator_ = *std::move(generator); | |
| writer_ = std::make_unique<ZipWriter>(generator_.get()); | |
| auto validator = CreateNonProdValidator({}); | |
| ABSL_ASSERT_OK(validator); | |
| validator_ = *std::move(validator); | |
| } | |
| std::unique_ptr<ManifestStoreGenerator> generator_; | |
| std::unique_ptr<ZipWriter> writer_; | |
| std::unique_ptr<AssetValidator> validator_; | |
| }; | |
| TEST_F(ZipWriterTest, Write) { | |
| GenerateManifestStoreRequest request; | |
| request.mutable_actions() | |
| ->mutable_created() | |
| ->mutable_action_metadata() | |
| ->set_description("test only"); | |
| auto source = credentio_testing::GetFileReader( | |
| "credentio/formats/zip/testing/" | |
| "no_c2pa_032_asset.pptx"); | |
| ABSL_ASSERT_OK(source); | |
| std::string result; | |
| riegeli::StringWriter destination(&result); | |
| ABSL_ASSERT_OK(destination.status()); | |
| ABSL_ASSERT_OK(writer_->Write(request, source->get(), &destination)); | |
| ASSERT_TRUE(destination.Close()); | |
| riegeli::StringReader result_reader(result); | |
| ABSL_ASSERT_OK(result_reader.status()); | |
| EXPECT_THAT(ZipExtractor().ExtractManifestStoreLocation(result_reader, {}), | |
| IsOkAndHolds(Optional(AllOf(Field(&ByteRange::offset, Gt(0)), | |
| Field(&ByteRange::length, Gt(0)))))); | |
| ASSERT_TRUE(result_reader.Seek(0)); | |
| auto validation_result = validator_->Validate( | |
| result_reader, | |
| "application/" | |
| "vnd.openxmlformats-officedocument.presentationml.presentation"); | |
| ABSL_ASSERT_OK(validation_result); | |
| EXPECT_THAT( | |
| (*validation_result)->proto().active_manifest().validation().failures(), | |
| IsEmpty()); | |
| } | |
| TEST_F(ZipWriterTest, WriteWithHardBindingFails) { | |
| GenerateManifestStoreRequest request; | |
| request.mutable_actions() | |
| ->mutable_created() | |
| ->mutable_action_metadata() | |
| ->set_description("test only"); | |
| request.mutable_hard_binding(); | |
| absl::StatusOr<std::string> pptx_file_contents = | |
| credentio_testing::GetContents( | |
| "credentio/formats/zip/testing/" | |
| "no_c2pa_032_asset.pptx"); | |
| ABSL_ASSERT_OK(pptx_file_contents); | |
| riegeli::StringReader source(*pptx_file_contents); | |
| ABSL_ASSERT_OK(source.status()); | |
| std::string result; | |
| riegeli::StringWriter destination(&result); | |
| ABSL_ASSERT_OK(destination.status()); | |
| EXPECT_THAT(writer_->Write(request, &source, &destination), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("must not include `hard_binding`"))); | |
| } | |
| TEST_F(ZipWriterTest, WriteToDestinationWithoutReadModeFails) { | |
| GenerateManifestStoreRequest request; | |
| request.mutable_actions() | |
| ->mutable_created() | |
| ->mutable_action_metadata() | |
| ->set_description("test only"); | |
| auto source = credentio_testing::GetFileReader( | |
| "credentio/formats/zip/testing/" | |
| "no_c2pa_032_asset.pptx"); | |
| ABSL_ASSERT_OK(source); | |
| // By default, `riegeli::CFileWriter` opens the file write-only. | |
| const std::string output_path = | |
| absl::StrCat(testing::TempDir(), "/write_only_output.pptx"); | |
| riegeli::CFileWriter destination(output_path); | |
| ABSL_ASSERT_OK(destination.status()); | |
| ASSERT_FALSE(destination.SupportsReadMode()); | |
| EXPECT_THAT(writer_->Write(request, source->get(), &destination), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("supports read mode"))); | |
| ASSERT_TRUE(destination.Close()); | |
| // Nothing is written to the rejected destination. | |
| riegeli::CFileReader output(output_path); | |
| ABSL_ASSERT_OK(output.status()); | |
| EXPECT_THAT(output.Size(), Optional(0)); | |
| } | |
| } // namespace | |
| } // namespace credentio |