1#ifndef RPNX_COMPRESSION_ZIP_HPP
2#define RPNX_COMPRESSION_ZIP_HPP
49 std::vector< std::byte >
data;
80 [[nodiscard]] std::vector< std::byte >
create_zip(std::span< zip_entry const > entries);
130 if (path.empty() || path.front() ==
'/' || path.front() ==
'\\' || path.find(
'\0') != std::string::npos || path.find(
'\\') != std::string::npos)
134 std::size_t segment_begin = 0U;
135 while (segment_begin <= path.size())
137 std::size_t
const segment_end = path.find(
'/', segment_begin);
138 std::size_t
const length = (segment_end == std::string::npos ? path.size() : segment_end) - segment_begin;
139 std::string_view
const segment(path.data() + segment_begin, length);
140 if (segment ==
".." || (segment_begin == 0U && segment.find(
':') != std::string_view::npos))
144 if (segment_end == std::string::npos)
148 segment_begin = segment_end + 1U;
154 template <
typename output_iterator >
189 return *
this =
static_cast< std::byte
>(value);
243 return std::move(m_output);
250 [[nodiscard]] std::size_t
size() const noexcept
256 output_iterator m_output;
257 std::size_t m_size = 0U;
270 template <
typename output_iterator >
273 auto read_u16 = [&](std::size_t offset)
275 if (offset > archive.size() || archive.size() - offset < 2U)
279 return static_cast< std::uint16_t
>(std::to_integer< std::uint8_t >(archive[offset]) |
static_cast< std::uint16_t
>(std::to_integer< std::uint8_t >(archive[offset + 1U]) << 8U));
281 auto read_u32 = [&](std::size_t offset)
283 if (offset > archive.size() || archive.size() - offset < 4U)
287 return static_cast< std::uint32_t
>(std::to_integer< std::uint8_t >(archive[offset])) | (
static_cast< std::uint32_t
>(std::to_integer< std::uint8_t >(archive[offset + 1U])) << 8U) | (
static_cast< std::uint32_t
>(std::to_integer< std::uint8_t >(archive[offset + 2U])) << 16U) | (
static_cast< std::uint32_t
>(std::to_integer< std::uint8_t >(archive[offset + 3U])) << 24U);
289 if (archive.size() < 22U)
293 std::size_t
const search_begin = archive.size() > 65557U ? archive.size() - 65557U : 0U;
294 std::optional< std::size_t > eocd_offset;
295 for (std::size_t offset = archive.size() - 22U;; --offset)
297 if (read_u32(offset) == 0x06054b50U)
299 eocd_offset = offset;
302 if (offset == search_begin)
307 if (!eocd_offset.has_value())
311 std::size_t
const eocd = *eocd_offset;
312 if (read_u16(eocd + 4U) != 0U || read_u16(eocd + 6U) != 0U || read_u16(eocd + 8U) != read_u16(eocd + 10U))
316 std::size_t
const entry_count = read_u16(eocd + 10U);
317 std::size_t
const directory_size = read_u32(eocd + 12U);
318 std::size_t
const directory_offset = read_u32(eocd + 16U);
319 std::size_t
const comment_size = read_u16(eocd + 20U);
320 if (eocd + 22U + comment_size != archive.size() || directory_offset > archive.size() || directory_size > archive.size() - directory_offset || directory_offset + directory_size != eocd)
329 std::set< std::string > paths;
330 std::size_t cursor = directory_offset;
331 std::size_t total_size = 0U;
332 for (std::size_t entry_index = 0U; entry_index < entry_count; ++entry_index)
334 if (read_u32(cursor) != 0x02014b50U || cursor > archive.size() || archive.size() - cursor < 46U)
338 std::uint16_t
const flags = read_u16(cursor + 8U);
339 std::uint16_t
const method = read_u16(cursor + 10U);
340 std::uint32_t
const expected_checksum = read_u32(cursor + 16U);
341 std::size_t
const compressed_size = read_u32(cursor + 20U);
342 std::size_t
const uncompressed_size = read_u32(cursor + 24U);
343 std::size_t
const name_size = read_u16(cursor + 28U);
344 std::size_t
const extra_size = read_u16(cursor + 30U);
345 std::size_t
const entry_comment_size = read_u16(cursor + 32U);
346 std::size_t
const local_offset = read_u32(cursor + 42U);
347 std::size_t
const central_record_size = 46U + name_size + extra_size + entry_comment_size;
348 if ((flags & 0x0001U) != 0U || (method != 0U && method != 8U))
352 if (cursor > archive.size() || central_record_size > archive.size() - cursor)
357 path.reserve(name_size);
358 for (std::size_t index = 0U; index < name_size; ++index)
360 path.push_back(
static_cast< char >(std::to_integer< std::uint8_t >(archive[cursor + 46U + index])));
370 if (read_u32(local_offset) != 0x04034b50U || local_offset > archive.size() || archive.size() - local_offset < 30U)
374 std::uint16_t
const local_flags = read_u16(local_offset + 6U);
375 std::size_t
const local_name_size = read_u16(local_offset + 26U);
376 std::size_t
const local_extra_size = read_u16(local_offset + 28U);
377 std::size_t
const data_offset = local_offset + 30U + local_name_size + local_extra_size;
378 bool const uses_descriptor = (flags & 0x0008U) != 0U;
379 std::uint32_t
const local_checksum = read_u32(local_offset + 14U);
380 std::uint32_t
const local_compressed_size = read_u32(local_offset + 18U);
381 std::uint32_t
const local_uncompressed_size = read_u32(local_offset + 22U);
382 if (data_offset > directory_offset || compressed_size > directory_offset - data_offset || local_flags != flags || read_u16(local_offset + 8U) != method || local_name_size != name_size || (!uses_descriptor && (local_checksum != expected_checksum || local_compressed_size != compressed_size || local_uncompressed_size != uncompressed_size)) || (uses_descriptor && ((local_checksum != 0U && local_checksum != expected_checksum) || (local_compressed_size != 0U && local_compressed_size != compressed_size) || (local_uncompressed_size != 0U && local_uncompressed_size != uncompressed_size))))
386 for (std::size_t index = 0U; index < name_size; ++index)
388 if (archive[local_offset + 30U + index] != archive[cursor + 46U + index])
395 std::size_t descriptor = data_offset + compressed_size;
396 if (read_u32(descriptor) == 0x08074b50U)
400 if (read_u32(descriptor) != expected_checksum || read_u32(descriptor + 4U) != compressed_size || read_u32(descriptor + 8U) != uncompressed_size)
406 std::span< std::byte const >
const encoded = archive.subspan(data_offset, compressed_size);
407 std::vector< std::byte > data;
408 data.reserve(uncompressed_size);
411 data.assign(encoded.begin(), encoded.end());
425 total_size += uncompressed_size;
426 cursor += central_record_size;
428 if (cursor != directory_offset + directory_size)
451 template < std::input_iterator input_iterator, std::sentinel_for< input_iterator > sentinel,
typename output_iterator >
452 output_iterator
create_zip(input_iterator first, sentinel last, output_iterator output)
454 std::set< std::string > paths;
455 std::vector< zip_detail::directory_record > directory;
456 std::uint64_t archive_size = 0U;
457 auto write_byte = [&](std::byte value)
461 if (archive_size > std::numeric_limits< std::uint32_t >::max())
466 auto write_u16 = [&](std::uint16_t value)
468 write_byte(
static_cast< std::byte
>(value));
469 write_byte(
static_cast< std::byte
>(value >> 8U));
471 auto write_u32 = [&](std::uint32_t value)
473 for (std::uint8_t index = 0U; index < 4U; ++index)
475 write_byte(
static_cast< std::byte
>(value >> (index * 8U)));
478 for (; first != last; ++first)
481 if (directory.size() == std::numeric_limits< std::uint16_t >::max())
489 if (entry.
path.size() > std::numeric_limits< std::uint16_t >::max() || entry.
data.size() > std::numeric_limits< std::uint32_t >::max())
494 constexpr std::uint16_t flags = 0x0808U;
495 std::uint32_t
const local_offset =
static_cast< std::uint32_t
>(archive_size);
497 write_u32(0x04034b50U);
506 write_u16(
static_cast< std::uint16_t
>(entry.
path.size()));
508 for (
char character : entry.
path)
510 write_byte(
static_cast< std::byte
>(
static_cast< std::uint8_t
>(character)));
515 for (std::byte value : entry.
data)
525 std::size_t
const compressed_size_value = counted.
size();
527 if (compressed_size_value > std::numeric_limits< std::uint32_t >::max())
531 archive_size += compressed_size_value;
532 write_u32(0x08074b50U);
534 write_u32(
static_cast< std::uint32_t
>(compressed_size_value));
535 write_u32(
static_cast< std::uint32_t
>(entry.
data.size()));
536 directory.push_back(
zip_detail::directory_record{entry.
path, checksum,
static_cast< std::uint32_t
>(compressed_size_value),
static_cast< std::uint32_t
>(entry.
data.size()), local_offset, method, flags});
539 std::uint32_t
const directory_offset =
static_cast< std::uint32_t
>(archive_size);
542 write_u32(0x02014b50U);
545 write_u16(record.flags);
546 write_u16(record.method);
549 write_u32(record.checksum);
550 write_u32(record.compressed_size);
551 write_u32(record.uncompressed_size);
552 write_u16(
static_cast< std::uint16_t
>(record.path.size()));
558 write_u32(record.local_header_offset);
559 for (
char character : record.path)
561 write_byte(
static_cast< std::byte
>(
static_cast< std::uint8_t
>(character)));
564 std::uint64_t
const directory_size = archive_size - directory_offset;
565 if (directory_size > std::numeric_limits< std::uint32_t >::max())
569 write_u32(0x06054b50U);
572 write_u16(
static_cast< std::uint16_t
>(directory.size()));
573 write_u16(
static_cast< std::uint16_t
>(directory.size()));
574 write_u32(
static_cast< std::uint32_t
>(directory_size));
575 write_u32(directory_offset);
596 template < std::input_iterator input_iterator, std::sentinel_for< input_iterator > sentinel,
typename output_iterator >
599 using input_value = std::remove_cv_t< std::iter_value_t< input_iterator > >;
600 static_assert(std::is_same_v< input_value, std::byte > || (std::is_integral_v< input_value > &&
sizeof(input_value) == 1U),
"ZIP input iterators must contain byte-sized values");
603 std::vector< std::byte > archive;
604 for (; first != last; ++first)
Exception raised for malformed streams, invalid options, and codec failures.
Assignment proxy returned by operator*.
assignment_proxy & operator=(std::byte value)
Forwards one std::byte assignment.
assignment_proxy(counting_output_iterator &owner) noexcept
Constructs a proxy for one counted iterator position.
assignment_proxy & operator=(std::uint8_t value)
Forwards one unsigned-byte assignment.
Output iterator that counts bytes while forwarding every assignment.
std::size_t size() const noexcept
Returns the number of forwarded byte assignments.
assignment_proxy operator*() noexcept
Returns an assignment proxy for the current position.
counting_output_iterator(output_iterator output)
Constructs a counter around an output iterator.
std::output_iterator_tag iterator_category
Iterator category advertised to generic algorithms.
std::ptrdiff_t difference_type
Signed distance type required by the output-iterator interface.
output_iterator take_output()
Releases the forwarded output iterator.
counting_output_iterator operator++(int) noexcept
Applies output-iterator post-increment semantics.
counting_output_iterator & operator++() noexcept
Advances after assignment.
Compression formats, options, errors, and buffer or iterator APIs.
Shared iterator, byte-conversion, and checksum primitives.
std::uint32_t crc32(std::span< std::byte const > input) noexcept
Computes the reflected IEEE CRC-32 used by gzip and ZIP.
void write_byte(output_iterator &output, std::byte value)
Writes one byte through an output iterator and advances it.
constexpr std::byte to_byte(value_type value) noexcept
Converts one supported iterator value to std::byte.
Internal ZIP32 parsing and output-iterator support.
bool path_is_safe(std::string const &path)
Tests whether a member path is confined to a relative extraction root.
output_iterator extract_archive(std::span< std::byte const > archive, output_iterator output, zip_extraction_options const &options)
Extracts an archive that has been materialized for random access.
Facilities for creating and decoding supported compressed streams.
zip_compression
Compression methods supported for individual ZIP members.
@ deflate
Encode the member as raw DEFLATE.
@ stored
Store the member without compression.
std::vector< std::byte > decompress(format stream_format, std::span< std::byte const > input, decompression_options const &options={})
Decompresses a contiguous byte buffer into an owning result.
@ deflate
Raw RFC 1951 DEFLATE stream.
@ zip
ZIP32 archive; use create_zip() and extract_zip().
std::vector< std::byte > create_zip(std::span< zip_entry const > entries)
Creates a deterministic ZIP32 archive from contiguous entries.
@ output_limit_exceeded
Decoding would exceed a configured resource limit.
@ invalid_option
An option or format value is outside its accepted range.
@ unsupported_feature
Valid input requires a format feature not implemented by the library.
@ invalid_data
The input does not conform to the selected format.
std::vector< std::byte > compress(format stream_format, std::span< std::byte const > input, compression_options const &options={})
Compresses a contiguous byte buffer into an owning result.
std::vector< zip_entry > extract_zip(std::span< std::byte const > archive, zip_extraction_options const &options={})
Extracts a contiguous ZIP32 archive into owning entries.
Resource and stream-validation policy for decompression operations.
std::size_t maximum_output_size
Maximum total number of bytes the operation may emit.
Directory metadata retained until the central directory is emitted.
std::uint32_t checksum
CRC-32 of the uncompressed member.
std::uint32_t local_header_offset
Byte offset of the corresponding local header.
std::uint32_t uncompressed_size
Decoded member size.
std::uint16_t method
ZIP compression-method identifier.
std::string path
Archive member path.
std::uint32_t compressed_size
Encoded member size.
std::uint16_t flags
General-purpose ZIP flags copied to the central directory.
An owning ZIP archive member.
std::vector< std::byte > data
Uncompressed member contents.
zip_compression compression
Method used when the entry is written to an archive.
std::string path
Relative, forward-slash-separated archive path.