blob: c8741d4730ac97d8028be4ba58543e668dc26f17 [file]
// 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 <cstdint>
#include <string>
#include <utility>
#include <vector>
#include "absl/base/nullability.h"
#include "absl/status/status.h"
#include "absl/status/status_macros.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "crypto/algorithms.h"
#include "formats/zip/embedder.h"
#include "formats/zip/hasher_data.h"
#include "formats/zip/reader.h"
#include "proto/generator.pb.h"
#include "riegeli/bytes/reader.h"
#include "riegeli/bytes/writer.h"
namespace credentio {
absl::Status ZipWriter::Write(GenerateManifestStoreRequest& request,
riegeli::Reader* absl_nonnull source,
riegeli::Writer* absl_nonnull destination) {
if (request.has_hard_binding()) {
return absl::InvalidArgumentError(
"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));
std::vector<ZipReader::FileEntry> file_entries;
while (reader->HasNext()) {
ABSL_ASSIGN_OR_RETURN(auto entry, reader->Next());
file_entries.push_back(entry);
}
// The two-phase approach is required for ZIP-based assets: The sections to be
// hashed can't be finalized until the manifest store size is known and
// content types stream is updated.
// Phase 1: Copy the asset to the destination, update the content types
// stream, compute preliminary hard binding, produce a manifest store
// template, and embed a zero-filled placeholder in the asset.
HashAlgorithm alg = HashAlgorithm::kSha256;
ABSL_ASSIGN_OR_RETURN(
*request.mutable_hard_binding()->mutable_collection_data_hash(),
CollectionDataHashTemplate(file_entries, alg));
ABSL_ASSIGN_OR_RETURN(ManifestStoreTemplate manifest_store_template,
generator_.GenerateManifestStoreTemplate(request));
constexpr int64_t kMaxManifestStoreSizeBytes = 100 * 1024 * 1024; // 100 MiB
if (manifest_store_template.manifest_store_size() <= 0 ||
manifest_store_template.manifest_store_size() >
kMaxManifestStoreSizeBytes) {
return absl::InvalidArgumentError(
absl::StrCat("invalid manifest store template size: ",
manifest_store_template.manifest_store_size()));
}
ZipEmbedder embedder;
ABSL_ASSIGN_OR_RETURN(
auto copy_and_allocate_result,
embedder.CopyAndAllocate(source, file_entries,
manifest_store_template.manifest_store_size(),
destination));
// Phase 2: Compute final hard binding based on the updated content, finalize
// the manifest store, and embed the final manifest store in the asset.
riegeli::Reader* phase2_reader = destination->ReadMode(0);
if (phase2_reader == nullptr) {
return absl::InternalError("failed to switch to read mode");
}
ABSL_ASSIGN_OR_RETURN(auto phase2_zip_reader,
ZipReader::Create(phase2_reader));
std::vector<ZipReader::FileEntry> phase2_file_entries;
while (phase2_zip_reader->HasNext()) {
ABSL_ASSIGN_OR_RETURN(auto entry, phase2_zip_reader->Next());
phase2_file_entries.push_back(entry);
}
FinalizeManifestStoreRequest finalize_request;
*finalize_request.mutable_manifest_store_template() =
std::move(manifest_store_template);
ABSL_ASSIGN_OR_RETURN(
*finalize_request.mutable_hard_binding()->mutable_collection_data_hash(),
DigestCollectionDataHash(
phase2_reader, alg, phase2_file_entries,
copy_and_allocate_result.central_directory_offset));
ABSL_ASSIGN_OR_RETURN(auto response,
generator_.FinalizeManifestStore(finalize_request));
if (response.manifest_store().size() !=
finalize_request.manifest_store_template().manifest_store_size()) {
return absl::InternalError("manifest store size does not match template");
}
ABSL_RETURN_IF_ERROR(embedder.Embed(response.manifest_store(),
copy_and_allocate_result, destination));
return absl::OkStatus();
}
} // namespace credentio