RPNX::Compress
Self-contained C++20 compression and ZIP library
 
Loading...
Searching...
No Matches
zip.hpp
Go to the documentation of this file.
1#ifndef RPNX_COMPRESSION_ZIP_HPP
2#define RPNX_COMPRESSION_ZIP_HPP
3
6
7#include <algorithm>
8#include <cstddef>
9#include <cstdint>
10#include <iterator>
11#include <limits>
12#include <optional>
13#include <set>
14#include <span>
15#include <string>
16#include <string_view>
17#include <type_traits>
18#include <utility>
19#include <vector>
20
21/**
22 * @file
23 * @brief Deterministic ZIP32 archive creation and validated extraction APIs.
24 */
25
26namespace rpnx::compression
27{
28
29 /** @brief Compression methods supported for individual ZIP members. */
30 enum class zip_compression : std::uint8_t
31 {
32 stored, ///< Store the member without compression.
33 deflate ///< Encode the member as raw DEFLATE.
34 };
35
36 /**
37 * @brief An owning ZIP archive member.
38 *
39 * Paths use forward slashes and must be relative. Archive creation and
40 * extraction reject empty paths, absolute paths, drive-qualified paths,
41 * backslashes, embedded nulls, and parent-directory components.
42 */
43 struct zip_entry
44 {
45 /** @brief Relative, forward-slash-separated archive path. */
46 std::string path;
47
48 /** @brief Uncompressed member contents. */
49 std::vector< std::byte > data;
50
51 /** @brief Method used when the entry is written to an archive. */
53 };
54
55 /** @brief Resource limits applied while parsing a ZIP archive. */
57 {
58 /** @brief Maximum number of members accepted from one archive. */
59 std::size_t maximum_entry_count = 100000U;
60
61 /** @brief Maximum uncompressed size accepted for any one member. */
62 std::size_t maximum_entry_size = 1024U * 1024U * 1024U;
63
64 /** @brief Maximum combined uncompressed size accepted for all members. */
65 std::size_t maximum_total_size = std::size_t{4U} * 1024U * 1024U * 1024U;
66 };
67
68 /**
69 * @brief Creates a deterministic ZIP32 archive from contiguous entries.
70 * @param entries Members to write in archive order. The span is only borrowed
71 * for this call.
72 * @return The complete ZIP32 archive.
73 * @throws compression_error If a path is unsafe or duplicated, compression
74 * fails, or ZIP64 would be required.
75 *
76 * The writer emits UTF-8 path flags, fixed timestamps, data descriptors, and
77 * no extra fields or archive comment. Identical entries therefore produce
78 * identical bytes.
79 */
80 [[nodiscard]] std::vector< std::byte > create_zip(std::span< zip_entry const > entries);
81
82 /**
83 * @brief Extracts a contiguous ZIP32 archive into owning entries.
84 * @param archive Complete ZIP archive. The span is only borrowed for this call.
85 * @param options Member-count and uncompressed-size limits.
86 * @return Owning entries in central-directory order. Extracted entries record
87 * the method declared by the archive.
88 * @throws compression_error If the archive is malformed, unsupported, unsafe,
89 * duplicated, fails checksum validation, or exceeds a configured limit.
90 */
91 [[nodiscard]] std::vector< zip_entry > extract_zip(std::span< std::byte const > archive, zip_extraction_options const& options = {});
92
93 /** @brief Internal ZIP32 parsing and output-iterator support. */
94 namespace zip_detail
95 {
96
97 /** Directory metadata retained until the central directory is emitted. */
99 {
100 /** @brief Archive member path. */
101 std::string path;
102
103 /** @brief CRC-32 of the uncompressed member. */
104 std::uint32_t checksum;
105
106 /** @brief Encoded member size. */
107 std::uint32_t compressed_size;
108
109 /** @brief Decoded member size. */
110 std::uint32_t uncompressed_size;
111
112 /** @brief Byte offset of the corresponding local header. */
113 std::uint32_t local_header_offset;
114
115 /** @brief ZIP compression-method identifier. */
116 std::uint16_t method;
117
118 /** @brief General-purpose ZIP flags copied to the central directory. */
119 std::uint16_t flags;
120 };
121
122 /**
123 * @brief Tests whether a member path is confined to a relative extraction root.
124 * @param path Path to validate using ZIP's forward-slash convention.
125 * @return true if the non-empty path is relative and contains no parent,
126 * drive, backslash, or embedded-null components.
127 */
128 [[nodiscard]] inline bool path_is_safe(std::string const& path)
129 {
130 if (path.empty() || path.front() == '/' || path.front() == '\\' || path.find('\0') != std::string::npos || path.find('\\') != std::string::npos)
131 {
132 return false;
133 }
134 std::size_t segment_begin = 0U;
135 while (segment_begin <= path.size())
136 {
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))
141 {
142 return false;
143 }
144 if (segment_end == std::string::npos)
145 {
146 break;
147 }
148 segment_begin = segment_end + 1U;
149 }
150 return true;
151 }
152
153 /** Output iterator that counts bytes while forwarding every assignment. */
154 template < typename output_iterator >
156 {
157 public:
158 /** Assignment proxy returned by operator*. */
160 {
161 public:
162 /**
163 * @brief Constructs a proxy for one counted iterator position.
164 * @param owner Counting iterator that receives assignments.
165 */
166 explicit assignment_proxy(counting_output_iterator& owner) noexcept : m_owner(owner)
167 {
168 }
169
170 /**
171 * @brief Forwards one std::byte assignment.
172 * @param value Byte to write.
173 * @return This proxy after the write has been counted.
174 */
175 assignment_proxy& operator=(std::byte value)
176 {
177 implementation::write_byte(m_owner.m_output, value);
178 ++m_owner.m_size;
179 return *this;
180 }
181
182 /**
183 * @brief Forwards one unsigned-byte assignment.
184 * @param value Byte to write.
185 * @return This proxy after the write has been counted.
186 */
187 assignment_proxy& operator=(std::uint8_t value)
188 {
189 return *this = static_cast< std::byte >(value);
190 }
191
192 private:
194 };
195
196 /** @brief Signed distance type required by the output-iterator interface. */
197 using difference_type = std::ptrdiff_t;
198
199 /** @brief Iterator category advertised to generic algorithms. */
200 using iterator_category = std::output_iterator_tag;
201
202 /**
203 * @brief Constructs a counter around an output iterator.
204 * @param output Destination iterator to own and forward to.
205 */
206 explicit counting_output_iterator(output_iterator output) : m_output(std::move(output))
207 {
208 }
209
210 /**
211 * @brief Returns an assignment proxy for the current position.
212 * @return Proxy that forwards and counts one assignment.
213 */
214 [[nodiscard]] assignment_proxy operator*() noexcept
215 {
216 return assignment_proxy(*this);
217 }
218
219 /**
220 * @brief Advances after assignment.
221 * @return This iterator; the underlying iterator was already advanced by assignment.
222 */
224 {
225 return *this;
226 }
227
228 /**
229 * @brief Applies output-iterator post-increment semantics.
230 * @return A copy referring to the same already-advanced destination state.
231 */
233 {
234 return *this;
235 }
236
237 /**
238 * @brief Releases the forwarded output iterator.
239 * @return The owned iterator at its current output position.
240 */
241 [[nodiscard]] output_iterator take_output()
242 {
243 return std::move(m_output);
244 }
245
246 /**
247 * @brief Returns the number of forwarded byte assignments.
248 * @return Encoded byte count since construction.
249 */
250 [[nodiscard]] std::size_t size() const noexcept
251 {
252 return m_size;
253 }
254
255 private:
256 output_iterator m_output;
257 std::size_t m_size = 0U;
258 };
259
260 /**
261 * @brief Extracts an archive that has been materialized for random access.
262 * @tparam output_iterator Output iterator accepting zip_entry values.
263 * @param archive Complete archive image.
264 * @param output Destination iterator, taken and returned by value.
265 * @param options Member-count and uncompressed-size limits.
266 * @return The destination iterator advanced past the final extracted member.
267 * @throws compression_error If structure, paths, sizes, methods, or checksums
268 * fail validation, or extraction exceeds a configured limit.
269 */
270 template < typename output_iterator >
271 output_iterator extract_archive(std::span< std::byte const > archive, output_iterator output, zip_extraction_options const& options)
272 {
273 auto read_u16 = [&](std::size_t offset)
274 {
275 if (offset > archive.size() || archive.size() - offset < 2U)
276 {
277 throw compression_error(error_code::invalid_data, format::zip, "truncated ZIP integer field");
278 }
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));
280 };
281 auto read_u32 = [&](std::size_t offset)
282 {
283 if (offset > archive.size() || archive.size() - offset < 4U)
284 {
285 throw compression_error(error_code::invalid_data, format::zip, "truncated ZIP integer field");
286 }
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);
288 };
289 if (archive.size() < 22U)
290 {
291 throw compression_error(error_code::invalid_data, format::zip, "ZIP archive is too short");
292 }
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)
296 {
297 if (read_u32(offset) == 0x06054b50U)
298 {
299 eocd_offset = offset;
300 break;
301 }
302 if (offset == search_begin)
303 {
304 break;
305 }
306 }
307 if (!eocd_offset.has_value())
308 {
309 throw compression_error(error_code::invalid_data, format::zip, "ZIP end-of-central-directory record is missing");
310 }
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))
313 {
314 throw compression_error(error_code::unsupported_feature, format::zip, "multi-disk ZIP archives are not supported");
315 }
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)
321 {
322 throw compression_error(error_code::invalid_data, format::zip, "invalid ZIP central-directory bounds");
323 }
324 if (entry_count > options.maximum_entry_count)
325 {
326 throw compression_error(error_code::output_limit_exceeded, format::zip, "ZIP entry count exceeds configured limit");
327 }
328
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)
333 {
334 if (read_u32(cursor) != 0x02014b50U || cursor > archive.size() || archive.size() - cursor < 46U)
335 {
336 throw compression_error(error_code::invalid_data, format::zip, "invalid ZIP central-directory entry");
337 }
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))
349 {
350 throw compression_error(error_code::unsupported_feature, format::zip, "encrypted or unknown ZIP method");
351 }
352 if (cursor > archive.size() || central_record_size > archive.size() - cursor)
353 {
354 throw compression_error(error_code::invalid_data, format::zip, "truncated ZIP central-directory entry");
355 }
356 std::string path;
357 path.reserve(name_size);
358 for (std::size_t index = 0U; index < name_size; ++index)
359 {
360 path.push_back(static_cast< char >(std::to_integer< std::uint8_t >(archive[cursor + 46U + index])));
361 }
362 if (!path_is_safe(path) || !paths.insert(path).second)
363 {
364 throw compression_error(error_code::invalid_data, format::zip, "ZIP contains an unsafe or duplicate path");
365 }
366 if (uncompressed_size > options.maximum_entry_size || total_size > options.maximum_total_size || uncompressed_size > options.maximum_total_size - total_size)
367 {
368 throw compression_error(error_code::output_limit_exceeded, format::zip, "ZIP output exceeds configured limit");
369 }
370 if (read_u32(local_offset) != 0x04034b50U || local_offset > archive.size() || archive.size() - local_offset < 30U)
371 {
372 throw compression_error(error_code::invalid_data, format::zip, "invalid ZIP local header");
373 }
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))))
383 {
384 throw compression_error(error_code::invalid_data, format::zip, "ZIP local and central headers disagree");
385 }
386 for (std::size_t index = 0U; index < name_size; ++index)
387 {
388 if (archive[local_offset + 30U + index] != archive[cursor + 46U + index])
389 {
390 throw compression_error(error_code::invalid_data, format::zip, "ZIP local and central paths disagree");
391 }
392 }
393 if (uses_descriptor)
394 {
395 std::size_t descriptor = data_offset + compressed_size;
396 if (read_u32(descriptor) == 0x08074b50U)
397 {
398 descriptor += 4U;
399 }
400 if (read_u32(descriptor) != expected_checksum || read_u32(descriptor + 4U) != compressed_size || read_u32(descriptor + 8U) != uncompressed_size)
401 {
402 throw compression_error(error_code::invalid_data, format::zip, "invalid ZIP data descriptor");
403 }
404 }
405
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);
409 if (method == 0U)
410 {
411 data.assign(encoded.begin(), encoded.end());
412 }
413 else
414 {
416 limits.maximum_output_size = uncompressed_size;
417 rpnx::compression::decompress(format::deflate, encoded.begin(), encoded.end(), std::back_inserter(data), limits);
418 }
419 if (data.size() != uncompressed_size || deflate_codec::crc32(data) != expected_checksum)
420 {
421 throw compression_error(error_code::invalid_data, format::zip, "ZIP member size or checksum is invalid");
422 }
423 *output = zip_entry{std::move(path), std::move(data), method == 0U ? zip_compression::stored : zip_compression::deflate};
424 ++output;
425 total_size += uncompressed_size;
426 cursor += central_record_size;
427 }
428 if (cursor != directory_offset + directory_size)
429 {
430 throw compression_error(error_code::invalid_data, format::zip, "ZIP central-directory size does not match its entries");
431 }
432 return output;
433 }
434
435 } // namespace zip_detail
436
437 /**
438 * @brief Creates a deterministic ZIP32 archive from an iterator range.
439 * @tparam input_iterator Single-pass iterator whose value type is zip_entry.
440 * @tparam sentinel Sentinel for @p first.
441 * @tparam output_iterator Output iterator accepting std::byte or std::uint8_t assignments.
442 * @param first Iterator to the first member.
443 * @param last Sentinel past the final member.
444 * @param output Destination iterator, taken and returned by value.
445 * @return The destination iterator advanced past the end-of-central-directory record.
446 * @throws compression_error If a member is invalid or ZIP64 would be required.
447 *
448 * Only central-directory metadata is retained; member data is consumed and
449 * emitted in archive order.
450 */
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)
453 {
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)
458 {
459 implementation::write_byte(output, value);
460 ++archive_size;
461 if (archive_size > std::numeric_limits< std::uint32_t >::max())
462 {
463 throw compression_error(error_code::unsupported_feature, format::zip, "ZIP64 archive creation is not supported");
464 }
465 };
466 auto write_u16 = [&](std::uint16_t value)
467 {
468 write_byte(static_cast< std::byte >(value));
469 write_byte(static_cast< std::byte >(value >> 8U));
470 };
471 auto write_u32 = [&](std::uint32_t value)
472 {
473 for (std::uint8_t index = 0U; index < 4U; ++index)
474 {
475 write_byte(static_cast< std::byte >(value >> (index * 8U)));
476 }
477 };
478 for (; first != last; ++first)
479 {
480 zip_entry const& entry = *first;
481 if (directory.size() == std::numeric_limits< std::uint16_t >::max())
482 {
483 throw compression_error(error_code::unsupported_feature, format::zip, "ZIP64 is required for more than 65535 entries");
484 }
485 if (!zip_detail::path_is_safe(entry.path) || !paths.insert(entry.path).second)
486 {
487 throw compression_error(error_code::invalid_option, format::zip, "ZIP entry path is unsafe or duplicated");
488 }
489 if (entry.path.size() > std::numeric_limits< std::uint16_t >::max() || entry.data.size() > std::numeric_limits< std::uint32_t >::max())
490 {
491 throw compression_error(error_code::unsupported_feature, format::zip, "ZIP64 archive creation is not supported");
492 }
493 std::uint16_t const method = entry.compression == zip_compression::stored ? 0U : 8U;
494 constexpr std::uint16_t flags = 0x0808U;
495 std::uint32_t const local_offset = static_cast< std::uint32_t >(archive_size);
496 std::uint32_t const checksum = deflate_codec::crc32(entry.data);
497 write_u32(0x04034b50U);
498 write_u16(20U);
499 write_u16(flags);
500 write_u16(method);
501 write_u16(0U);
502 write_u16(0x0021U);
503 write_u32(0U);
504 write_u32(0U);
505 write_u32(0U);
506 write_u16(static_cast< std::uint16_t >(entry.path.size()));
507 write_u16(0U);
508 for (char character : entry.path)
509 {
510 write_byte(static_cast< std::byte >(static_cast< std::uint8_t >(character)));
511 }
513 if (method == 0U)
514 {
515 for (std::byte value : entry.data)
516 {
517 *counted = value;
518 ++counted;
519 }
520 }
521 else
522 {
523 counted = rpnx::compression::compress(format::deflate, entry.data.begin(), entry.data.end(), std::move(counted));
524 }
525 std::size_t const compressed_size_value = counted.size();
526 output = counted.take_output();
527 if (compressed_size_value > std::numeric_limits< std::uint32_t >::max())
528 {
529 throw compression_error(error_code::unsupported_feature, format::zip, "ZIP64 archive creation is not supported");
530 }
531 archive_size += compressed_size_value;
532 write_u32(0x08074b50U);
533 write_u32(checksum);
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});
537 }
538
539 std::uint32_t const directory_offset = static_cast< std::uint32_t >(archive_size);
540 for (zip_detail::directory_record const& record : directory)
541 {
542 write_u32(0x02014b50U);
543 write_u16(20U);
544 write_u16(20U);
545 write_u16(record.flags);
546 write_u16(record.method);
547 write_u16(0U);
548 write_u16(0x0021U);
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()));
553 write_u16(0U);
554 write_u16(0U);
555 write_u16(0U);
556 write_u16(0U);
557 write_u32(0U);
558 write_u32(record.local_header_offset);
559 for (char character : record.path)
560 {
561 write_byte(static_cast< std::byte >(static_cast< std::uint8_t >(character)));
562 }
563 }
564 std::uint64_t const directory_size = archive_size - directory_offset;
565 if (directory_size > std::numeric_limits< std::uint32_t >::max())
566 {
567 throw compression_error(error_code::unsupported_feature, format::zip, "ZIP64 archive creation is not supported");
568 }
569 write_u32(0x06054b50U);
570 write_u16(0U);
571 write_u16(0U);
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);
576 write_u16(0U);
577 return output;
578 }
579
580 /**
581 * @brief Extracts validated ZIP members through an STL output iterator.
582 * @tparam input_iterator Single-pass iterator whose value type is std::byte or a one-byte integral type.
583 * @tparam sentinel Sentinel for @p first.
584 * @tparam output_iterator Output iterator accepting zip_entry values.
585 * @param first Iterator to the first archive byte.
586 * @param last Sentinel past the final archive byte.
587 * @param output Destination iterator, taken and returned by value.
588 * @param options Member-count and uncompressed-size limits.
589 * @return The destination iterator advanced past the final extracted member.
590 * @throws compression_error If the archive is malformed, unsupported, unsafe,
591 * duplicated, fails checksum validation, or exceeds a configured limit.
592 *
593 * ZIP extraction materializes the archive because its trailing central
594 * directory points backward to local headers.
595 */
596 template < std::input_iterator input_iterator, std::sentinel_for< input_iterator > sentinel, typename output_iterator >
597 output_iterator extract_zip(input_iterator first, sentinel last, output_iterator output, zip_extraction_options const& options = {})
598 {
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");
601 // A single-pass ZIP reader must retain the archive because the central
602 // directory follows member data and points backward to local headers.
603 std::vector< std::byte > archive;
604 for (; first != last; ++first)
605 {
606 archive.push_back(implementation::to_byte(*first));
607 }
608 return zip_detail::extract_archive(archive, std::move(output), options);
609 }
610
611} // namespace rpnx::compression
612
613#endif
Exception raised for malformed streams, invalid options, and codec failures.
assignment_proxy & operator=(std::byte value)
Forwards one std::byte assignment.
Definition zip.hpp:175
assignment_proxy(counting_output_iterator &owner) noexcept
Constructs a proxy for one counted iterator position.
Definition zip.hpp:166
assignment_proxy & operator=(std::uint8_t value)
Forwards one unsigned-byte assignment.
Definition zip.hpp:187
Output iterator that counts bytes while forwarding every assignment.
Definition zip.hpp:156
std::size_t size() const noexcept
Returns the number of forwarded byte assignments.
Definition zip.hpp:250
assignment_proxy operator*() noexcept
Returns an assignment proxy for the current position.
Definition zip.hpp:214
counting_output_iterator(output_iterator output)
Constructs a counter around an output iterator.
Definition zip.hpp:206
std::output_iterator_tag iterator_category
Iterator category advertised to generic algorithms.
Definition zip.hpp:200
std::ptrdiff_t difference_type
Signed distance type required by the output-iterator interface.
Definition zip.hpp:197
output_iterator take_output()
Releases the forwarded output iterator.
Definition zip.hpp:241
counting_output_iterator operator++(int) noexcept
Applies output-iterator post-increment semantics.
Definition zip.hpp:232
counting_output_iterator & operator++() noexcept
Advances after assignment.
Definition zip.hpp:223
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.
Definition deflate.hpp:183
void write_byte(output_iterator &output, std::byte value)
Writes one byte through an output iterator and advances it.
Definition io.hpp:48
constexpr std::byte to_byte(value_type value) noexcept
Converts one supported iterator value to std::byte.
Definition io.hpp:27
Internal ZIP32 parsing and output-iterator support.
Definition zip.hpp:95
bool path_is_safe(std::string const &path)
Tests whether a member path is confined to a relative extraction root.
Definition zip.hpp:128
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.
Definition zip.hpp:271
Facilities for creating and decoding supported compressed streams.
zip_compression
Compression methods supported for individual ZIP members.
Definition zip.hpp:31
@ deflate
Encode the member as raw DEFLATE.
Definition zip.hpp:33
@ stored
Store the member without compression.
Definition zip.hpp:32
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.
Definition zip.hpp:99
std::uint32_t checksum
CRC-32 of the uncompressed member.
Definition zip.hpp:104
std::uint32_t local_header_offset
Byte offset of the corresponding local header.
Definition zip.hpp:113
std::uint32_t uncompressed_size
Decoded member size.
Definition zip.hpp:110
std::uint16_t method
ZIP compression-method identifier.
Definition zip.hpp:116
std::string path
Archive member path.
Definition zip.hpp:101
std::uint32_t compressed_size
Encoded member size.
Definition zip.hpp:107
std::uint16_t flags
General-purpose ZIP flags copied to the central directory.
Definition zip.hpp:119
An owning ZIP archive member.
Definition zip.hpp:44
std::vector< std::byte > data
Uncompressed member contents.
Definition zip.hpp:49
zip_compression compression
Method used when the entry is written to an archive.
Definition zip.hpp:52
std::string path
Relative, forward-slash-separated archive path.
Definition zip.hpp:46
Resource limits applied while parsing a ZIP archive.
Definition zip.hpp:57
std::size_t maximum_entry_size
Maximum uncompressed size accepted for any one member.
Definition zip.hpp:62
std::size_t maximum_entry_count
Maximum number of members accepted from one archive.
Definition zip.hpp:59
std::size_t maximum_total_size
Maximum combined uncompressed size accepted for all members.
Definition zip.hpp:65