Public release: - ZipWriter now rejects a destination that does not support... - Fix asset_write signing of ZIP-based files (ZIP, DOCX, PP... GitOrigin-RevId: 4f4f9976d4e9721657fd140cccc733963f77dceb Change-Id: I97317a69dc3e76b66ec26cbcbdd1b4403d75af7c
diff --git a/formats/zip/BUILD b/formats/zip/BUILD index 1a0dbf8..b9679fb 100644 --- a/formats/zip/BUILD +++ b/formats/zip/BUILD
@@ -378,7 +378,10 @@ "@abseil-cpp//absl/status", "@abseil-cpp//absl/status:status_matchers", "@abseil-cpp//absl/status:statusor", + "@abseil-cpp//absl/strings", "@googletest//:gtest_main", + "@riegeli//riegeli/bytes:cfile_reader", + "@riegeli//riegeli/bytes:cfile_writer", "@riegeli//riegeli/bytes:string_reader", "@riegeli//riegeli/bytes:string_writer", ],
diff --git a/formats/zip/writer.cc b/formats/zip/writer.cc index c349aad..c8741d4 100644 --- a/formats/zip/writer.cc +++ b/formats/zip/writer.cc
@@ -43,6 +43,17 @@ "request must not include `hard_binding` (binding is computed by " "ZipWriter)"); } + // Phase 2 below reads back and patches `destination`. Check this before + // writing anything, so that an unsuitable `destination` is left untouched. + if (!destination->SupportsReadMode()) { + return absl::InvalidArgumentError( + "ZipWriter::Write requires a riegeli::Writer that supports read mode."); + } + if (!destination->SupportsRandomAccess()) { + return absl::InvalidArgumentError( + "ZipWriter::Write requires a riegeli::Writer that supports random " + "access."); + } ABSL_ASSIGN_OR_RETURN(auto reader, ZipReader::Create(source)); @@ -83,10 +94,6 @@ // Phase 2: Compute final hard binding based on the updated content, finalize // the manifest store, and embed the final manifest store in the asset. - if (!destination->SupportsReadMode()) { - return absl::InvalidArgumentError( - "ZipWriter::Write requires a riegeli::Writer that supports read mode."); - } riegeli::Reader* phase2_reader = destination->ReadMode(0); if (phase2_reader == nullptr) { return absl::InternalError("failed to switch to read mode");
diff --git a/formats/zip/writer.h b/formats/zip/writer.h index 78b7a76..5583c56 100644 --- a/formats/zip/writer.h +++ b/formats/zip/writer.h
@@ -41,6 +41,12 @@ // asset. The riegeli::Reader and riegeli::Writer cannot be pointed to the // same underlying data. The request must not include `hard_binding`. // Fails if C2PA metadata already exists. + // + // `destination` must support read mode and random access (e.g. + // `riegeli::StringWriter`, or `riegeli::CFileWriter` opened with + // `set_read(true)`), because the embedded manifest store is finalized by + // reading back and patching the written asset. Otherwise, returns + // `InvalidArgumentError` without writing to `destination`. absl::Status Write(GenerateManifestStoreRequest& request, riegeli::Reader* absl_nonnull source, riegeli::Writer* absl_nonnull destination);
diff --git a/formats/zip/writer_test.cc b/formats/zip/writer_test.cc index 9761ef3..a6ea71f 100644 --- a/formats/zip/writer_test.cc +++ b/formats/zip/writer_test.cc
@@ -23,6 +23,7 @@ #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" @@ -31,6 +32,8 @@ #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" @@ -127,5 +130,35 @@ 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
diff --git a/tools/BUILD b/tools/BUILD index cf83f83..c11089f 100644 --- a/tools/BUILD +++ b/tools/BUILD
@@ -110,22 +110,13 @@ deps = [ ":asset_writer", "//crypto/default:default_crypto_read_handler", - "//formats/bmff:extractor", "//formats/bmff:test_utils", "//formats/gif:constants", - "//formats/gif:extractor", "//formats/gif:test_builder", - "//formats/id3:extractor", - "//formats/jpeg:extractor", "//formats/jpeg/testing:jpeg_creator", - "//formats/pdf:extractor", - "//formats/png:extractor", "//formats/png:test_utils", "//formats/riff:create_riff", - "//formats/riff:extractor", "//formats/tiff:constants", - "//formats/tiff:extractor", - "//formats/zip:extractor", "//proto:generator_cc_proto", "//proto:manifest_cc_proto", "//proto:validation_status_cc_proto", @@ -139,9 +130,11 @@ "@abseil-cpp//absl/status:status_matchers", "@abseil-cpp//absl/status:statusor", "@abseil-cpp//absl/strings", + "@abseil-cpp//absl/strings:string_view", "@boringssl//:crypto", "@googletest//:gtest_main", "@riegeli//riegeli/bytes:cfile_reader", + "@riegeli//riegeli/bytes:reader", "@riegeli//riegeli/bytes:string_reader", "@riegeli//riegeli/bytes:string_writer", "@riegeli//riegeli/endian:endian_writing",
diff --git a/tools/asset_writer.cc b/tools/asset_writer.cc index a177688..55f3268 100644 --- a/tools/asset_writer.cc +++ b/tools/asset_writer.cc
@@ -162,7 +162,10 @@ return source.status(); } - riegeli::CFileWriter<> destination(output_path); + // Some writers (e.g. `ZipWriter`) read back the destination, so it must be + // opened for reading as well. + riegeli::CFileWriter<> destination( + output_path, riegeli::CFileWriterBase::Options().set_read(true)); if (!destination.ok()) { return destination.status(); }
diff --git a/tools/asset_writer_test.cc b/tools/asset_writer_test.cc index 75c3654..81edcae 100644 --- a/tools/asset_writer_test.cc +++ b/tools/asset_writer_test.cc
@@ -17,6 +17,7 @@ #include <cstdint> #include <fstream> +#include <functional> #include <ios> #include <iterator> #include <memory> @@ -29,29 +30,22 @@ #include "absl/status/status_matchers.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" #include "crypto/default/default_crypto_read_handler.h" -#include "formats/bmff/extractor.h" #include "formats/bmff/test_utils.h" #include "formats/gif/constants.h" -#include "formats/gif/extractor.h" #include "formats/gif/test_builder.h" -#include "formats/id3/extractor.h" -#include "formats/jpeg/extractor.h" #include "formats/jpeg/testing/jpeg_creator.h" -#include "formats/pdf/extractor.h" -#include "formats/png/extractor.h" #include "formats/png/test_utils.h" #include "formats/riff/create_riff.h" -#include "formats/riff/extractor.h" #include "formats/tiff/constants.h" -#include "formats/tiff/extractor.h" -#include "formats/zip/extractor.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/reader.h" #include "riegeli/bytes/string_reader.h" #include "riegeli/bytes/string_writer.h" #include "riegeli/endian/endian_writing.h" @@ -69,16 +63,17 @@ using ::testing::IsEmpty; using ::testing::Not; -LocalKeyCryptoWriteHandlerOptions CreateWriteOptions( - const TestPki& pki, bool disable_timestamping) { +constexpr absl::string_view kPptxMediaType = + "application/" + "vnd.openxmlformats-officedocument.presentationml.presentation"; + +// Signs with the claim signer and timestamps with the local TSA of `pki`. +LocalKeyCryptoWriteHandlerOptions CreateWriteOptions(const TestPki& pki) { LocalKeyCryptoWriteHandlerOptions options; options.claim_signer.key_pem = pki.claim_signer.key_pem; options.claim_signer.certificate_chain = {pki.claim_signer.cert_der}; - options.disable_timestamping = disable_timestamping; - if (!disable_timestamping) { - options.tsa.key_pem = pki.tsa.key_pem; - options.tsa.cert_pem = pki.tsa.cert_pem; - } + options.tsa.key_pem = pki.tsa.key_pem; + options.tsa.cert_pem = pki.tsa.cert_pem; return options; } @@ -95,140 +90,38 @@ }); } -TEST(AssetWriterTest, WritesPngManifestStore) { - auto png_or = CreatePng({Chunk{.type = 0x49444154, .payload = "pixel_data"}}); - ABSL_ASSERT_OK(png_or); - - riegeli::StringReader<> source(*png_or); - std::string output_bytes; - riegeli::StringWriter<> destination(&output_bytes); - +GenerateManifestStoreRequest CreateRequest() { GenerateManifestStoreRequest request; request.mutable_actions()->mutable_created(); - - TestPki pki = CreateTestPki(); - EXPECT_THAT(WriteAsset(CreateWriteOptions(pki, /*disable_timestamping=*/true), - std::move(request), "image/png", source, destination), - IsOk()); - ASSERT_TRUE(destination.Close()); - - riegeli::StringReader<> result_reader(output_bytes); - PngExtractor extractor; - auto extracted = extractor.ExtractManifestStore(result_reader); - ABSL_ASSERT_OK(extracted); - EXPECT_THAT(*extracted, Not(IsEmpty())); - - ASSERT_TRUE(result_reader.Seek(0)); - auto validator = CreateTestValidator(pki); - auto validation_result = validator->Validate(result_reader, "image/png"); - ABSL_ASSERT_OK(validation_result); - EXPECT_THAT( - (*validation_result)->proto().active_manifest().validation().failures(), - IsEmpty()); + return request; } -TEST(AssetWriterTest, WritesJpegManifestStore) { - std::string jpeg_bytes = CreateJpeg({}); - riegeli::StringReader<> source(jpeg_bytes); - std::string output_bytes; - riegeli::StringWriter<> destination(&output_bytes); +// Minimal unsigned test assets, one per supported container format. - GenerateManifestStoreRequest request; - request.mutable_actions()->mutable_created(); - - TestPki pki = CreateTestPki(); - EXPECT_THAT(WriteAsset(CreateWriteOptions(pki, /*disable_timestamping=*/true), - std::move(request), "image/jpeg", source, destination), - IsOk()); - ASSERT_TRUE(destination.Close()); - - riegeli::StringReader<> result_reader(output_bytes); - JpegExtractor extractor; - auto extracted = extractor.ExtractManifestStore(result_reader); - ABSL_ASSERT_OK(extracted); - EXPECT_THAT(*extracted, Not(IsEmpty())); - - ASSERT_TRUE(result_reader.Seek(0)); - auto validator = CreateTestValidator(pki); - auto validation_result = validator->Validate(result_reader, "image/jpeg"); - ABSL_ASSERT_OK(validation_result); - EXPECT_THAT( - (*validation_result)->proto().active_manifest().validation().failures(), - IsEmpty()); +absl::StatusOr<std::string> CreatePngAsset() { + return CreatePng({Chunk{.type = 0x49444154, .payload = "pixel_data"}}); } -TEST(AssetWriterTest, WritesGifManifestStore) { +absl::StatusOr<std::string> CreateJpegAsset() { return CreateJpeg({}); } + +absl::StatusOr<std::string> CreateGifAsset() { TestGifBuilder builder; builder.AddGifHeader(); builder.AddLogicalDescriptor(/*has_color_table=*/false); builder.AddBlock(kGifExtensionIntroducer, kGifExtensionApplication, "other_app_info", "misc_data"); builder.AddBlock(kGifTrailerIntroducer); - std::string gif_bytes = builder.GetAsset(); - - riegeli::StringReader<> source(gif_bytes); - std::string output_bytes; - riegeli::StringWriter<> destination(&output_bytes); - - GenerateManifestStoreRequest request; - request.mutable_actions()->mutable_created(); - - TestPki pki = CreateTestPki(); - EXPECT_THAT(WriteAsset(CreateWriteOptions(pki, /*disable_timestamping=*/true), - std::move(request), "image/gif", source, destination), - IsOk()); - ASSERT_TRUE(destination.Close()); - - riegeli::StringReader<> result_reader(output_bytes); - GifExtractor extractor; - auto extracted = extractor.ExtractManifestStore(result_reader); - ABSL_ASSERT_OK(extracted); - EXPECT_THAT(*extracted, Not(IsEmpty())); - - ASSERT_TRUE(result_reader.Seek(0)); - auto validator = CreateTestValidator(pki); - auto validation_result = validator->Validate(result_reader, "image/gif"); - ABSL_ASSERT_OK(validation_result); - EXPECT_THAT( - (*validation_result)->proto().active_manifest().validation().failures(), - IsEmpty()); + return builder.GetAsset(); } -TEST(AssetWriterTest, WritesBmffManifestStore) { - std::string bmff_bytes = absl::StrCat( +absl::StatusOr<std::string> CreateBmffAsset() { + return absl::StrCat( credentio_testing::Box("ftyp", {"isom\x00\x00\x02\x00isom", 12}), credentio_testing::Box("moov", ""), credentio_testing::Box("mdat", "video_payload")); - - riegeli::StringReader<> source(bmff_bytes); - std::string output_bytes; - riegeli::StringWriter<> destination(&output_bytes); - - GenerateManifestStoreRequest request; - request.mutable_actions()->mutable_created(); - - TestPki pki = CreateTestPki(); - EXPECT_THAT(WriteAsset(CreateWriteOptions(pki, /*disable_timestamping=*/true), - std::move(request), "video/mp4", source, destination), - IsOk()); - ASSERT_TRUE(destination.Close()); - - riegeli::StringReader<> result_reader(output_bytes); - BmffExtractor extractor; - auto extracted = extractor.ExtractManifestStore(result_reader); - ABSL_ASSERT_OK(extracted); - EXPECT_THAT(*extracted, Not(IsEmpty())); - - ASSERT_TRUE(result_reader.Seek(0)); - auto validator = CreateTestValidator(pki); - auto validation_result = validator->Validate(result_reader, "video/mp4"); - ABSL_ASSERT_OK(validation_result); - EXPECT_THAT( - (*validation_result)->proto().active_manifest().validation().failures(), - IsEmpty()); } -TEST(AssetWriterTest, WritesTiffManifestStore) { +absl::StatusOr<std::string> CreateTiffAsset() { std::string tiff_bytes; riegeli::StringWriter<> writer(&tiff_bytes); riegeli::WriteBigEndian<uint32_t>(kTiffBigEndianHeader, writer); @@ -239,103 +132,23 @@ riegeli::WriteBigEndian<uint32_t>(1, writer); riegeli::WriteBigEndian<uint32_t>(10, writer); riegeli::WriteBigEndian<uint32_t>(0, writer); // next IFD offset - ASSERT_TRUE(writer.Close()); - - riegeli::StringReader<> source(tiff_bytes); - std::string output_bytes; - riegeli::StringWriter<> destination(&output_bytes); - - GenerateManifestStoreRequest request; - request.mutable_actions()->mutable_created(); - - TestPki pki = CreateTestPki(); - EXPECT_THAT(WriteAsset(CreateWriteOptions(pki, /*disable_timestamping=*/true), - std::move(request), "image/tiff", source, destination), - IsOk()); - ASSERT_TRUE(destination.Close()); - - riegeli::StringReader<> result_reader(output_bytes); - TiffExtractor extractor; - auto extracted = extractor.ExtractManifestStore(result_reader); - ABSL_ASSERT_OK(extracted); - EXPECT_THAT(*extracted, Not(IsEmpty())); - - ASSERT_TRUE(result_reader.Seek(0)); - auto validator = CreateTestValidator(pki); - auto validation_result = validator->Validate(result_reader, "image/tiff"); - ABSL_ASSERT_OK(validation_result); - EXPECT_THAT( - (*validation_result)->proto().active_manifest().validation().failures(), - IsEmpty()); + if (!writer.Close()) return writer.status(); + return tiff_bytes; } -TEST(AssetWriterTest, WritesRiffManifestStore) { - std::string riff_bytes = credentio_riff::RiffChunk( - "WEBP", credentio_riff::Chunk("VP8 ", "image_data")); - - riegeli::StringReader<> source(riff_bytes); - std::string output_bytes; - riegeli::StringWriter<> destination(&output_bytes); - - GenerateManifestStoreRequest request; - request.mutable_actions()->mutable_created(); - - TestPki pki = CreateTestPki(); - EXPECT_THAT(WriteAsset(CreateWriteOptions(pki, /*disable_timestamping=*/true), - std::move(request), "image/webp", source, destination), - IsOk()); - ASSERT_TRUE(destination.Close()); - - riegeli::StringReader<> result_reader(output_bytes); - RiffExtractor extractor; - auto extracted = extractor.ExtractManifestStore(result_reader); - ABSL_ASSERT_OK(extracted); - EXPECT_THAT(*extracted, Not(IsEmpty())); - - ASSERT_TRUE(result_reader.Seek(0)); - auto validator = CreateTestValidator(pki); - auto validation_result = validator->Validate(result_reader, "image/webp"); - ABSL_ASSERT_OK(validation_result); - EXPECT_THAT( - (*validation_result)->proto().active_manifest().validation().failures(), - IsEmpty()); +absl::StatusOr<std::string> CreateWebpAsset() { + return credentio_riff::RiffChunk("WEBP", + credentio_riff::Chunk("VP8 ", "image_data")); } -TEST(AssetWriterTest, WritesId3ManifestStore) { +absl::StatusOr<std::string> CreateMp3Asset() { constexpr char kMp3Bytes[] = {'\xFF', '\xF3', '\x40', '\xC0', '\x01', '\x02', '\x03', '\x04'}; - std::string id3_bytes(kMp3Bytes, sizeof(kMp3Bytes)); - - riegeli::StringReader<> source(id3_bytes); - std::string output_bytes; - riegeli::StringWriter<> destination(&output_bytes); - - GenerateManifestStoreRequest request; - request.mutable_actions()->mutable_created(); - - TestPki pki = CreateTestPki(); - EXPECT_THAT(WriteAsset(CreateWriteOptions(pki, /*disable_timestamping=*/true), - std::move(request), "audio/mpeg", source, destination), - IsOk()); - ASSERT_TRUE(destination.Close()); - - riegeli::StringReader<> result_reader(output_bytes); - Id3Extractor extractor; - auto extracted = extractor.ExtractManifestStore(result_reader); - ABSL_ASSERT_OK(extracted); - EXPECT_THAT(*extracted, Not(IsEmpty())); - - ASSERT_TRUE(result_reader.Seek(0)); - auto validator = CreateTestValidator(pki); - auto validation_result = validator->Validate(result_reader, "audio/mpeg"); - ABSL_ASSERT_OK(validation_result); - EXPECT_THAT( - (*validation_result)->proto().active_manifest().validation().failures(), - IsEmpty()); + return std::string(kMp3Bytes, sizeof(kMp3Bytes)); } -TEST(AssetWriterTest, WritesPdfManifestStore) { - constexpr absl::string_view kPdfBytes = R"(%PDF-1.7 +absl::StatusOr<std::string> CreatePdfAsset() { + return std::string(R"(%PDF-1.7 %%%%% 1 0 obj /test_name @@ -357,161 +170,124 @@ startxref 98 %%EOF -)"; - - riegeli::StringReader<> source(kPdfBytes); - std::string output_bytes; - riegeli::StringWriter<> destination(&output_bytes); - - GenerateManifestStoreRequest request; - request.mutable_actions()->mutable_created(); - - TestPki pki = CreateTestPki(); - EXPECT_THAT( - WriteAsset(CreateWriteOptions(pki, /*disable_timestamping=*/true), - std::move(request), "application/pdf", source, destination), - IsOk()); - ASSERT_TRUE(destination.Close()); - - riegeli::StringReader<> result_reader(output_bytes); - PdfExtractor extractor; - auto extracted = extractor.ExtractManifestStore(result_reader); - ABSL_ASSERT_OK(extracted); - EXPECT_THAT(*extracted, Not(IsEmpty())); - - ASSERT_TRUE(result_reader.Seek(0)); - auto validator = CreateTestValidator(pki); - auto validation_result = - validator->Validate(result_reader, "application/pdf"); - ABSL_ASSERT_OK(validation_result); - EXPECT_THAT( - (*validation_result)->proto().active_manifest().validation().failures(), - IsEmpty()); +)"); } -TEST(AssetWriterTest, WritesZipManifestStore) { - absl::StatusOr<std::string> pptx_bytes = credentio_testing::GetContents( +absl::StatusOr<std::string> CreatePptxAsset() { + return credentio_testing::GetContents( "credentio/formats/zip/testing/" "no_c2pa_032_asset.pptx"); - ABSL_ASSERT_OK(pptx_bytes); - riegeli::StringReader<> source(*pptx_bytes); +} + +struct FormatTestCase { + std::string test_name; + // File extension used by `WriteAssetFile` to infer the media type. + std::string extension; + std::string media_type; + std::function<absl::StatusOr<std::string>()> create_asset; +}; + +class AssetWriterFormatTest : public testing::TestWithParam<FormatTestCase> { + protected: + // Checks that `signed_asset` has an active manifest that validates against + // `pki` without failures. + void ExpectSignedAndValid(riegeli::Reader& signed_asset, const TestPki& pki) { + auto validation_result = + CreateTestValidator(pki)->Validate(signed_asset, GetParam().media_type); + ABSL_ASSERT_OK(validation_result); + const ValidationStatusSet& validation = + (*validation_result)->proto().active_manifest().validation(); + EXPECT_THAT(validation.failures(), IsEmpty()); + EXPECT_THAT(validation.successes(), Not(IsEmpty())); + } +}; + +TEST_P(AssetWriterFormatTest, WriteAsset) { + const FormatTestCase& test_case = GetParam(); + absl::StatusOr<std::string> asset_bytes = test_case.create_asset(); + ABSL_ASSERT_OK(asset_bytes); + + riegeli::StringReader<> source(*asset_bytes); std::string output_bytes; riegeli::StringWriter<> destination(&output_bytes); - GenerateManifestStoreRequest request; - request.mutable_actions()->mutable_created(); - - constexpr absl::string_view kPptxMediaType = - "application/" - "vnd.openxmlformats-officedocument.presentationml.presentation"; - TestPki pki = CreateTestPki(); - EXPECT_THAT( - WriteAsset(CreateWriteOptions(pki, /*disable_timestamping=*/true), - std::move(request), kPptxMediaType, source, destination), - IsOk()); + ASSERT_THAT(WriteAsset(CreateWriteOptions(pki), CreateRequest(), + test_case.media_type, source, destination), + IsOk()); ASSERT_TRUE(destination.Close()); riegeli::StringReader<> result_reader(output_bytes); - ZipExtractor extractor; - auto extracted = extractor.ExtractManifestStore(result_reader); - ABSL_ASSERT_OK(extracted); - EXPECT_THAT(*extracted, Not(IsEmpty())); - - ASSERT_TRUE(result_reader.Seek(0)); - auto validator = CreateTestValidator(pki); - auto validation_result = validator->Validate(result_reader, kPptxMediaType); - ABSL_ASSERT_OK(validation_result); - EXPECT_THAT( - (*validation_result)->proto().active_manifest().validation().failures(), - IsEmpty()); + ExpectSignedAndValid(result_reader, pki); } -TEST(AssetWriterTest, WritesPngWithTimestamp) { - auto png_or = CreatePng({Chunk{.type = 0x49444154, .payload = "pixel_data"}}); - ABSL_ASSERT_OK(png_or); - - riegeli::StringReader<> source(*png_or); - std::string output_bytes; - riegeli::StringWriter<> destination(&output_bytes); - - GenerateManifestStoreRequest request; - request.mutable_actions()->mutable_created(); - - TestPki pki = CreateTestPki(); - EXPECT_THAT( - WriteAsset(CreateWriteOptions(pki, /*disable_timestamping=*/false), - std::move(request), "image/png", source, destination), - IsOk()); - ASSERT_TRUE(destination.Close()); - - riegeli::StringReader<> result_reader(output_bytes); - auto validator = CreateTestValidator(pki); - auto validation_result = validator->Validate(result_reader, "image/png"); - ABSL_ASSERT_OK(validation_result); - EXPECT_THAT( - (*validation_result)->proto().active_manifest().validation().failures(), - IsEmpty()); -} - -TEST(AssetWriterTest, WriteAssetFileWritesAndValidatesPng) { - auto png_or = CreatePng({Chunk{.type = 0x49444154, .payload = "pixel_data"}}); - ABSL_ASSERT_OK(png_or); +// Unlike `WriteAsset` above, this writes to a real file through +// `riegeli::CFileWriter`, which e.g. `ZipWriter` must be able to read back. +TEST_P(AssetWriterFormatTest, WriteAssetFile) { + const FormatTestCase& test_case = GetParam(); + absl::StatusOr<std::string> asset_bytes = test_case.create_asset(); + ABSL_ASSERT_OK(asset_bytes); const std::string input_path = - absl::StrCat(testing::TempDir(), "/test_input.png"); + absl::StrCat(testing::TempDir(), "/", test_case.test_name, "_input.", + test_case.extension); const std::string output_path = - absl::StrCat(testing::TempDir(), "/test_output.png"); - + absl::StrCat(testing::TempDir(), "/", test_case.test_name, "_output.", + test_case.extension); { std::ofstream out(input_path, std::ios::binary); - out << *png_or; + out << *asset_bytes; + ASSERT_TRUE(out.good()); } - GenerateManifestStoreRequest request; - request.mutable_actions()->mutable_created(); - TestPki pki = CreateTestPki(); - EXPECT_THAT( - WriteAssetFile(CreateWriteOptions(pki, /*disable_timestamping=*/true), - std::move(request), input_path, output_path), - IsOk()); + ASSERT_THAT(WriteAssetFile(CreateWriteOptions(pki), CreateRequest(), + input_path, output_path), + IsOk()); riegeli::CFileReader<> result_reader(output_path); ASSERT_TRUE(result_reader.ok()); - auto validator = CreateTestValidator(pki); - auto validation_result = validator->Validate(result_reader, "image/png"); - ABSL_ASSERT_OK(validation_result); - EXPECT_THAT( - (*validation_result)->proto().active_manifest().validation().failures(), - IsEmpty()); + ExpectSignedAndValid(result_reader, pki); } +INSTANTIATE_TEST_SUITE_P( + AllFormats, AssetWriterFormatTest, + testing::ValuesIn(std::vector<FormatTestCase>{ + {"Png", "png", "image/png", CreatePngAsset}, + {"Jpeg", "jpg", "image/jpeg", CreateJpegAsset}, + {"Gif", "gif", "image/gif", CreateGifAsset}, + {"Bmff", "mp4", "video/mp4", CreateBmffAsset}, + {"Tiff", "tiff", "image/tiff", CreateTiffAsset}, + {"Webp", "webp", "image/webp", CreateWebpAsset}, + {"Mp3", "mp3", "audio/mpeg", CreateMp3Asset}, + {"Pdf", "pdf", "application/pdf", CreatePdfAsset}, + {"Pptx", "pptx", std::string(kPptxMediaType), CreatePptxAsset}, + }), + [](const testing::TestParamInfo<FormatTestCase>& info) { + return info.param.test_name; + }); + TEST(AssetWriterTest, RejectsInPlaceWrite) { - auto png_or = CreatePng({Chunk{.type = 0x49444154, .payload = "pixel_data"}}); - ABSL_ASSERT_OK(png_or); + absl::StatusOr<std::string> png_bytes = CreatePngAsset(); + ABSL_ASSERT_OK(png_bytes); const std::string path = absl::StrCat(testing::TempDir(), "/test_in_place.png"); { std::ofstream out(path, std::ios::binary); - out << *png_or; + out << *png_bytes; } - GenerateManifestStoreRequest request; - request.mutable_actions()->mutable_created(); - TestPki pki = CreateTestPki(); EXPECT_THAT( - WriteAssetFile(CreateWriteOptions(pki, /*disable_timestamping=*/true), - std::move(request), path, path), + WriteAssetFile(CreateWriteOptions(pki), CreateRequest(), path, path), StatusIs(absl::StatusCode::kInvalidArgument)); // The input asset must be left untouched. std::ifstream in(path, std::ios::binary); const std::string contents((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>()); - EXPECT_EQ(contents, *png_or); + EXPECT_EQ(contents, *png_bytes); } TEST(AssetWriterTest, RejectsUnsupportedMediaType) { @@ -519,13 +295,9 @@ std::string output_bytes; riegeli::StringWriter<> destination(&output_bytes); - GenerateManifestStoreRequest request; - request.mutable_actions()->mutable_created(); - TestPki pki = CreateTestPki(); - EXPECT_THAT(WriteAsset(CreateWriteOptions(pki, /*disable_timestamping=*/true), - std::move(request), "application/octet-stream", source, - destination), + EXPECT_THAT(WriteAsset(CreateWriteOptions(pki), CreateRequest(), + "application/octet-stream", source, destination), StatusIs(absl::StatusCode::kUnimplemented)); }