omnizip 0.4.7-x86_64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +32 -0
- data/.rubocop_todo.yml +293 -0
- data/CHANGELOG.md +1647 -0
- data/CONTEXT.md +68 -0
- data/COPYING +502 -0
- data/Gemfile +19 -0
- data/LICENSE +12 -0
- data/README.adoc +1327 -0
- data/Rakefile +15 -0
- data/TODO.refactor/00-overview.md +67 -0
- data/TODO.refactor/01-registry-base-class.md +81 -0
- data/TODO.refactor/02-error-hierarchy.md +57 -0
- data/TODO.refactor/03-autoload-entry-point.md +69 -0
- data/TODO.refactor/04-filter-base-consolidation.md +49 -0
- data/TODO.refactor/05-send-private-methods.md +64 -0
- data/TODO.refactor/06-instance-variable-access.md +69 -0
- data/TODO.refactor/07-respond-to-replacement.md +237 -0
- data/TODO.refactor/08-spec-doubles.md +27 -0
- data/TODO.refactor/09-cli-shared-module.md +35 -0
- data/TODO.refactor/10-format-detector-ocp.md +34 -0
- data/TODO.refactor/11-convenience-decoupling.md +38 -0
- data/TODO.refactor/12-thread-safety.md +8 -0
- data/TODO.refactor/13-lutaml-model-migration.md +158 -0
- data/TODO.refactor/14-add-missing-specs.md +25 -0
- data/benchmark/README.md +260 -0
- data/benchmark/benchmark_suite.rb +125 -0
- data/benchmark/compression_bench.rb +181 -0
- data/benchmark/filter_bench.rb +180 -0
- data/benchmark/models/benchmark_result.rb +59 -0
- data/benchmark/models/comparison_result.rb +69 -0
- data/benchmark/profile_suite.rb +167 -0
- data/benchmark/reporter.rb +150 -0
- data/benchmark/run_benchmarks.rb +66 -0
- data/benchmark/test_data.rb +137 -0
- data/config/formats/rar3_spec.yml +92 -0
- data/config/formats/rar5_spec.yml +102 -0
- data/docs/.github/workflows/docs.yml +142 -0
- data/docs/.gitignore +21 -0
- data/docs/.lychee.toml +67 -0
- data/docs/Gemfile +13 -0
- data/docs/RAR_WRITE_SUPPORT.md +26 -0
- data/docs/README.md +101 -0
- data/docs/_config.yml +112 -0
- data/docs/assets/logo.svg +1 -0
- data/docs/assets/omnizip-logo.pdf +1540 -11
- data/docs/comparison/feature-matrix.adoc +694 -0
- data/docs/comparison/index.adoc +113 -0
- data/docs/comparison/vs-7zip.adoc +309 -0
- data/docs/comparison/vs-peazip.adoc +77 -0
- data/docs/comparison/vs-rubyzip.adoc +342 -0
- data/docs/comparison/vs-winrar.adoc +100 -0
- data/docs/compatibility.adoc +579 -0
- data/docs/concepts/index.adoc +129 -0
- data/docs/developer/architecture.adoc +256 -0
- data/docs/developer/contributing.adoc +158 -0
- data/docs/developer/index.adoc +25 -0
- data/docs/developer/testing.adoc +212 -0
- data/docs/getting-started/basic-usage.adoc +263 -0
- data/docs/getting-started/index.adoc +42 -0
- data/docs/getting-started/installation.adoc +138 -0
- data/docs/getting-started/quick-start.adoc +183 -0
- data/docs/getting-started/your-first-archive.adoc +219 -0
- data/docs/guides/advanced-features/encryption.adoc +277 -0
- data/docs/guides/advanced-features/index.adoc +59 -0
- data/docs/guides/advanced-features/parallel-processing.adoc +144 -0
- data/docs/guides/advanced-features/progress-tracking.adoc +124 -0
- data/docs/guides/advanced-features/streaming.adoc +142 -0
- data/docs/guides/archive-formats/gzip-format.adoc +105 -0
- data/docs/guides/archive-formats/index.adoc +160 -0
- data/docs/guides/archive-formats/ole-format.adoc +151 -0
- data/docs/guides/archive-formats/rar-format.adoc +104 -0
- data/docs/guides/archive-formats/rar5.adoc +521 -0
- data/docs/guides/archive-formats/rpm-format.adoc +108 -0
- data/docs/guides/archive-formats/seven-zip-format.adoc +35 -0
- data/docs/guides/archive-formats/tar-format.adoc +106 -0
- data/docs/guides/archive-formats/xz-format.adoc +118 -0
- data/docs/guides/archive-formats/zip-format.adoc +35 -0
- data/docs/guides/compression-algorithms/bzip2.adoc +114 -0
- data/docs/guides/compression-algorithms/deflate.adoc +313 -0
- data/docs/guides/compression-algorithms/index.adoc +190 -0
- data/docs/guides/compression-algorithms/lzma.adoc +394 -0
- data/docs/guides/compression-algorithms/lzma2.adoc +244 -0
- data/docs/guides/compression-algorithms/ppmd.adoc +313 -0
- data/docs/guides/compression-algorithms/zstandard.adoc +296 -0
- data/docs/guides/creating-archives.adoc +348 -0
- data/docs/guides/extracting-archives.adoc +53 -0
- data/docs/guides/format-conversion.adoc +64 -0
- data/docs/guides/index.adoc +49 -0
- data/docs/guides/migration-rubyzip.adoc +217 -0
- data/docs/guides/parity-archives.adoc +605 -0
- data/docs/guides/performance-tuning.adoc +88 -0
- data/docs/index.adoc +228 -0
- data/docs/lychee.toml +67 -0
- data/docs/reference/api/overview.adoc +187 -0
- data/docs/reference/cli/compress-command.adoc +114 -0
- data/docs/reference/cli/overview.adoc +140 -0
- data/docs/reference/index.adoc +26 -0
- data/docs/resources/faq.adoc +185 -0
- data/docs/resources/quick-reference.adoc +222 -0
- data/docs/rust-backend.md +0 -0
- data/docs/troubleshooting/index.adoc +207 -0
- data/docs/xar_format.md +216 -0
- data/examples/api_comparison.rb +205 -0
- data/examples/deflate64_example.rb +96 -0
- data/examples/par2_demo.rb +121 -0
- data/examples/quick_start_native.rb +150 -0
- data/examples/quick_start_rubyzip.rb +115 -0
- data/examples/rubyzip_compatibility_demo.rb +194 -0
- data/exe/omnizip +27 -0
- data/lib/omnizip/algorithm.rb +176 -0
- data/lib/omnizip/algorithm_registry.rb +65 -0
- data/lib/omnizip/algorithms/.keep +0 -0
- data/lib/omnizip/algorithms/bzip2/bwt.rb +236 -0
- data/lib/omnizip/algorithms/bzip2/bz2.rb +687 -0
- data/lib/omnizip/algorithms/bzip2/rle.rb +146 -0
- data/lib/omnizip/algorithms/bzip2.rb +108 -0
- data/lib/omnizip/algorithms/deflate/constants.rb +28 -0
- data/lib/omnizip/algorithms/deflate/decoder.rb +37 -0
- data/lib/omnizip/algorithms/deflate/encoder.rb +45 -0
- data/lib/omnizip/algorithms/deflate.rb +135 -0
- data/lib/omnizip/algorithms/deflate64/constants.rb +45 -0
- data/lib/omnizip/algorithms/deflate64/decoder.rb +151 -0
- data/lib/omnizip/algorithms/deflate64/encoder.rb +95 -0
- data/lib/omnizip/algorithms/deflate64/huffman_coder.rb +352 -0
- data/lib/omnizip/algorithms/deflate64/lz77_encoder.rb +140 -0
- data/lib/omnizip/algorithms/deflate64.rb +118 -0
- data/lib/omnizip/algorithms/lzma/bit_model.rb +120 -0
- data/lib/omnizip/algorithms/lzma/constants.rb +112 -0
- data/lib/omnizip/algorithms/lzma/decoder.rb +146 -0
- data/lib/omnizip/algorithms/lzma/dictionary.rb +70 -0
- data/lib/omnizip/algorithms/lzma/distance_coder.rb +326 -0
- data/lib/omnizip/algorithms/lzma/encoder.rb +139 -0
- data/lib/omnizip/algorithms/lzma/length_coder.rb +172 -0
- data/lib/omnizip/algorithms/lzma/literal_decoder.rb +204 -0
- data/lib/omnizip/algorithms/lzma/literal_encoder.rb +208 -0
- data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +368 -0
- data/lib/omnizip/algorithms/lzma/lzma1_encoder.rb +105 -0
- data/lib/omnizip/algorithms/lzma/lzma_alone_decoder.rb +194 -0
- data/lib/omnizip/algorithms/lzma/lzma_state.rb +128 -0
- data/lib/omnizip/algorithms/lzma/match.rb +32 -0
- data/lib/omnizip/algorithms/lzma/match_finder.rb +233 -0
- data/lib/omnizip/algorithms/lzma/match_finder_config.rb +142 -0
- data/lib/omnizip/algorithms/lzma/match_finder_factory.rb +84 -0
- data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +159 -0
- data/lib/omnizip/algorithms/lzma/probability_models.rb +72 -0
- data/lib/omnizip/algorithms/lzma/range_coder.rb +82 -0
- data/lib/omnizip/algorithms/lzma/range_decoder.rb +274 -0
- data/lib/omnizip/algorithms/lzma/range_encoder.rb +202 -0
- data/lib/omnizip/algorithms/lzma/state.rb +127 -0
- data/lib/omnizip/algorithms/lzma/xz_buffered_range_encoder.rb +323 -0
- data/lib/omnizip/algorithms/lzma/xz_encoder.rb +420 -0
- data/lib/omnizip/algorithms/lzma/xz_encoder_fast.rb +640 -0
- data/lib/omnizip/algorithms/lzma/xz_match_finder_adapter.rb +224 -0
- data/lib/omnizip/algorithms/lzma/xz_price_calculator.rb +167 -0
- data/lib/omnizip/algorithms/lzma/xz_probability_models.rb +258 -0
- data/lib/omnizip/algorithms/lzma/xz_range_encoder.rb +223 -0
- data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +314 -0
- data/lib/omnizip/algorithms/lzma/xz_state.rb +108 -0
- data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +1311 -0
- data/lib/omnizip/algorithms/lzma.rb +266 -0
- data/lib/omnizip/algorithms/lzma2/chunk_manager.rb +180 -0
- data/lib/omnizip/algorithms/lzma2/constants.rb +38 -0
- data/lib/omnizip/algorithms/lzma2/encoder.rb +145 -0
- data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +165 -0
- data/lib/omnizip/algorithms/lzma2/properties.rb +179 -0
- data/lib/omnizip/algorithms/lzma2/simple_lzma2_encoder.rb +122 -0
- data/lib/omnizip/algorithms/lzma2/xz_encoder_adapter.rb +85 -0
- data/lib/omnizip/algorithms/lzma2.rb +133 -0
- data/lib/omnizip/algorithms/ppmd7/constants.rb +74 -0
- data/lib/omnizip/algorithms/ppmd7/context.rb +151 -0
- data/lib/omnizip/algorithms/ppmd7/decoder.rb +127 -0
- data/lib/omnizip/algorithms/ppmd7/encoder.rb +153 -0
- data/lib/omnizip/algorithms/ppmd7/model.rb +245 -0
- data/lib/omnizip/algorithms/ppmd7/symbol_state.rb +57 -0
- data/lib/omnizip/algorithms/ppmd7.rb +146 -0
- data/lib/omnizip/algorithms/ppmd8/constants.rb +61 -0
- data/lib/omnizip/algorithms/ppmd8/context.rb +32 -0
- data/lib/omnizip/algorithms/ppmd8/decoder.rb +104 -0
- data/lib/omnizip/algorithms/ppmd8/encoder.rb +135 -0
- data/lib/omnizip/algorithms/ppmd8/model.rb +246 -0
- data/lib/omnizip/algorithms/ppmd8/restoration_method.rb +78 -0
- data/lib/omnizip/algorithms/ppmd8.rb +122 -0
- data/lib/omnizip/algorithms/ppmd_base.rb +142 -0
- data/lib/omnizip/algorithms/registration.rb +28 -0
- data/lib/omnizip/algorithms/zstandard/constants.rb +158 -0
- data/lib/omnizip/algorithms/zstandard/decoder.rb +249 -0
- data/lib/omnizip/algorithms/zstandard/dictionary.rb +73 -0
- data/lib/omnizip/algorithms/zstandard/encoder.rb +294 -0
- data/lib/omnizip/algorithms/zstandard/frame/block.rb +143 -0
- data/lib/omnizip/algorithms/zstandard/frame/header.rb +233 -0
- data/lib/omnizip/algorithms/zstandard/frame.rb +33 -0
- data/lib/omnizip/algorithms/zstandard/fse/bitstream.rb +183 -0
- data/lib/omnizip/algorithms/zstandard/fse/encoder.rb +534 -0
- data/lib/omnizip/algorithms/zstandard/fse/interleaved.rb +92 -0
- data/lib/omnizip/algorithms/zstandard/fse/table.rb +188 -0
- data/lib/omnizip/algorithms/zstandard/fse/table_description.rb +203 -0
- data/lib/omnizip/algorithms/zstandard/fse.rb +53 -0
- data/lib/omnizip/algorithms/zstandard/huffman.rb +276 -0
- data/lib/omnizip/algorithms/zstandard/huffman_encoder.rb +327 -0
- data/lib/omnizip/algorithms/zstandard/ldm.rb +121 -0
- data/lib/omnizip/algorithms/zstandard/literals.rb +241 -0
- data/lib/omnizip/algorithms/zstandard/literals_encoder.rb +96 -0
- data/lib/omnizip/algorithms/zstandard/match_finder.rb +483 -0
- data/lib/omnizip/algorithms/zstandard/sequences.rb +350 -0
- data/lib/omnizip/algorithms/zstandard/sequences_encoder.rb +333 -0
- data/lib/omnizip/algorithms/zstandard/xxhash.rb +159 -0
- data/lib/omnizip/algorithms/zstandard.rb +210 -0
- data/lib/omnizip/algorithms.rb +35 -0
- data/lib/omnizip/archive/builder.rb +48 -0
- data/lib/omnizip/archive/reader_session.rb +67 -0
- data/lib/omnizip/archive.rb +86 -0
- data/lib/omnizip/archive_handler.rb +82 -0
- data/lib/omnizip/archive_handlers/cpio_handler.rb +47 -0
- data/lib/omnizip/archive_handlers/iso_handler.rb +54 -0
- data/lib/omnizip/archive_handlers/ole_handler.rb +37 -0
- data/lib/omnizip/archive_handlers/rar_handler.rb +96 -0
- data/lib/omnizip/archive_handlers/rpm_handler.rb +45 -0
- data/lib/omnizip/archive_handlers/seven_zip_handler.rb +89 -0
- data/lib/omnizip/archive_handlers/tar_handler.rb +75 -0
- data/lib/omnizip/archive_handlers/xar_handler.rb +49 -0
- data/lib/omnizip/archive_handlers/zip_handler.rb +115 -0
- data/lib/omnizip/archive_handlers.rb +18 -0
- data/lib/omnizip/backends.rb +160 -0
- data/lib/omnizip/buffer/memory_archive.rb +246 -0
- data/lib/omnizip/buffer/memory_extractor.rb +234 -0
- data/lib/omnizip/buffer/seven_zip_bridge.rb +171 -0
- data/lib/omnizip/buffer.rb +187 -0
- data/lib/omnizip/checksum_registry.rb +41 -0
- data/lib/omnizip/checksums/crc32.rb +101 -0
- data/lib/omnizip/checksums/crc64.rb +102 -0
- data/lib/omnizip/checksums/crc_base.rb +158 -0
- data/lib/omnizip/checksums/registration.rb +16 -0
- data/lib/omnizip/checksums/verifier.rb +130 -0
- data/lib/omnizip/checksums.rb +11 -0
- data/lib/omnizip/chunked/memory_manager.rb +194 -0
- data/lib/omnizip/chunked/reader.rb +78 -0
- data/lib/omnizip/chunked/writer.rb +123 -0
- data/lib/omnizip/chunked.rb +123 -0
- data/lib/omnizip/cli/output_formatter.rb +112 -0
- data/lib/omnizip/cli.rb +604 -0
- data/lib/omnizip/commands/.keep +0 -0
- data/lib/omnizip/commands/archive_create_command.rb +245 -0
- data/lib/omnizip/commands/archive_extract_command.rb +231 -0
- data/lib/omnizip/commands/archive_list_command.rb +204 -0
- data/lib/omnizip/commands/archive_repair_command.rb +129 -0
- data/lib/omnizip/commands/archive_verify_command.rb +115 -0
- data/lib/omnizip/commands/compress_command.rb +115 -0
- data/lib/omnizip/commands/decompress_command.rb +149 -0
- data/lib/omnizip/commands/list_command.rb +51 -0
- data/lib/omnizip/commands/metadata_command.rb +215 -0
- data/lib/omnizip/commands/parity_create_command.rb +114 -0
- data/lib/omnizip/commands/parity_repair_command.rb +120 -0
- data/lib/omnizip/commands/parity_verify_command.rb +122 -0
- data/lib/omnizip/commands/profile_list_command.rb +54 -0
- data/lib/omnizip/commands/profile_show_command.rb +44 -0
- data/lib/omnizip/commands.rb +21 -0
- data/lib/omnizip/convenience.rb +389 -0
- data/lib/omnizip/converter/conversion_registry.rb +50 -0
- data/lib/omnizip/converter/conversion_strategy.rb +108 -0
- data/lib/omnizip/converter/extract_repack_strategy.rb +51 -0
- data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +43 -0
- data/lib/omnizip/converter/zip_to_seven_zip_strategy.rb +54 -0
- data/lib/omnizip/converter.rb +124 -0
- data/lib/omnizip/crypto/aes256/cipher.rb +104 -0
- data/lib/omnizip/crypto/aes256/constants.rb +28 -0
- data/lib/omnizip/crypto/aes256/key_derivation.rb +100 -0
- data/lib/omnizip/crypto/aes256.rb +102 -0
- data/lib/omnizip/crypto.rb +11 -0
- data/lib/omnizip/entry.rb +44 -0
- data/lib/omnizip/error.rb +110 -0
- data/lib/omnizip/eta/exponential_smoothing_estimator.rb +95 -0
- data/lib/omnizip/eta/moving_average_estimator.rb +96 -0
- data/lib/omnizip/eta/rate_calculator.rb +104 -0
- data/lib/omnizip/eta/sample_history.rb +143 -0
- data/lib/omnizip/eta/time_estimator.rb +106 -0
- data/lib/omnizip/eta.rb +68 -0
- data/lib/omnizip/extraction/filter_chain.rb +179 -0
- data/lib/omnizip/extraction/glob_pattern.rb +140 -0
- data/lib/omnizip/extraction/pattern_matcher.rb +66 -0
- data/lib/omnizip/extraction/predicate_pattern.rb +52 -0
- data/lib/omnizip/extraction/regex_pattern.rb +50 -0
- data/lib/omnizip/extraction/selective_extractor.rb +247 -0
- data/lib/omnizip/extraction.rb +115 -0
- data/lib/omnizip/file_type/mime_classifier.rb +144 -0
- data/lib/omnizip/file_type.rb +117 -0
- data/lib/omnizip/filter.rb +141 -0
- data/lib/omnizip/filter_pipeline.rb +108 -0
- data/lib/omnizip/filter_registry.rb +113 -0
- data/lib/omnizip/filters/bcj.rb +277 -0
- data/lib/omnizip/filters/bcj2/constants.rb +53 -0
- data/lib/omnizip/filters/bcj2/decoder.rb +263 -0
- data/lib/omnizip/filters/bcj2/encoder.rb +58 -0
- data/lib/omnizip/filters/bcj2/stream_data.rb +91 -0
- data/lib/omnizip/filters/bcj2.rb +93 -0
- data/lib/omnizip/filters/bcj_arm.rb +174 -0
- data/lib/omnizip/filters/bcj_arm64.rb +242 -0
- data/lib/omnizip/filters/bcj_ia64.rb +194 -0
- data/lib/omnizip/filters/bcj_ppc.rb +188 -0
- data/lib/omnizip/filters/bcj_sparc.rb +174 -0
- data/lib/omnizip/filters/bcj_x86.rb +191 -0
- data/lib/omnizip/filters/delta.rb +195 -0
- data/lib/omnizip/filters/xz_delta.rb +258 -0
- data/lib/omnizip/filters.rb +20 -0
- data/lib/omnizip/format_detector.rb +166 -0
- data/lib/omnizip/formats/.keep +0 -0
- data/lib/omnizip/formats/bzip2_file.rb +159 -0
- data/lib/omnizip/formats/cpio/bounded_io.rb +66 -0
- data/lib/omnizip/formats/cpio/constants.rb +55 -0
- data/lib/omnizip/formats/cpio/entry.rb +389 -0
- data/lib/omnizip/formats/cpio/reader.rb +194 -0
- data/lib/omnizip/formats/cpio/writer.rb +232 -0
- data/lib/omnizip/formats/cpio.rb +132 -0
- data/lib/omnizip/formats/format_spec_loader.rb +228 -0
- data/lib/omnizip/formats/gzip.rb +236 -0
- data/lib/omnizip/formats/iso/directory_builder.rb +321 -0
- data/lib/omnizip/formats/iso/directory_record.rb +162 -0
- data/lib/omnizip/formats/iso/joliet.rb +204 -0
- data/lib/omnizip/formats/iso/path_table.rb +125 -0
- data/lib/omnizip/formats/iso/reader.rb +194 -0
- data/lib/omnizip/formats/iso/rock_ridge.rb +349 -0
- data/lib/omnizip/formats/iso/volume_builder.rb +319 -0
- data/lib/omnizip/formats/iso/volume_descriptor.rb +168 -0
- data/lib/omnizip/formats/iso/writer.rb +543 -0
- data/lib/omnizip/formats/iso.rb +133 -0
- data/lib/omnizip/formats/lzip.rb +161 -0
- data/lib/omnizip/formats/lzma_alone.rb +149 -0
- data/lib/omnizip/formats/msi/cab_extractor.rb +181 -0
- data/lib/omnizip/formats/msi/constants.rb +157 -0
- data/lib/omnizip/formats/msi/directory_resolver.rb +188 -0
- data/lib/omnizip/formats/msi/entry.rb +134 -0
- data/lib/omnizip/formats/msi/reader.rb +301 -0
- data/lib/omnizip/formats/msi/string_pool.rb +246 -0
- data/lib/omnizip/formats/msi/table_parser.rb +244 -0
- data/lib/omnizip/formats/msi.rb +73 -0
- data/lib/omnizip/formats/ole/allocation_table.rb +263 -0
- data/lib/omnizip/formats/ole/constants.rb +61 -0
- data/lib/omnizip/formats/ole/dirent.rb +439 -0
- data/lib/omnizip/formats/ole/header.rb +199 -0
- data/lib/omnizip/formats/ole/ranges_io.rb +347 -0
- data/lib/omnizip/formats/ole/storage.rb +482 -0
- data/lib/omnizip/formats/ole/types/variant.rb +338 -0
- data/lib/omnizip/formats/ole.rb +143 -0
- data/lib/omnizip/formats/rar/archive_repairer.rb +240 -0
- data/lib/omnizip/formats/rar/archive_verifier.rb +192 -0
- data/lib/omnizip/formats/rar/block_parser.rb +282 -0
- data/lib/omnizip/formats/rar/compression/bit_stream.rb +180 -0
- data/lib/omnizip/formats/rar/compression/dispatcher.rb +210 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/decoder.rb +212 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/encoder.rb +154 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_builder.rb +217 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_coder.rb +189 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/match_finder.rb +135 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/sliding_window.rb +165 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman.rb +25 -0
- data/lib/omnizip/formats/rar/compression/ppmd/context.rb +103 -0
- data/lib/omnizip/formats/rar/compression/ppmd/decoder.rb +238 -0
- data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +250 -0
- data/lib/omnizip/formats/rar/compression/ppmd.rb +16 -0
- data/lib/omnizip/formats/rar/compression.rb +17 -0
- data/lib/omnizip/formats/rar/compression_method_registry.rb +106 -0
- data/lib/omnizip/formats/rar/constants.rb +88 -0
- data/lib/omnizip/formats/rar/decompressor.rb +316 -0
- data/lib/omnizip/formats/rar/external_writer.rb +311 -0
- data/lib/omnizip/formats/rar/header.rb +209 -0
- data/lib/omnizip/formats/rar/license_validator.rb +111 -0
- data/lib/omnizip/formats/rar/models/rar_archive.rb +77 -0
- data/lib/omnizip/formats/rar/models/rar_entry.rb +65 -0
- data/lib/omnizip/formats/rar/models/rar_volume.rb +56 -0
- data/lib/omnizip/formats/rar/models.rb +14 -0
- data/lib/omnizip/formats/rar/parity_handler.rb +299 -0
- data/lib/omnizip/formats/rar/rar5/compression/lzma.rb +200 -0
- data/lib/omnizip/formats/rar/rar5/compression/lzss.rb +584 -0
- data/lib/omnizip/formats/rar/rar5/compression/store.rb +60 -0
- data/lib/omnizip/formats/rar/rar5/crc32.rb +39 -0
- data/lib/omnizip/formats/rar/rar5/encryption/aes256_cbc.rb +96 -0
- data/lib/omnizip/formats/rar/rar5/encryption/encryption_header.rb +114 -0
- data/lib/omnizip/formats/rar/rar5/encryption/encryption_manager.rb +162 -0
- data/lib/omnizip/formats/rar/rar5/encryption/key_derivation.rb +97 -0
- data/lib/omnizip/formats/rar/rar5/header.rb +208 -0
- data/lib/omnizip/formats/rar/rar5/models/encryption_options.rb +74 -0
- data/lib/omnizip/formats/rar/rar5/models/recovery_options.rb +63 -0
- data/lib/omnizip/formats/rar/rar5/models/solid_options.rb +63 -0
- data/lib/omnizip/formats/rar/rar5/models/volume_options.rb +74 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/ARCHITECTURE.md +290 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_manager.rb +260 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_splitter.rb +155 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_writer.rb +176 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_encoder.rb +108 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_manager.rb +139 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_stream.rb +121 -0
- data/lib/omnizip/formats/rar/rar5/vint.rb +53 -0
- data/lib/omnizip/formats/rar/rar5/writer.rb +540 -0
- data/lib/omnizip/formats/rar/rar_format_base.rb +239 -0
- data/lib/omnizip/formats/rar/reader.rb +377 -0
- data/lib/omnizip/formats/rar/recovery_record.rb +245 -0
- data/lib/omnizip/formats/rar/volume_manager.rb +166 -0
- data/lib/omnizip/formats/rar/writer.rb +437 -0
- data/lib/omnizip/formats/rar.rb +193 -0
- data/lib/omnizip/formats/rar3/compressor.rb +71 -0
- data/lib/omnizip/formats/rar3/decompressor.rb +64 -0
- data/lib/omnizip/formats/rar3/reader.rb +107 -0
- data/lib/omnizip/formats/rar3/writer.rb +220 -0
- data/lib/omnizip/formats/rar3.rb +15 -0
- data/lib/omnizip/formats/rar5/compressor.rb +71 -0
- data/lib/omnizip/formats/rar5/decompressor.rb +64 -0
- data/lib/omnizip/formats/rar5/reader.rb +130 -0
- data/lib/omnizip/formats/rar5/writer.rb +65 -0
- data/lib/omnizip/formats/rar5.rb +15 -0
- data/lib/omnizip/formats/rpm/constants.rb +58 -0
- data/lib/omnizip/formats/rpm/entry.rb +106 -0
- data/lib/omnizip/formats/rpm/header.rb +106 -0
- data/lib/omnizip/formats/rpm/lead.rb +132 -0
- data/lib/omnizip/formats/rpm/tag.rb +228 -0
- data/lib/omnizip/formats/rpm/writer.rb +652 -0
- data/lib/omnizip/formats/rpm.rb +458 -0
- data/lib/omnizip/formats/seven_zip/bcj2_stream_decompressor.rb +274 -0
- data/lib/omnizip/formats/seven_zip/coder_chain.rb +169 -0
- data/lib/omnizip/formats/seven_zip/constants.rb +126 -0
- data/lib/omnizip/formats/seven_zip/encoded_header.rb +110 -0
- data/lib/omnizip/formats/seven_zip/encrypted_header.rb +139 -0
- data/lib/omnizip/formats/seven_zip/file_collector.rb +143 -0
- data/lib/omnizip/formats/seven_zip/header.rb +102 -0
- data/lib/omnizip/formats/seven_zip/header_encryptor.rb +132 -0
- data/lib/omnizip/formats/seven_zip/header_writer.rb +425 -0
- data/lib/omnizip/formats/seven_zip/models/coder_info.rb +30 -0
- data/lib/omnizip/formats/seven_zip/models/file_entry.rb +65 -0
- data/lib/omnizip/formats/seven_zip/models/folder.rb +67 -0
- data/lib/omnizip/formats/seven_zip/models/stream_info.rb +42 -0
- data/lib/omnizip/formats/seven_zip/models.rb +20 -0
- data/lib/omnizip/formats/seven_zip/parser.rb +671 -0
- data/lib/omnizip/formats/seven_zip/reader.rb +626 -0
- data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +611 -0
- data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +308 -0
- data/lib/omnizip/formats/seven_zip/stream_compressor.rb +151 -0
- data/lib/omnizip/formats/seven_zip/stream_decompressor.rb +166 -0
- data/lib/omnizip/formats/seven_zip/writer.rb +801 -0
- data/lib/omnizip/formats/seven_zip.rb +179 -0
- data/lib/omnizip/formats/tar/constants.rb +73 -0
- data/lib/omnizip/formats/tar/entry.rb +98 -0
- data/lib/omnizip/formats/tar/header.rb +206 -0
- data/lib/omnizip/formats/tar/reader.rb +119 -0
- data/lib/omnizip/formats/tar/writer.rb +212 -0
- data/lib/omnizip/formats/tar.rb +74 -0
- data/lib/omnizip/formats/xar/constants.rb +119 -0
- data/lib/omnizip/formats/xar/entry.rb +268 -0
- data/lib/omnizip/formats/xar/header.rb +223 -0
- data/lib/omnizip/formats/xar/reader.rb +400 -0
- data/lib/omnizip/formats/xar/toc.rb +570 -0
- data/lib/omnizip/formats/xar/writer.rb +461 -0
- data/lib/omnizip/formats/xar.rb +145 -0
- data/lib/omnizip/formats/xz/reader.rb +121 -0
- data/lib/omnizip/formats/xz/writer.rb +416 -0
- data/lib/omnizip/formats/xz.rb +210 -0
- data/lib/omnizip/formats/xz_const.rb +49 -0
- data/lib/omnizip/formats/xz_impl/block_decoder.rb +761 -0
- data/lib/omnizip/formats/xz_impl/block_encoder.rb +304 -0
- data/lib/omnizip/formats/xz_impl/block_header.rb +210 -0
- data/lib/omnizip/formats/xz_impl/block_header_parser.rb +182 -0
- data/lib/omnizip/formats/xz_impl/index_decoder.rb +174 -0
- data/lib/omnizip/formats/xz_impl/index_encoder.rb +120 -0
- data/lib/omnizip/formats/xz_impl/stream_decoder.rb +470 -0
- data/lib/omnizip/formats/xz_impl/stream_encoder.rb +95 -0
- data/lib/omnizip/formats/xz_impl/stream_footer.rb +79 -0
- data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +115 -0
- data/lib/omnizip/formats/xz_impl/stream_header.rb +53 -0
- data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +106 -0
- data/lib/omnizip/formats/xz_impl/vli.rb +128 -0
- data/lib/omnizip/formats/xz_impl.rb +52 -0
- data/lib/omnizip/formats/zip/central_directory_header.rb +223 -0
- data/lib/omnizip/formats/zip/constants.rb +69 -0
- data/lib/omnizip/formats/zip/end_of_central_directory.rb +132 -0
- data/lib/omnizip/formats/zip/local_file_header.rb +136 -0
- data/lib/omnizip/formats/zip/reader.rb +376 -0
- data/lib/omnizip/formats/zip/unix_extra_field.rb +153 -0
- data/lib/omnizip/formats/zip/writer.rb +489 -0
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +102 -0
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +64 -0
- data/lib/omnizip/formats/zip/zip64_extra_field.rb +112 -0
- data/lib/omnizip/formats/zip.rb +50 -0
- data/lib/omnizip/formats.rb +29 -0
- data/lib/omnizip/implementations/base/lzma2_decoder_base.rb +75 -0
- data/lib/omnizip/implementations/base/lzma2_encoder_base.rb +128 -0
- data/lib/omnizip/implementations/base/lzma_decoder_base.rb +83 -0
- data/lib/omnizip/implementations/base/lzma_encoder_base.rb +108 -0
- data/lib/omnizip/implementations/base/state_machine_base.rb +182 -0
- data/lib/omnizip/implementations/base.rb +19 -0
- data/lib/omnizip/implementations/rust/archive.rb +93 -0
- data/lib/omnizip/implementations/rust/library.rb +221 -0
- data/lib/omnizip/implementations/rust.rb +26 -0
- data/lib/omnizip/implementations/seven_zip/lzma/decoder.rb +428 -0
- data/lib/omnizip/implementations/seven_zip/lzma/encoder.rb +456 -0
- data/lib/omnizip/implementations/seven_zip/lzma/match_finder.rb +285 -0
- data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +197 -0
- data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +194 -0
- data/lib/omnizip/implementations/seven_zip/lzma/state_machine.rb +139 -0
- data/lib/omnizip/implementations/seven_zip/lzma.rb +21 -0
- data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +56 -0
- data/lib/omnizip/implementations/seven_zip/lzma2.rb +12 -0
- data/lib/omnizip/implementations/seven_zip.rb +11 -0
- data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +547 -0
- data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +748 -0
- data/lib/omnizip/implementations/xz_utils/lzma2.rb +13 -0
- data/lib/omnizip/implementations/xz_utils.rb +10 -0
- data/lib/omnizip/implementations.rb +25 -0
- data/lib/omnizip/io/buffered_input.rb +147 -0
- data/lib/omnizip/io/buffered_output.rb +106 -0
- data/lib/omnizip/io/source.rb +143 -0
- data/lib/omnizip/io/stream_manager.rb +117 -0
- data/lib/omnizip/io.rb +15 -0
- data/lib/omnizip/link_handler/hard_link.rb +79 -0
- data/lib/omnizip/link_handler/symbolic_link.rb +74 -0
- data/lib/omnizip/link_handler.rb +129 -0
- data/lib/omnizip/metadata/archive_metadata.rb +114 -0
- data/lib/omnizip/metadata/entry_metadata.rb +153 -0
- data/lib/omnizip/metadata/metadata_editor.rb +169 -0
- data/lib/omnizip/metadata/metadata_registry.rb +71 -0
- data/lib/omnizip/metadata/metadata_validator.rb +101 -0
- data/lib/omnizip/metadata.rb +58 -0
- data/lib/omnizip/models/.keep +0 -0
- data/lib/omnizip/models/algorithm_metadata.rb +41 -0
- data/lib/omnizip/models/attribute_apply.rb +40 -0
- data/lib/omnizip/models/compression_options.rb +53 -0
- data/lib/omnizip/models/conversion_options.rb +79 -0
- data/lib/omnizip/models/conversion_result.rb +141 -0
- data/lib/omnizip/models/eta_result.rb +45 -0
- data/lib/omnizip/models/extraction_rule.rb +115 -0
- data/lib/omnizip/models/filter_chain.rb +142 -0
- data/lib/omnizip/models/filter_config.rb +148 -0
- data/lib/omnizip/models/match_result.rb +141 -0
- data/lib/omnizip/models/optimization_suggestion.rb +95 -0
- data/lib/omnizip/models/parallel_options.rb +116 -0
- data/lib/omnizip/models/performance_result.rb +85 -0
- data/lib/omnizip/models/profile_report.rb +116 -0
- data/lib/omnizip/models/progress_options.rb +39 -0
- data/lib/omnizip/models/split_options.rb +116 -0
- data/lib/omnizip/optimization_registry.rb +45 -0
- data/lib/omnizip/parallel/engine.rb +52 -0
- data/lib/omnizip/parallel/job_queue.rb +209 -0
- data/lib/omnizip/parallel/job_scheduler.rb +225 -0
- data/lib/omnizip/parallel/parallel_compressor.rb +326 -0
- data/lib/omnizip/parallel/parallel_extractor.rb +238 -0
- data/lib/omnizip/parallel/thread_pool.rb +60 -0
- data/lib/omnizip/parallel/worker_pool.rb +227 -0
- data/lib/omnizip/parallel.rb +156 -0
- data/lib/omnizip/parity/chunked_block_processor.rb +194 -0
- data/lib/omnizip/parity/galois16.rb +145 -0
- data/lib/omnizip/parity/models/creator_packet.rb +71 -0
- data/lib/omnizip/parity/models/file_description_packet.rb +131 -0
- data/lib/omnizip/parity/models/ifsc_packet.rb +111 -0
- data/lib/omnizip/parity/models/main_packet.rb +126 -0
- data/lib/omnizip/parity/models/packet.rb +156 -0
- data/lib/omnizip/parity/models/packet_registry.rb +102 -0
- data/lib/omnizip/parity/models/recovery_slice_packet.rb +76 -0
- data/lib/omnizip/parity/models.rb +27 -0
- data/lib/omnizip/parity/par2_creator.rb +496 -0
- data/lib/omnizip/parity/par2_repairer.rb +409 -0
- data/lib/omnizip/parity/par2_verifier.rb +373 -0
- data/lib/omnizip/parity/par2cmdline_algorithm.rb +108 -0
- data/lib/omnizip/parity/par2cmdline_coefficients.rb +78 -0
- data/lib/omnizip/parity/reed_solomon_decoder.rb +263 -0
- data/lib/omnizip/parity/reed_solomon_encoder.rb +108 -0
- data/lib/omnizip/parity/reed_solomon_matrix.rb +339 -0
- data/lib/omnizip/parity.rb +207 -0
- data/lib/omnizip/password/encryption_registry.rb +26 -0
- data/lib/omnizip/password/encryption_strategy.rb +96 -0
- data/lib/omnizip/password/password_validator.rb +129 -0
- data/lib/omnizip/password/winzip_aes_strategy.rb +190 -0
- data/lib/omnizip/password/zip_crypto_strategy.rb +135 -0
- data/lib/omnizip/password.rb +88 -0
- data/lib/omnizip/pipe/stream_compressor.rb +124 -0
- data/lib/omnizip/pipe/stream_decompressor.rb +173 -0
- data/lib/omnizip/pipe.rb +120 -0
- data/lib/omnizip/platform/ntfs_streams.rb +200 -0
- data/lib/omnizip/platform.rb +189 -0
- data/lib/omnizip/profile/archive_profile.rb +36 -0
- data/lib/omnizip/profile/balanced_profile.rb +31 -0
- data/lib/omnizip/profile/binary_profile.rb +33 -0
- data/lib/omnizip/profile/compression_profile.rb +157 -0
- data/lib/omnizip/profile/custom_profile.rb +155 -0
- data/lib/omnizip/profile/fast_profile.rb +31 -0
- data/lib/omnizip/profile/maximum_profile.rb +31 -0
- data/lib/omnizip/profile/profile_detector.rb +108 -0
- data/lib/omnizip/profile/profile_registry.rb +160 -0
- data/lib/omnizip/profile/text_profile.rb +33 -0
- data/lib/omnizip/profile.rb +261 -0
- data/lib/omnizip/profiler/memory_profiler.rb +66 -0
- data/lib/omnizip/profiler/method_profiler.rb +49 -0
- data/lib/omnizip/profiler/report_generator.rb +158 -0
- data/lib/omnizip/profiler.rb +192 -0
- data/lib/omnizip/progress/callback_reporter.rb +34 -0
- data/lib/omnizip/progress/console_reporter.rb +59 -0
- data/lib/omnizip/progress/log_reporter.rb +89 -0
- data/lib/omnizip/progress/operation_progress.rb +118 -0
- data/lib/omnizip/progress/progress_bar.rb +156 -0
- data/lib/omnizip/progress/progress_reporter.rb +40 -0
- data/lib/omnizip/progress/progress_tracker.rb +215 -0
- data/lib/omnizip/progress/silent_reporter.rb +22 -0
- data/lib/omnizip/progress.rb +131 -0
- data/lib/omnizip/registry.rb +111 -0
- data/lib/omnizip/rubyzip_compat.rb +61 -0
- data/lib/omnizip/temp/safe_extract.rb +124 -0
- data/lib/omnizip/temp/temp_file.rb +123 -0
- data/lib/omnizip/temp/temp_file_pool.rb +109 -0
- data/lib/omnizip/temp.rb +184 -0
- data/lib/omnizip/version.rb +5 -0
- data/lib/omnizip/zip/entry.rb +160 -0
- data/lib/omnizip/zip/file.rb +485 -0
- data/lib/omnizip/zip/input_stream.rb +270 -0
- data/lib/omnizip/zip/output_stream.rb +320 -0
- data/lib/omnizip/zip.rb +23 -0
- data/lib/omnizip.rb +157 -0
- data/lib/tasks/msi_test_files.rake +66 -0
- data/lib/tasks/platform_gems.rake +147 -0
- data/lib/tasks/rust_backend.rake +48 -0
- data/readme-docs/advanced-features.adoc +514 -0
- data/readme-docs/api-usage.adoc +445 -0
- data/readme-docs/architecture.adoc +450 -0
- data/readme-docs/archive-formats.adoc +479 -0
- data/readme-docs/cli-usage.adoc +222 -0
- data/readme-docs/compression-algorithms.adoc +446 -0
- data/readme-docs/compression-profiles.adoc +247 -0
- data/readme-docs/encryption-checksums.adoc +328 -0
- data/readme-docs/format-converter.adoc +316 -0
- data/readme-docs/installation.adoc +228 -0
- data/readme-docs/par2-archives.adoc +608 -0
- data/readme-docs/performance-profiler.adoc +389 -0
- data/readme-docs/preprocessing-filters.adoc +280 -0
- data/vendor/libomnizip_ffi.so +0 -0
- data/xz-file-format-1.2.1.txt +1174 -0
- metadata +768 -0
|
@@ -0,0 +1,801 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
|
|
5
|
+
module Omnizip
|
|
6
|
+
module Formats
|
|
7
|
+
module SevenZip
|
|
8
|
+
# .7z archive writer - 7-Zip compatible implementation
|
|
9
|
+
#
|
|
10
|
+
# Creates archives that are fully compatible with official 7-Zip (7zz command).
|
|
11
|
+
#
|
|
12
|
+
# Archive structure:
|
|
13
|
+
# - Start Header (32 bytes)
|
|
14
|
+
# - LZMA2 compressed file data
|
|
15
|
+
# - UNCOMPRESSED Next Header metadata (properties: kHeader, kPackInfo, etc.)
|
|
16
|
+
# - Metadata footer (filename, attributes, timestamps)
|
|
17
|
+
class Writer
|
|
18
|
+
include Omnizip::Formats::SevenZip::Constants
|
|
19
|
+
|
|
20
|
+
# Constants for array literals used in loops (RuboCop Performance/CollectionLiteralInLoop)
|
|
21
|
+
COPY_MAIN_BYTE = [0x01].pack("C").freeze
|
|
22
|
+
COPY_METHOD_ID = [0x00].pack("C").freeze
|
|
23
|
+
NULL_TERMINATOR = [0x00, 0x00].pack("CC").freeze
|
|
24
|
+
FILE_ATTRIBUTE_ARCHIVE = [0x20].pack("V").freeze
|
|
25
|
+
|
|
26
|
+
attr_reader :output_path, :options, :entries
|
|
27
|
+
|
|
28
|
+
def initialize(output_path, options = {})
|
|
29
|
+
@output_path = output_path
|
|
30
|
+
@options = {
|
|
31
|
+
algorithm: :lzma2,
|
|
32
|
+
level: 5,
|
|
33
|
+
dict_size: 8 * 1024 * 1024, # 8MB default dictionary
|
|
34
|
+
solid: true, # Solid mode for LZMA2 compression
|
|
35
|
+
filters: [],
|
|
36
|
+
encrypt_headers: false,
|
|
37
|
+
}.merge(options)
|
|
38
|
+
@collector = FileCollector.new
|
|
39
|
+
@entries = []
|
|
40
|
+
|
|
41
|
+
# Preprocessing filters are decode-side only: the write
|
|
42
|
+
# path does not apply them, so say so instead of silently
|
|
43
|
+
# ignoring the option
|
|
44
|
+
filters = @options[:filters]
|
|
45
|
+
return unless filters && !filters.empty?
|
|
46
|
+
|
|
47
|
+
warn "omnizip: the 7z writer does not apply filters " \
|
|
48
|
+
"(#{Array(filters).join(', ')}); writing unfiltered data. " \
|
|
49
|
+
"Filters apply when reading archives that contain them."
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def add_file(file_path, archive_path = nil)
|
|
53
|
+
@collector.add_path(file_path, archive_path: archive_path,
|
|
54
|
+
recursive: false)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def add_directory(dir_path, recursive = true)
|
|
58
|
+
@collector.add_path(dir_path, recursive: recursive)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def add_files(pattern)
|
|
62
|
+
@collector.add_glob(pattern)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def add_data(archive_path, data, options = {})
|
|
66
|
+
data_str = data.is_a?(String) ? data : data.read
|
|
67
|
+
|
|
68
|
+
entry = Models::FileEntry.new
|
|
69
|
+
entry.name = archive_path
|
|
70
|
+
entry.source_path = nil
|
|
71
|
+
entry.size = data_str.bytesize
|
|
72
|
+
entry.mtime = Time.now
|
|
73
|
+
# Zero-length data has no stream: readers identify it as an
|
|
74
|
+
# empty file through kEmptyStream + kEmptyFile.
|
|
75
|
+
entry.has_stream = data_str.bytesize.positive?
|
|
76
|
+
entry.inline_data = data_str
|
|
77
|
+
entry.compression_options = options
|
|
78
|
+
@entries << entry
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Add an explicit directory entry with no data or stream
|
|
82
|
+
#
|
|
83
|
+
# @param archive_path [String] Directory path inside the
|
|
84
|
+
# archive (trailing slash optional)
|
|
85
|
+
# @return [self]
|
|
86
|
+
def add_directory_entry(archive_path)
|
|
87
|
+
entry = Models::FileEntry.new
|
|
88
|
+
# Trailing slash is preserved: explicit directory entries
|
|
89
|
+
# carry it ("testdir/"), matching the collector's tree walk
|
|
90
|
+
entry.name = archive_path
|
|
91
|
+
entry.source_path = nil
|
|
92
|
+
entry.size = 0
|
|
93
|
+
entry.is_dir = true
|
|
94
|
+
entry.has_stream = false
|
|
95
|
+
entry.mtime = Time.now
|
|
96
|
+
@entries << entry
|
|
97
|
+
self
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def write
|
|
101
|
+
# Collect any files from the collector (if add_file/add_directory was used)
|
|
102
|
+
collected_entries = @collector.collect_files
|
|
103
|
+
# Merge with entries added via add_data
|
|
104
|
+
@entries.concat(collected_entries)
|
|
105
|
+
|
|
106
|
+
# Check if split archive requested
|
|
107
|
+
if @options[:volume_size]
|
|
108
|
+
write_split_archive
|
|
109
|
+
else
|
|
110
|
+
File.open(@output_path, "wb") do |io|
|
|
111
|
+
write_archive(io)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
# Write split archive (delegates to SplitArchiveWriter)
|
|
119
|
+
def write_split_archive
|
|
120
|
+
split_options = Omnizip::Models::SplitOptions.new
|
|
121
|
+
split_options.volume_size = @options[:volume_size]
|
|
122
|
+
|
|
123
|
+
writer = SplitArchiveWriter.new(@output_path, @options, split_options)
|
|
124
|
+
|
|
125
|
+
# Add all collected files
|
|
126
|
+
@entries.each do |entry|
|
|
127
|
+
if entry.directory?
|
|
128
|
+
# Directories are already in entries
|
|
129
|
+
next
|
|
130
|
+
elsif entry.source_path
|
|
131
|
+
writer.add_file(entry.source_path, entry.name)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
writer.write
|
|
136
|
+
@entries = writer.entries
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def write_archive(io)
|
|
140
|
+
# Reserve space for start header
|
|
141
|
+
io.write("\0" * START_HEADER_SIZE)
|
|
142
|
+
|
|
143
|
+
# Step 1: Collect file data
|
|
144
|
+
file_data = collect_file_data
|
|
145
|
+
|
|
146
|
+
# Step 2: Build compressed data based on mode
|
|
147
|
+
if @options[:solid]
|
|
148
|
+
# Solid mode: compress all data together with LZMA2
|
|
149
|
+
packed_data, packed_sizes = build_solid_packed_data(file_data)
|
|
150
|
+
else
|
|
151
|
+
# Non-solid mode: each file stored separately (COPY method - no compression)
|
|
152
|
+
packed_data, packed_sizes = build_non_solid_packed_data(file_data)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Step 3: Build Next Header properties
|
|
156
|
+
# This includes kHeader, MAIN_STREAMS_INFO, FILES_INFO, etc.
|
|
157
|
+
next_header_data = build_next_header_properties(file_data,
|
|
158
|
+
packed_sizes)
|
|
159
|
+
|
|
160
|
+
# Step 4: Write the complete data section
|
|
161
|
+
# Note: CRC is stored in StartHeader, NOT appended to Next Header
|
|
162
|
+
io.write(packed_data) # Packed file data
|
|
163
|
+
io.write(next_header_data) # Next Header (CRC is in StartHeader)
|
|
164
|
+
|
|
165
|
+
# Step 5: Write Start Header
|
|
166
|
+
# Next Header starts after the packed data
|
|
167
|
+
# The offset is RELATIVE to the END of the StartHeader (byte 32)
|
|
168
|
+
next_header_offset = packed_data.bytesize
|
|
169
|
+
|
|
170
|
+
# Next Header size is the size of the Next Header data WITHOUT the CRC32
|
|
171
|
+
# (CRC32 is appended after the header data, not included in size)
|
|
172
|
+
next_header_size = next_header_data.bytesize
|
|
173
|
+
|
|
174
|
+
write_start_header(io, next_header_offset, next_header_size,
|
|
175
|
+
next_header_data)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Build packed data for solid mode (LZMA2 compression)
|
|
179
|
+
def build_solid_packed_data(file_data)
|
|
180
|
+
lzma2_chunk, compressed_size = build_lzma2_compressed_chunk(file_data[:data])
|
|
181
|
+
[lzma2_chunk, [compressed_size]]
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Build packed data for non-solid mode: every file is its own
|
|
185
|
+
# LZMA2 stream. Empty files keep a zero-size pack stream.
|
|
186
|
+
def build_non_solid_packed_data(file_data)
|
|
187
|
+
packed_data = String.new(encoding: Encoding::BINARY)
|
|
188
|
+
packed_sizes = []
|
|
189
|
+
|
|
190
|
+
file_data[:streams].each do |stream|
|
|
191
|
+
if stream[:size].positive?
|
|
192
|
+
compressed = compress_with_lzma2(stream[:data])
|
|
193
|
+
packed_data << compressed
|
|
194
|
+
packed_sizes << compressed.bytesize
|
|
195
|
+
else
|
|
196
|
+
packed_sizes << 0
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
[packed_data, packed_sizes]
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def collect_file_data
|
|
204
|
+
files_with_data = @entries.select(&:has_stream?)
|
|
205
|
+
|
|
206
|
+
if @options[:solid]
|
|
207
|
+
# Solid mode: combine all files into one stream
|
|
208
|
+
combined = String.new(encoding: "BINARY")
|
|
209
|
+
total_size = 0
|
|
210
|
+
|
|
211
|
+
files_with_data.each do |entry|
|
|
212
|
+
data = entry.inline_data || File.binread(entry.source_path)
|
|
213
|
+
combined << data
|
|
214
|
+
total_size += data.bytesize
|
|
215
|
+
|
|
216
|
+
crc = Omnizip::Checksums::Crc32.new
|
|
217
|
+
crc.update(data)
|
|
218
|
+
entry.crc = crc.finalize
|
|
219
|
+
entry.size = data.bytesize
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
{ data: combined, total_size: total_size,
|
|
223
|
+
streams: [{ data: combined, size: total_size }] }
|
|
224
|
+
else
|
|
225
|
+
# Non-solid mode: each file gets its own stream
|
|
226
|
+
streams = []
|
|
227
|
+
total_size = 0
|
|
228
|
+
|
|
229
|
+
files_with_data.each do |entry|
|
|
230
|
+
data = entry.inline_data || File.binread(entry.source_path)
|
|
231
|
+
|
|
232
|
+
crc = Omnizip::Checksums::Crc32.new
|
|
233
|
+
crc.update(data)
|
|
234
|
+
entry.crc = crc.finalize
|
|
235
|
+
entry.size = data.bytesize
|
|
236
|
+
|
|
237
|
+
streams << { data: data, size: data.bytesize }
|
|
238
|
+
total_size += data.bytesize
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Combine all data for writing
|
|
242
|
+
combined = streams.map { |s| s[:data] }.join
|
|
243
|
+
{ data: combined, total_size: total_size, streams: streams }
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def build_lzma2_compressed_chunk(data)
|
|
248
|
+
return ["", 0] if data.nil? || data.empty?
|
|
249
|
+
|
|
250
|
+
# Use actual LZMA2 compression via 7-Zip SDK encoder
|
|
251
|
+
compressed = compress_with_lzma2(data)
|
|
252
|
+
[compressed, compressed.bytesize]
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def build_next_header_properties(file_data, packed_sizes)
|
|
256
|
+
metadata = String.new(encoding: "BINARY")
|
|
257
|
+
unpack_size = file_data[:total_size]
|
|
258
|
+
num_files = @entries.size
|
|
259
|
+
solid = @options[:solid]
|
|
260
|
+
|
|
261
|
+
# kHeader property (0x01)
|
|
262
|
+
metadata << [PropertyId::HEADER].pack("C")
|
|
263
|
+
|
|
264
|
+
# kMainStreamsInfo property (0x04) - WRAPPER for stream info
|
|
265
|
+
metadata << [PropertyId::MAIN_STREAMS_INFO].pack("C")
|
|
266
|
+
|
|
267
|
+
if solid
|
|
268
|
+
# Solid mode: one pack stream, one folder
|
|
269
|
+
# packed_sizes is a single-element array with compressed size
|
|
270
|
+
compressed_size = packed_sizes.first
|
|
271
|
+
build_solid_streams_info(metadata, unpack_size, compressed_size,
|
|
272
|
+
num_files)
|
|
273
|
+
else
|
|
274
|
+
# Non-solid mode: one pack stream per file, one folder per file
|
|
275
|
+
build_non_solid_streams_info(metadata, file_data[:streams],
|
|
276
|
+
packed_sizes)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# kEnd for MainStreamsInfo
|
|
280
|
+
metadata << [PropertyId::K_END].pack("C")
|
|
281
|
+
|
|
282
|
+
# FILES_INFO section
|
|
283
|
+
build_files_info(metadata)
|
|
284
|
+
|
|
285
|
+
# kEnd for Header (closes the entire Next Header)
|
|
286
|
+
metadata << [PropertyId::K_END].pack("C")
|
|
287
|
+
|
|
288
|
+
# Encrypt headers if requested
|
|
289
|
+
if @options[:encrypt_headers]
|
|
290
|
+
metadata = encrypt_header(metadata)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
metadata
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Encrypt header data
|
|
297
|
+
#
|
|
298
|
+
# @param header_data [String] Unencrypted header
|
|
299
|
+
# @return [String] Encrypted header with metadata
|
|
300
|
+
def encrypt_header(header_data)
|
|
301
|
+
unless @options[:password]
|
|
302
|
+
raise Omnizip::PasswordError, "Password required for header encryption"
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
encryptor = HeaderEncryptor.new(@options[:password])
|
|
306
|
+
result = encryptor.encrypt(header_data)
|
|
307
|
+
|
|
308
|
+
# Create encrypted header structure
|
|
309
|
+
encrypted_header = EncryptedHeader.new(
|
|
310
|
+
encrypted_data: result[:data],
|
|
311
|
+
salt: result[:salt],
|
|
312
|
+
iv: result[:iv],
|
|
313
|
+
original_size: result[:size],
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
encrypted_header.to_binary
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def build_solid_streams_info(metadata, unpack_size, compressed_size,
|
|
320
|
+
_num_files)
|
|
321
|
+
# kPackInfo property (0x06)
|
|
322
|
+
metadata << [PropertyId::PACK_INFO].pack("C")
|
|
323
|
+
metadata << write_number(0) # Pack position
|
|
324
|
+
metadata << write_number(1) # Number of pack streams
|
|
325
|
+
|
|
326
|
+
# kSize property
|
|
327
|
+
metadata << [PropertyId::SIZE].pack("C")
|
|
328
|
+
metadata << write_number(compressed_size)
|
|
329
|
+
|
|
330
|
+
# kEnd for PackInfo
|
|
331
|
+
metadata << [PropertyId::K_END].pack("C")
|
|
332
|
+
|
|
333
|
+
# kUnpackInfo property (0x07)
|
|
334
|
+
metadata << [PropertyId::UNPACK_INFO].pack("C")
|
|
335
|
+
|
|
336
|
+
# kFolder property (0x0B)
|
|
337
|
+
metadata << [PropertyId::FOLDER].pack("C")
|
|
338
|
+
metadata << write_number(1) # Number of folders
|
|
339
|
+
|
|
340
|
+
# External flag (0 = inline, folders follow)
|
|
341
|
+
metadata << [0].pack("C")
|
|
342
|
+
|
|
343
|
+
# Folder definitions
|
|
344
|
+
build_folder_coder(metadata)
|
|
345
|
+
|
|
346
|
+
# kCodersUnpackSize - comes AFTER all folder definitions
|
|
347
|
+
metadata << [PropertyId::CODERS_UNPACK_SIZE].pack("C")
|
|
348
|
+
metadata << write_number(unpack_size)
|
|
349
|
+
|
|
350
|
+
# kEnd for UnpackInfo
|
|
351
|
+
metadata << [PropertyId::K_END].pack("C")
|
|
352
|
+
|
|
353
|
+
# kSubStreamsInfo describes exactly the entries WITH a
|
|
354
|
+
# stream: directory entries (empty-stream) are excluded,
|
|
355
|
+
# or readers reject the header.
|
|
356
|
+
metadata << [PropertyId::SUBSTREAMS_INFO].pack("C")
|
|
357
|
+
stream_files = @entries.select(&:has_stream?)
|
|
358
|
+
# Write the explicit form whenever any entry lacks a
|
|
359
|
+
# stream (directory entries): without kNumUnpackStreams,
|
|
360
|
+
# readers assume one substream per file and reject the
|
|
361
|
+
# digest count.
|
|
362
|
+
if stream_files.size > 1 || stream_files.size != @entries.size
|
|
363
|
+
|
|
364
|
+
# NUM_UNPACK_STREAM - number of substreams in this folder
|
|
365
|
+
metadata << [PropertyId::NUM_UNPACK_STREAM].pack("C")
|
|
366
|
+
metadata << write_number(stream_files.size)
|
|
367
|
+
|
|
368
|
+
# SIZE - size of each substream except the last one
|
|
369
|
+
# Per 7-Zip spec: only write numSubstreams-1 sizes, last
|
|
370
|
+
# is calculated from folder's unpack size minus the sum
|
|
371
|
+
metadata << [PropertyId::SIZE].pack("C")
|
|
372
|
+
stream_files[0..-2].each do |entry|
|
|
373
|
+
metadata << write_number(entry.size)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# CRCs
|
|
377
|
+
metadata << [PropertyId::CRC].pack("C")
|
|
378
|
+
metadata << [1].pack("C") # All defined
|
|
379
|
+
stream_files.each do |entry|
|
|
380
|
+
# Use 0 for entries without CRC (empty files)
|
|
381
|
+
crc = entry.crc || 0
|
|
382
|
+
metadata << [crc].pack("V")
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
else
|
|
386
|
+
# Single substream: CRC goes in SubStreamsInfo
|
|
387
|
+
metadata << [PropertyId::CRC].pack("C")
|
|
388
|
+
metadata << [1].pack("C") # All defined
|
|
389
|
+
# Use 0 for entries without CRC (empty files)
|
|
390
|
+
crc = stream_files.first&.crc || 0
|
|
391
|
+
metadata << [crc].pack("V")
|
|
392
|
+
end
|
|
393
|
+
metadata << [PropertyId::K_END].pack("C")
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def build_non_solid_streams_info(metadata, streams, packed_sizes)
|
|
397
|
+
num_streams = streams.size
|
|
398
|
+
|
|
399
|
+
# kPackInfo property (0x06)
|
|
400
|
+
metadata << [PropertyId::PACK_INFO].pack("C")
|
|
401
|
+
metadata << write_number(0) # Pack position
|
|
402
|
+
metadata << write_number(num_streams) # Number of pack streams
|
|
403
|
+
|
|
404
|
+
# kSize property - sizes of each pack stream (compressed)
|
|
405
|
+
metadata << [PropertyId::SIZE].pack("C")
|
|
406
|
+
packed_sizes.each do |packed|
|
|
407
|
+
metadata << write_number(packed)
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# kEnd for PackInfo
|
|
411
|
+
metadata << [PropertyId::K_END].pack("C")
|
|
412
|
+
|
|
413
|
+
# kUnpackInfo property (0x07)
|
|
414
|
+
metadata << [PropertyId::UNPACK_INFO].pack("C")
|
|
415
|
+
|
|
416
|
+
# kFolder property (0x0B)
|
|
417
|
+
metadata << [PropertyId::FOLDER].pack("C")
|
|
418
|
+
metadata << write_number(num_streams) # Number of folders
|
|
419
|
+
|
|
420
|
+
# External flag for all folders
|
|
421
|
+
metadata << [0].pack("C")
|
|
422
|
+
|
|
423
|
+
# Folder definitions (coders only, no sizes yet):
|
|
424
|
+
# one LZMA2 coder per non-empty stream, COPY for empties
|
|
425
|
+
streams.each do |stream|
|
|
426
|
+
if stream[:size].positive?
|
|
427
|
+
build_folder_coder(metadata)
|
|
428
|
+
else
|
|
429
|
+
metadata << write_number(1)
|
|
430
|
+
metadata << COPY_MAIN_BYTE
|
|
431
|
+
metadata << COPY_METHOD_ID
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# kCodersUnpackSize - comes after ALL folder definitions
|
|
436
|
+
# (original sizes; kSize above carries the packed sizes)
|
|
437
|
+
metadata << [PropertyId::CODERS_UNPACK_SIZE].pack("C")
|
|
438
|
+
streams.each do |stream|
|
|
439
|
+
metadata << write_number(stream[:size])
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
# kEnd for UnpackInfo
|
|
443
|
+
metadata << [PropertyId::K_END].pack("C")
|
|
444
|
+
|
|
445
|
+
# kSubStreamsInfo - CRC for each substream. Each folder
|
|
446
|
+
# holds exactly one substream here, so digests cover only
|
|
447
|
+
# the entries WITH a stream (directory entries have none).
|
|
448
|
+
metadata << [PropertyId::SUBSTREAMS_INFO].pack("C")
|
|
449
|
+
metadata << [PropertyId::CRC].pack("C")
|
|
450
|
+
metadata << [1].pack("C") # All defined
|
|
451
|
+
@entries.select(&:has_stream?).each do |entry|
|
|
452
|
+
crc = entry.crc || 0
|
|
453
|
+
metadata << [crc].pack("V")
|
|
454
|
+
end
|
|
455
|
+
metadata << [PropertyId::K_END].pack("C")
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
def build_folder_coder(metadata)
|
|
459
|
+
# Number of coders
|
|
460
|
+
metadata << write_number(1)
|
|
461
|
+
|
|
462
|
+
# Coder info for LZMA2 (method 0x21)
|
|
463
|
+
# MainByte format:
|
|
464
|
+
# bits 0-3 = num bytes for CodecID (1 for 0x21)
|
|
465
|
+
# bit 4 = IsComplexCoder (0)
|
|
466
|
+
# bits 5-7 = num property bytes (we set to 0 and write size separately)
|
|
467
|
+
# Per 7-Zip SDK, bits 5-7 indicate if there ARE properties (non-zero = has props)
|
|
468
|
+
# MainByte = 0x21 means: 1 byte for CodecID + has properties
|
|
469
|
+
metadata << [0x21].pack("C") # MainByte: 1 byte for CodecID + has properties
|
|
470
|
+
metadata << [0x21].pack("C") # Method ID: LZMA2 = 0x21
|
|
471
|
+
|
|
472
|
+
# LZMA2 property byte encodes dictionary size
|
|
473
|
+
dict_size = @options[:dict_size] || (8 * 1024 * 1024)
|
|
474
|
+
prop_byte = encode_lzma2_dict_size(dict_size)
|
|
475
|
+
|
|
476
|
+
# 7-Zip format: write property SIZE (as VLI), then property bytes
|
|
477
|
+
metadata << write_number(1) # PropsSize = 1 byte
|
|
478
|
+
metadata << [prop_byte].pack("C") # Property byte
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
def build_files_info(metadata)
|
|
482
|
+
# kFilesInfo property (0x05)
|
|
483
|
+
metadata << [PropertyId::FILES_INFO].pack("C")
|
|
484
|
+
|
|
485
|
+
# Number of files
|
|
486
|
+
metadata << write_number(@entries.size)
|
|
487
|
+
|
|
488
|
+
# Build EMPTY_STREAM property (0x0E) — written first, like
|
|
489
|
+
# 7-Zip itself: readers derive the directory flag from it;
|
|
490
|
+
# without it, directory entries are extracted as (invalid)
|
|
491
|
+
# files. Format: [EMPTY_STREAM] [size] [bit vector]
|
|
492
|
+
empty_bits = @entries.map { |e| !e.has_stream? }
|
|
493
|
+
if empty_bits.any?
|
|
494
|
+
# kEmptyStream carries the raw bit bytes only — NO
|
|
495
|
+
# all-defined marker byte (verified against 7-Zip's own
|
|
496
|
+
# -mhc=off output; a marker byte here shifts the whole
|
|
497
|
+
# header for strict readers).
|
|
498
|
+
empty_data = write_bit_bytes(empty_bits)
|
|
499
|
+
metadata << [PropertyId::EMPTY_STREAM].pack("C")
|
|
500
|
+
metadata << write_number(empty_data.bytesize)
|
|
501
|
+
metadata << empty_data
|
|
502
|
+
|
|
503
|
+
# kEmptyFile distinguishes zero-byte files from
|
|
504
|
+
# directories among the empty-stream entries.
|
|
505
|
+
empty_file_bits = @entries.map do |e|
|
|
506
|
+
!e.has_stream? && !e.is_dir
|
|
507
|
+
end
|
|
508
|
+
if empty_file_bits.any?
|
|
509
|
+
ef_data = write_bit_bytes(empty_file_bits)
|
|
510
|
+
metadata << [PropertyId::EMPTY_FILE].pack("C")
|
|
511
|
+
metadata << write_number(ef_data.bytesize)
|
|
512
|
+
metadata << ef_data
|
|
513
|
+
end
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# Build NAME property (0x11)
|
|
517
|
+
# Format: [NAME] [size] [external] [UTF-16LE names with null terminators]
|
|
518
|
+
name_data = String.new(encoding: "BINARY")
|
|
519
|
+
|
|
520
|
+
@entries.each do |entry|
|
|
521
|
+
# Encode name as UTF-16LE and force to BINARY
|
|
522
|
+
name_utf16le = entry.name.encode("UTF-16LE").b
|
|
523
|
+
# Add null terminator (2 bytes)
|
|
524
|
+
name_data << name_utf16le
|
|
525
|
+
name_data << NULL_TERMINATOR
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
metadata << [PropertyId::NAME].pack("C")
|
|
529
|
+
metadata << write_number(name_data.bytesize + 1) # +1 for External byte
|
|
530
|
+
metadata << [0].pack("C") # External = 0 (inline)
|
|
531
|
+
metadata << name_data
|
|
532
|
+
|
|
533
|
+
# Build MTIME property (0x14) - MUST come before WIN_ATTRIB per 7-Zip spec
|
|
534
|
+
# Format: [MTIME] [size] [defined bits] [external] [FILETIME values]
|
|
535
|
+
time_data = String.new(encoding: "BINARY")
|
|
536
|
+
|
|
537
|
+
@entries.each do |entry|
|
|
538
|
+
unix_time = entry.mtime.to_i
|
|
539
|
+
windows_time = (unix_time + 11_644_473_600) * 10_000_000
|
|
540
|
+
time_data << [windows_time].pack("Q<")
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
metadata << [PropertyId::MTIME].pack("C")
|
|
544
|
+
metadata << write_number(time_data.bytesize + 2) # +2 for all_defined and external bytes
|
|
545
|
+
metadata << [1].pack("C") # All defined
|
|
546
|
+
metadata << [0].pack("C") # External = 0 (inline)
|
|
547
|
+
metadata << time_data
|
|
548
|
+
|
|
549
|
+
# Build WIN_ATTRIB property (0x15) - comes after MTIME per 7-Zip spec
|
|
550
|
+
# Format: [WIN_ATTRIB] [size] [defined bits] [external] [attributes]
|
|
551
|
+
attr_data = String.new(encoding: "BINARY")
|
|
552
|
+
@entries.each do |entry|
|
|
553
|
+
attr_data << [entry.attributes || FILE_ATTRIBUTE_ARCHIVE.unpack1("V")].pack("V")
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
metadata << [PropertyId::WIN_ATTRIB].pack("C")
|
|
557
|
+
metadata << write_number(attr_data.bytesize + 2) # +2 for all_defined and external bytes
|
|
558
|
+
metadata << [1].pack("C") # All defined
|
|
559
|
+
metadata << [0].pack("C") # External = 0 (inline)
|
|
560
|
+
metadata << attr_data
|
|
561
|
+
|
|
562
|
+
# kEnd for FilesInfo
|
|
563
|
+
metadata << [PropertyId::K_END].pack("C")
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def compress_with_lzma2(data)
|
|
567
|
+
# Use 7-Zip SDK LZMA2 encoder for 7-Zip format
|
|
568
|
+
#
|
|
569
|
+
# The encoder dictionary is sized to the data but never
|
|
570
|
+
# exceeds the size announced in the coder properties
|
|
571
|
+
# (options default 8MB): the decoder allocates its window
|
|
572
|
+
# from the announced size, so a larger encoder dictionary
|
|
573
|
+
# could emit matches reading outside the decoder window.
|
|
574
|
+
announced = @options[:dict_size] || (8 * 1024 * 1024)
|
|
575
|
+
dict_size = [4096, data.bytesize].max
|
|
576
|
+
dict_size = announced if announced < dict_size
|
|
577
|
+
|
|
578
|
+
encoder = Omnizip::Implementations::SevenZip::LZMA2::Encoder.new(
|
|
579
|
+
dict_size: dict_size,
|
|
580
|
+
lc: 3,
|
|
581
|
+
lp: 0,
|
|
582
|
+
pb: 2,
|
|
583
|
+
standalone: false, # No property byte (raw mode)
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
encoder.encode(data)
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
# Encode dictionary size to LZMA2 property byte
|
|
590
|
+
#
|
|
591
|
+
# LZMA2 property byte encoding (per XZ spec, same for 7-Zip):
|
|
592
|
+
# dict_size = (2 | (props & 1)) << (props / 2 + 11)
|
|
593
|
+
#
|
|
594
|
+
# This gives sizes from 4KB (props=0) to 4GB (props=40)
|
|
595
|
+
#
|
|
596
|
+
# @param dict_size [Integer] Dictionary size in bytes
|
|
597
|
+
# @return [Integer] Property byte (0-40)
|
|
598
|
+
def encode_lzma2_dict_size(dict_size)
|
|
599
|
+
# Find the smallest prop value that gives >= dict_size
|
|
600
|
+
(0..40).each do |prop|
|
|
601
|
+
# Decode formula: dict_size = (2 | (prop & 1)) << (prop / 2 + 11)
|
|
602
|
+
base = 2 | (prop & 1)
|
|
603
|
+
size = base << ((prop / 2) + 11)
|
|
604
|
+
return prop if size >= dict_size
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
# Maximum property value
|
|
608
|
+
40
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
def build_next_header_metadata(compressed_size, unpack_size)
|
|
612
|
+
metadata = String.new(encoding: "BINARY")
|
|
613
|
+
|
|
614
|
+
# kHeader property (0x01)
|
|
615
|
+
metadata << [PropertyId::HEADER].pack("C")
|
|
616
|
+
|
|
617
|
+
# kMainStreamsInfo property (0x04) - WRAPPER for stream info
|
|
618
|
+
metadata << [PropertyId::MAIN_STREAMS_INFO].pack("C")
|
|
619
|
+
|
|
620
|
+
# kPackInfo property (0x06)
|
|
621
|
+
metadata << [PropertyId::PACK_INFO].pack("C")
|
|
622
|
+
metadata << write_number(0) # Pack position
|
|
623
|
+
metadata << write_number(1) # Number of pack streams
|
|
624
|
+
|
|
625
|
+
# kSize property
|
|
626
|
+
metadata << [PropertyId::SIZE].pack("C")
|
|
627
|
+
metadata << write_number(compressed_size)
|
|
628
|
+
|
|
629
|
+
# kEnd for PackInfo
|
|
630
|
+
metadata << [PropertyId::K_END].pack("C")
|
|
631
|
+
|
|
632
|
+
# kUnpackInfo property (0x07)
|
|
633
|
+
metadata << [PropertyId::UNPACK_INFO].pack("C")
|
|
634
|
+
|
|
635
|
+
# kFolder property (0x0B)
|
|
636
|
+
metadata << [PropertyId::FOLDER].pack("C")
|
|
637
|
+
metadata << write_number(1) # Number of folders
|
|
638
|
+
|
|
639
|
+
# Folder content
|
|
640
|
+
metadata << [0].pack("C") # External flag (0 = inline)
|
|
641
|
+
|
|
642
|
+
# Number of coders
|
|
643
|
+
metadata << write_number(1)
|
|
644
|
+
|
|
645
|
+
# Coder info for LZMA2
|
|
646
|
+
# Method ID: LZMA2 = 0x21
|
|
647
|
+
# Main byte: 1 byte for ID + 0x00 for no properties
|
|
648
|
+
metadata << [1].pack("C") # 1 byte for ID
|
|
649
|
+
metadata << [0x21].pack("C") # LZMA2 method ID
|
|
650
|
+
|
|
651
|
+
# kCodersUnpackSize
|
|
652
|
+
metadata << [PropertyId::CODERS_UNPACK_SIZE].pack("C")
|
|
653
|
+
metadata << write_number(unpack_size)
|
|
654
|
+
|
|
655
|
+
# kSubStreamsInfo property (0x08)
|
|
656
|
+
metadata << [PropertyId::SUBSTREAMS_INFO].pack("C")
|
|
657
|
+
|
|
658
|
+
# kCRC property (0x0a)
|
|
659
|
+
metadata << [PropertyId::CRC].pack("C")
|
|
660
|
+
metadata << [1].pack("C") # All streams have CRC (1 = yes, 0 = no)
|
|
661
|
+
|
|
662
|
+
# EMPTY_STREAM (0x0e): Number of empty unpack streams
|
|
663
|
+
metadata << [PropertyId::EMPTY_STREAM].pack("C")
|
|
664
|
+
metadata << [0].pack("C") # 0 empty streams (file has data)
|
|
665
|
+
|
|
666
|
+
# kEnd for SubStreamsInfo
|
|
667
|
+
metadata << [PropertyId::K_END].pack("C")
|
|
668
|
+
|
|
669
|
+
# kEnd for UnpackInfo
|
|
670
|
+
metadata << [PropertyId::K_END].pack("C")
|
|
671
|
+
|
|
672
|
+
# kEnd for MainStreamsInfo
|
|
673
|
+
metadata << [PropertyId::K_END].pack("C")
|
|
674
|
+
|
|
675
|
+
# kEnd for Header
|
|
676
|
+
metadata << [PropertyId::K_END].pack("C")
|
|
677
|
+
|
|
678
|
+
metadata
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
def build_metadata_footer
|
|
682
|
+
footer = String.new(encoding: "BINARY")
|
|
683
|
+
|
|
684
|
+
# Single loop to add filename, attributes, and timestamps for each entry
|
|
685
|
+
@entries.each do |entry|
|
|
686
|
+
# Add filename in UTF-16LE with null terminator
|
|
687
|
+
entry.name.encode("UTF-16LE").each_byte do |byte|
|
|
688
|
+
footer << [byte].pack("C")
|
|
689
|
+
end
|
|
690
|
+
footer << NULL_TERMINATOR
|
|
691
|
+
|
|
692
|
+
# Add file attributes (Windows FILE attributes)
|
|
693
|
+
footer << FILE_ATTRIBUTE_ARCHIVE
|
|
694
|
+
|
|
695
|
+
# Add Windows FILETIME for modification time
|
|
696
|
+
unix_time = entry.mtime.to_i
|
|
697
|
+
windows_time = (unix_time + 11_644_473_600) * 10_000_000
|
|
698
|
+
footer << [windows_time].pack("Q<")
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
footer
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
def write_start_header(io, next_header_offset, next_header_size,
|
|
705
|
+
next_header_data)
|
|
706
|
+
header = String.new(encoding: "BINARY")
|
|
707
|
+
|
|
708
|
+
# Signature (6 bytes)
|
|
709
|
+
header << SIGNATURE
|
|
710
|
+
|
|
711
|
+
# Version (2 bytes)
|
|
712
|
+
header << [MAJOR_VERSION, MINOR_VERSION].pack("CC")
|
|
713
|
+
|
|
714
|
+
# Calculate CRC for next header info
|
|
715
|
+
next_header_info = String.new(encoding: "BINARY")
|
|
716
|
+
next_header_info << [next_header_offset].pack("Q<")
|
|
717
|
+
next_header_info << [next_header_size].pack("Q<")
|
|
718
|
+
|
|
719
|
+
# Calculate CRC for next header
|
|
720
|
+
crc = Omnizip::Checksums::Crc32.new
|
|
721
|
+
crc.update(next_header_data)
|
|
722
|
+
next_header_crc = crc.finalize
|
|
723
|
+
next_header_info << [next_header_crc].pack("V")
|
|
724
|
+
|
|
725
|
+
# Calculate CRC for next header info
|
|
726
|
+
info_crc = Omnizip::Checksums::Crc32.new
|
|
727
|
+
info_crc.update(next_header_info)
|
|
728
|
+
|
|
729
|
+
# Start header CRC (4 bytes)
|
|
730
|
+
header << [info_crc.finalize].pack("V")
|
|
731
|
+
|
|
732
|
+
# Next header info (20 bytes)
|
|
733
|
+
header << next_header_info
|
|
734
|
+
|
|
735
|
+
# Write at the beginning
|
|
736
|
+
io.seek(0)
|
|
737
|
+
io.write(header)
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
# Encode variable-length integer (7-Zip VLI format)
|
|
741
|
+
#
|
|
742
|
+
# 7-Zip VLI encoding uses the first byte's high bits to determine
|
|
743
|
+
# the number of additional bytes:
|
|
744
|
+
# 0xxxxxxx : value = xxxxxxx (0-127)
|
|
745
|
+
# 10xxxxxx BYTE y[1] : value = (xxxxxx << 8) + y
|
|
746
|
+
# 110xxxxx BYTE y[2] : value = (xxxxx << 16) + y
|
|
747
|
+
# 1110xxxx BYTE y[3] : value = (xxxx << 24) + y
|
|
748
|
+
# ...up to 8 bytes total
|
|
749
|
+
# Raw MSB-first packed bit bytes, no marker
|
|
750
|
+
def write_bit_bytes(bits)
|
|
751
|
+
bytes = Array.new((bits.size + 7) / 8, 0)
|
|
752
|
+
bits.each_with_index do |bit, i|
|
|
753
|
+
bytes[i / 8] |= (1 << (7 - (i % 8))) if bit
|
|
754
|
+
end
|
|
755
|
+
bytes.pack("C*")
|
|
756
|
+
end
|
|
757
|
+
|
|
758
|
+
# Bit vector with an all-defined marker byte, MSB-first
|
|
759
|
+
# packing (same layout as HeaderWriter's)
|
|
760
|
+
def write_bit_vector(bits)
|
|
761
|
+
return [1].pack("C") if bits.all?
|
|
762
|
+
|
|
763
|
+
bytes = Array.new((bits.size + 7) / 8, 0)
|
|
764
|
+
bits.each_with_index do |bit, i|
|
|
765
|
+
bytes[i / 8] |= (1 << (7 - (i % 8))) if bit
|
|
766
|
+
end
|
|
767
|
+
[0].pack("C") + bytes.pack("C*")
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
def write_number(value)
|
|
771
|
+
# Single byte encoding (0-127)
|
|
772
|
+
return [value].pack("C") if value < 0x80
|
|
773
|
+
|
|
774
|
+
# 7-Zip VLI encoding: extra bytes are little-endian per SDK ReadNumberSpec().
|
|
775
|
+
# Reference: https://py7zr.readthedocs.io/en/latest/archive_format.html
|
|
776
|
+
result = String.new(encoding: "BINARY")
|
|
777
|
+
|
|
778
|
+
# Determine how many extra bytes are needed
|
|
779
|
+
thresholds = [0x80, 0x4000, 0x20_0000, 0x1000_0000,
|
|
780
|
+
0x8_0000_0000, 0x400_0000_0000,
|
|
781
|
+
0x2_0000_0000_0000, 0x100_0000_0000_0000]
|
|
782
|
+
num_extra = thresholds.index { |t| value < t } || 8
|
|
783
|
+
|
|
784
|
+
# Build first byte: num_extra leading 1-bits, then 0-bit, then data bits
|
|
785
|
+
first_byte = 0
|
|
786
|
+
num_extra.times { |i| first_byte |= (0x80 >> i) }
|
|
787
|
+
data_bits_shift = 8 * num_extra
|
|
788
|
+
first_byte |= (value >> data_bits_shift) & ((0x80 >> num_extra) - 1)
|
|
789
|
+
result << [first_byte].pack("C")
|
|
790
|
+
|
|
791
|
+
# Extra bytes in little-endian order (low byte first)
|
|
792
|
+
num_extra.times do |i|
|
|
793
|
+
result << [(value >> (8 * i)) & 0xFF].pack("C")
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
result
|
|
797
|
+
end
|
|
798
|
+
end
|
|
799
|
+
end
|
|
800
|
+
end
|
|
801
|
+
end
|