omnizip 0.4.7-aarch64-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,626 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "set"
|
|
5
|
+
|
|
6
|
+
module Omnizip
|
|
7
|
+
module Formats
|
|
8
|
+
module SevenZip
|
|
9
|
+
# .7z archive reader
|
|
10
|
+
# Provides read-only access to .7z archives
|
|
11
|
+
class Reader
|
|
12
|
+
include Omnizip::Formats::SevenZip::Constants
|
|
13
|
+
|
|
14
|
+
attr_reader :file_path, :header, :entries, :stream_info, :split_reader
|
|
15
|
+
|
|
16
|
+
# Initialize reader with file path
|
|
17
|
+
#
|
|
18
|
+
# @param file_path [String] Path to .7z file
|
|
19
|
+
# @param options [Hash] Reader options
|
|
20
|
+
# @option options [String] :password Password for encrypted headers
|
|
21
|
+
# @option options [Integer] :offset Offset for embedded archives
|
|
22
|
+
def initialize(file_path, options = {})
|
|
23
|
+
@file_path = file_path
|
|
24
|
+
@entries = []
|
|
25
|
+
@stream_info = nil
|
|
26
|
+
@split_reader = nil
|
|
27
|
+
@password = options[:password]
|
|
28
|
+
@offset = options[:offset] || 0
|
|
29
|
+
@solid_cache = {}
|
|
30
|
+
@solid_cache_extracted = {}
|
|
31
|
+
@opened = false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Open and parse .7z archive
|
|
35
|
+
#
|
|
36
|
+
# @raise [RuntimeError] if file cannot be opened or parsed
|
|
37
|
+
def open
|
|
38
|
+
# Check if this is a split archive
|
|
39
|
+
if split_archive?
|
|
40
|
+
@split_reader = SplitArchiveReader.new(@file_path)
|
|
41
|
+
@split_reader.open
|
|
42
|
+
@header = @split_reader.header
|
|
43
|
+
@entries = @split_reader.entries
|
|
44
|
+
@stream_info = @split_reader.stream_info
|
|
45
|
+
else
|
|
46
|
+
File.open(@file_path, "rb") do |io|
|
|
47
|
+
# Seek to offset for embedded archives
|
|
48
|
+
io.seek(@offset) if @offset.positive?
|
|
49
|
+
parse_archive(io)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
self
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Check if archive is split
|
|
56
|
+
#
|
|
57
|
+
# @return [Boolean] true if split across multiple volumes
|
|
58
|
+
def split?
|
|
59
|
+
@split_reader&.split? || false
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Get total number of volumes (for split archives)
|
|
63
|
+
#
|
|
64
|
+
# @return [Integer] Number of volumes
|
|
65
|
+
def total_volumes
|
|
66
|
+
@split_reader&.total_volumes || 1
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Get volume size (for split archives)
|
|
70
|
+
#
|
|
71
|
+
# @return [Integer] Volume size in bytes
|
|
72
|
+
def volume_size
|
|
73
|
+
@split_reader&.volume_size || File.size(@file_path)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Get list of volumes (for split archives)
|
|
77
|
+
#
|
|
78
|
+
# @return [Array<String>] Volume paths
|
|
79
|
+
def volumes
|
|
80
|
+
@split_reader&.volumes || [@file_path]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# List all files in archive, parsing on first use — a Reader
|
|
84
|
+
# is always usable; #open remains for eager loading
|
|
85
|
+
#
|
|
86
|
+
# @return [Array<Models::FileEntry>] File entries
|
|
87
|
+
def list_files
|
|
88
|
+
ensure_open
|
|
89
|
+
@entries.reject { |e| e.name.nil? || e.name.empty? }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Extract file to output path
|
|
93
|
+
#
|
|
94
|
+
# @param entry_name [String] File name to extract
|
|
95
|
+
# @param output_path [String] Destination path
|
|
96
|
+
# @raise [RuntimeError] if entry not found or extraction fails
|
|
97
|
+
def extract_entry(entry_name, output_path)
|
|
98
|
+
ensure_open
|
|
99
|
+
# Delegate to split reader if available
|
|
100
|
+
if @split_reader
|
|
101
|
+
@split_reader.extract_entry(entry_name, output_path)
|
|
102
|
+
return
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
entry = @entries.find { |e| e.name == entry_name }
|
|
106
|
+
raise Errno::ENOENT, "Entry not found: #{entry_name}" unless entry
|
|
107
|
+
|
|
108
|
+
# Create directory if needed
|
|
109
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
|
110
|
+
|
|
111
|
+
# Extract file
|
|
112
|
+
if entry.directory?
|
|
113
|
+
FileUtils.mkdir_p(output_path)
|
|
114
|
+
elsif entry.has_stream?
|
|
115
|
+
File.open(@file_path, "rb") do |io|
|
|
116
|
+
# Seek to offset for embedded archives
|
|
117
|
+
io.seek(@offset) if @offset.positive?
|
|
118
|
+
data = extract_entry_data(io, entry)
|
|
119
|
+
File.binwrite(output_path, data)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Set timestamp if available
|
|
123
|
+
if entry.mtime
|
|
124
|
+
File.utime(entry.atime || entry.mtime || Time.now,
|
|
125
|
+
entry.mtime || Time.now,
|
|
126
|
+
output_path)
|
|
127
|
+
end
|
|
128
|
+
else
|
|
129
|
+
# Empty file
|
|
130
|
+
FileUtils.touch(output_path)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Extract all files to directory
|
|
135
|
+
#
|
|
136
|
+
# @param output_dir [String] Destination directory
|
|
137
|
+
# @raise [RuntimeError] on extraction error
|
|
138
|
+
def extract_all(output_dir)
|
|
139
|
+
ensure_open
|
|
140
|
+
FileUtils.mkdir_p(output_dir)
|
|
141
|
+
|
|
142
|
+
real_out = File.realpath(output_dir)
|
|
143
|
+
|
|
144
|
+
@entries.each do |entry|
|
|
145
|
+
next if entry.name.nil? || entry.name.empty?
|
|
146
|
+
|
|
147
|
+
output_path = File.join(real_out, entry.name)
|
|
148
|
+
expanded = File.expand_path(output_path)
|
|
149
|
+
unless expanded.start_with?("#{real_out}#{File::SEPARATOR}") || expanded == real_out
|
|
150
|
+
raise Omnizip::FormatError,
|
|
151
|
+
"Path traversal detected: #{entry.name}"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
extract_entry(entry.name, output_path)
|
|
155
|
+
end
|
|
156
|
+
ensure
|
|
157
|
+
@solid_cache.clear
|
|
158
|
+
@solid_cache_extracted.clear
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Check if archive is valid .7z format
|
|
162
|
+
#
|
|
163
|
+
# @return [Boolean] true if valid
|
|
164
|
+
def valid?
|
|
165
|
+
!@header.nil? && @header.valid?
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Check if headers are encrypted
|
|
169
|
+
#
|
|
170
|
+
# @return [Boolean] true if headers are encrypted
|
|
171
|
+
def encrypted?
|
|
172
|
+
!@encrypted_header.nil?
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Check if can decrypt headers (password provided)
|
|
176
|
+
#
|
|
177
|
+
# @return [Boolean] true if password available for encrypted headers
|
|
178
|
+
def can_decrypt?
|
|
179
|
+
encrypted? && !@password.nil?
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Extract raw entry payload from the archive. Public so converters
|
|
183
|
+
# and parallel extractors can reuse the stream-positioning +
|
|
184
|
+
# decompression logic.
|
|
185
|
+
#
|
|
186
|
+
# @param io [IO] Archive file handle
|
|
187
|
+
# @param entry [Models::FileEntry] Entry to extract
|
|
188
|
+
# @return [String] Extracted bytes
|
|
189
|
+
def extract_entry_data(io, entry)
|
|
190
|
+
extract_entry_data_private(io, entry)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
private
|
|
194
|
+
|
|
195
|
+
# Parse the archive once, on demand
|
|
196
|
+
def ensure_open
|
|
197
|
+
open unless @opened
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Parse .7z archive structure
|
|
201
|
+
#
|
|
202
|
+
# @param io [io] Input stream
|
|
203
|
+
def parse_archive(io)
|
|
204
|
+
# Read and validate start header
|
|
205
|
+
@header = Header.read(io)
|
|
206
|
+
|
|
207
|
+
# Read next header metadata
|
|
208
|
+
# NOTE: next_header_offset is from the END of the Start Header (byte 32)
|
|
209
|
+
# NOT from the end of the file
|
|
210
|
+
# For embedded archives, add offset to get absolute position
|
|
211
|
+
next_header_pos = @offset + Constants::START_HEADER_SIZE + @header.next_header_offset
|
|
212
|
+
io.seek(next_header_pos)
|
|
213
|
+
next_header_data = io.read(@header.next_header_size)
|
|
214
|
+
|
|
215
|
+
# Check if header is encoded (compressed or encrypted)
|
|
216
|
+
# ENCODED_HEADER (0x17) can mean compressed or encrypted
|
|
217
|
+
first_byte = next_header_data.getbyte(0)
|
|
218
|
+
if first_byte == PropertyId::ENCODED_HEADER
|
|
219
|
+
# Try to parse as encrypted first (if enough data and has structure)
|
|
220
|
+
if next_header_data.bytesize >= 54
|
|
221
|
+
begin
|
|
222
|
+
# Check if it's actually an encrypted header
|
|
223
|
+
EncryptedHeader.from_binary(next_header_data)
|
|
224
|
+
# If we got here, it's encrypted - try to decrypt
|
|
225
|
+
next_header_data = decrypt_header(next_header_data)
|
|
226
|
+
rescue Omnizip::PasswordError
|
|
227
|
+
# Decryption refused or failed — a password problem,
|
|
228
|
+
# never a compressed header
|
|
229
|
+
raise
|
|
230
|
+
rescue StandardError
|
|
231
|
+
# Not an encrypted header — it's a compressed header;
|
|
232
|
+
# decompress it using the encoded stream info
|
|
233
|
+
next_header_data = decompress_encoded_header(io,
|
|
234
|
+
next_header_data)
|
|
235
|
+
end
|
|
236
|
+
else
|
|
237
|
+
# Too short for encrypted header, must be compressed
|
|
238
|
+
next_header_data = decompress_encoded_header(io, next_header_data)
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Parse metadata
|
|
243
|
+
parser = Parser.new(next_header_data)
|
|
244
|
+
begin
|
|
245
|
+
@stream_info, @entries = parse_metadata(parser)
|
|
246
|
+
rescue StandardError => e
|
|
247
|
+
if @headers_decrypted
|
|
248
|
+
raise Omnizip::PasswordError, "Failed to decrypt headers: incorrect password " \
|
|
249
|
+
"or corrupted data (#{e.message})"
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
raise
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Map entries to their folders/streams
|
|
256
|
+
map_entries_to_streams
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Decrypt encrypted header
|
|
260
|
+
#
|
|
261
|
+
# @param encrypted_data [String] Encrypted header bytes
|
|
262
|
+
# @return [String] Decrypted header data
|
|
263
|
+
# @raise [RuntimeError] if password not provided
|
|
264
|
+
def decrypt_header(encrypted_data)
|
|
265
|
+
# Parse encrypted header structure
|
|
266
|
+
@encrypted_header = EncryptedHeader.from_binary(encrypted_data)
|
|
267
|
+
|
|
268
|
+
unless @password
|
|
269
|
+
raise Omnizip::PasswordError, "Archive headers are encrypted. Password required to access."
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Decrypt using password
|
|
273
|
+
encryptor = HeaderEncryptor.new(@password)
|
|
274
|
+
decrypted = encryptor.decrypt(
|
|
275
|
+
@encrypted_header.encrypted_data,
|
|
276
|
+
@encrypted_header.salt,
|
|
277
|
+
@encrypted_header.iv,
|
|
278
|
+
)
|
|
279
|
+
# AES-CBC 'succeeds' with any key and yields garbage; wrong
|
|
280
|
+
# passwords surface as later header-parse failures, so mark
|
|
281
|
+
# the archive and translate those into the password error
|
|
282
|
+
@headers_decrypted = true
|
|
283
|
+
decrypted
|
|
284
|
+
rescue OpenSSL::Cipher::CipherError => e
|
|
285
|
+
raise Omnizip::PasswordError, "Failed to decrypt headers: incorrect password (#{e.message})"
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Decompress encoded (compressed) header
|
|
289
|
+
#
|
|
290
|
+
# @param io [IO] Archive file handle
|
|
291
|
+
# @param encoded_data [String] Encoded header bytes
|
|
292
|
+
# @return [String] Decompressed header data
|
|
293
|
+
def decompress_encoded_header(io, encoded_data)
|
|
294
|
+
# Skip ENCODED_HEADER marker
|
|
295
|
+
parser = Parser.new(encoded_data[1..])
|
|
296
|
+
|
|
297
|
+
# Parse the streams info for the encoded header
|
|
298
|
+
stream_info = Models::StreamInfo.new
|
|
299
|
+
|
|
300
|
+
# Read streams info - can be either MAIN_STREAMS_INFO or direct stream properties
|
|
301
|
+
type = parser.read_byte
|
|
302
|
+
|
|
303
|
+
if type == PropertyId::MAIN_STREAMS_INFO
|
|
304
|
+
parse_streams_info(parser, stream_info)
|
|
305
|
+
elsif type == PropertyId::PACK_INFO
|
|
306
|
+
# Direct PackInfo without MAIN_STREAMS_INFO wrapper
|
|
307
|
+
parser.read_pack_info(stream_info)
|
|
308
|
+
|
|
309
|
+
# Read UNPACK_INFO
|
|
310
|
+
type = parser.read_byte
|
|
311
|
+
if type == PropertyId::UNPACK_INFO
|
|
312
|
+
parser.read_unpack_info(stream_info)
|
|
313
|
+
end
|
|
314
|
+
else
|
|
315
|
+
raise Omnizip::InvalidArchiveError, "Unexpected property in encoded header: 0x#{type.to_s(16)}"
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Decompress the header using the stream info
|
|
319
|
+
# For embedded archives, add offset to get absolute file position
|
|
320
|
+
pack_pos = @offset + @header.start_pos_after_header + stream_info.pack_pos
|
|
321
|
+
folder = stream_info.folders[0]
|
|
322
|
+
pack_size = stream_info.pack_sizes[0]
|
|
323
|
+
unpack_size = folder.uncompressed_size
|
|
324
|
+
|
|
325
|
+
decompressor = StreamDecompressor.new(io, folder, pack_pos, pack_size)
|
|
326
|
+
decompressor.decompress(unpack_size)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# Parse archive metadata
|
|
330
|
+
#
|
|
331
|
+
# @param parser [Parser] Parser instance
|
|
332
|
+
# @return [Array<StreamInfo, Array<Models::FileEntry>>] Parsed data
|
|
333
|
+
def parse_metadata(parser)
|
|
334
|
+
stream_info = Models::StreamInfo.new
|
|
335
|
+
entries = []
|
|
336
|
+
|
|
337
|
+
# Read main header
|
|
338
|
+
type = parser.read_byte
|
|
339
|
+
raise Omnizip::InvalidArchiveError, "Expected Header, got 0x#{type.to_s(16)}" unless
|
|
340
|
+
type == PropertyId::HEADER
|
|
341
|
+
|
|
342
|
+
# Parse header sections
|
|
343
|
+
until parser.eof?
|
|
344
|
+
prop_type = parser.read_byte
|
|
345
|
+
|
|
346
|
+
case prop_type
|
|
347
|
+
when PropertyId::MAIN_STREAMS_INFO
|
|
348
|
+
parse_streams_info(parser, stream_info)
|
|
349
|
+
when PropertyId::FILES_INFO
|
|
350
|
+
entries = parser.read_files_info
|
|
351
|
+
when PropertyId::K_END
|
|
352
|
+
break
|
|
353
|
+
else
|
|
354
|
+
# Skip unknown properties
|
|
355
|
+
parser.skip_data if !parser.eof? &&
|
|
356
|
+
parser.peek_byte != PropertyId::K_END
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
parser.read_byte if !parser.eof? &&
|
|
361
|
+
parser.peek_byte == PropertyId::K_END
|
|
362
|
+
|
|
363
|
+
[stream_info, entries]
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# Parse streams info section
|
|
367
|
+
#
|
|
368
|
+
# @param parser [Parser] Parser instance
|
|
369
|
+
# @param stream_info [Models::StreamInfo] Stream info to populate
|
|
370
|
+
def parse_streams_info(parser, stream_info)
|
|
371
|
+
until parser.eof?
|
|
372
|
+
prop_type = parser.read_byte
|
|
373
|
+
|
|
374
|
+
case prop_type
|
|
375
|
+
when PropertyId::PACK_INFO
|
|
376
|
+
parser.read_pack_info(stream_info)
|
|
377
|
+
when PropertyId::UNPACK_INFO
|
|
378
|
+
parser.read_unpack_info(stream_info)
|
|
379
|
+
when PropertyId::SUBSTREAMS_INFO
|
|
380
|
+
parser.read_substreams_info(stream_info)
|
|
381
|
+
when PropertyId::K_END
|
|
382
|
+
break
|
|
383
|
+
else
|
|
384
|
+
# Unknown property within streams_info - skip it
|
|
385
|
+
parser.skip_data if !parser.eof? && parser.peek_byte != PropertyId::K_END
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# Consume final K_END for MAIN_STREAMS_INFO section
|
|
390
|
+
parser.read_byte if !parser.eof? && parser.peek_byte == PropertyId::K_END
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# Map entries to their folders and streams
|
|
394
|
+
def map_entries_to_streams
|
|
395
|
+
return if @stream_info.nil?
|
|
396
|
+
|
|
397
|
+
stream_idx = 0
|
|
398
|
+
@entries.each_with_index do |entry, i|
|
|
399
|
+
next unless entry.has_stream?
|
|
400
|
+
|
|
401
|
+
# Find which folder this stream belongs to
|
|
402
|
+
folder_idx = 0
|
|
403
|
+
accumulated = 0
|
|
404
|
+
@stream_info.num_unpack_streams_in_folders.each_with_index do |num, fi|
|
|
405
|
+
if stream_idx < accumulated + num
|
|
406
|
+
folder_idx = fi
|
|
407
|
+
break
|
|
408
|
+
end
|
|
409
|
+
accumulated += num
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
entry.folder_index = folder_idx
|
|
413
|
+
entry.file_index = i
|
|
414
|
+
entry.size = @stream_info.unpack_sizes[stream_idx] if @stream_info.unpack_sizes[stream_idx]
|
|
415
|
+
|
|
416
|
+
# Single-stream folders own their packed bytes outright,
|
|
417
|
+
# so the entry's compressed size is exact. Solid folders
|
|
418
|
+
# share packed bytes across streams — leave nil rather
|
|
419
|
+
# than fabricate a split.
|
|
420
|
+
num_in_folder = @stream_info.num_unpack_streams_in_folders[folder_idx]
|
|
421
|
+
if num_in_folder == 1
|
|
422
|
+
folder = @stream_info.folders[folder_idx]
|
|
423
|
+
packed = folder.pack_stream_indices.sum do |pi|
|
|
424
|
+
@stream_info.pack_sizes[pi] || 0
|
|
425
|
+
end
|
|
426
|
+
entry.compressed_size = packed
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
stream_idx += 1
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
# Extract entry data (private implementation)
|
|
434
|
+
#
|
|
435
|
+
# @param io [IO] Archive file handle
|
|
436
|
+
# @param entry [Models::FileEntry] Entry to extract
|
|
437
|
+
# @return [String] Extracted data
|
|
438
|
+
def extract_entry_data_private(io, entry)
|
|
439
|
+
return "" unless entry.has_stream?
|
|
440
|
+
return "" unless @stream_info
|
|
441
|
+
|
|
442
|
+
folder = @stream_info.folders[entry.folder_index]
|
|
443
|
+
return "" unless folder
|
|
444
|
+
|
|
445
|
+
# Calculate pack position (add offset for embedded archives)
|
|
446
|
+
pack_pos = @offset + @header.start_pos_after_header +
|
|
447
|
+
@stream_info.pack_pos
|
|
448
|
+
|
|
449
|
+
# Get pack sizes for this folder
|
|
450
|
+
pack_idx = 0
|
|
451
|
+
entry.folder_index.times do |i|
|
|
452
|
+
num_streams = @stream_info.folders[i].pack_stream_indices.size
|
|
453
|
+
pack_idx += num_streams
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
# Pack streams are laid out sequentially; skip past every
|
|
457
|
+
# stream owned by earlier folders to reach this folder's
|
|
458
|
+
# bytes (with stored archives pack sizes equal unpack
|
|
459
|
+
# sizes, which masked this offset in the past)
|
|
460
|
+
pack_idx.times { |i| pack_pos += @stream_info.pack_sizes[i] || 0 }
|
|
461
|
+
|
|
462
|
+
# Check if this is a BCJ2 multi-stream folder
|
|
463
|
+
if Bcj2StreamDecompressor.bcj2_folder?(folder)
|
|
464
|
+
extract_bcj2_entry(io, entry, folder, pack_pos, pack_idx)
|
|
465
|
+
else
|
|
466
|
+
extract_regular_entry(io, entry, folder, pack_pos, pack_idx)
|
|
467
|
+
end
|
|
468
|
+
rescue StandardError => e
|
|
469
|
+
warn "Extraction failed for #{entry.name}: #{e.message}"
|
|
470
|
+
raise
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# Extract entry from BCJ2 multi-stream folder
|
|
474
|
+
#
|
|
475
|
+
# @param io [IO] Archive file handle
|
|
476
|
+
# @param entry [Models::FileEntry] Entry to extract
|
|
477
|
+
# @param folder [Models::Folder] Folder specification
|
|
478
|
+
# @param pack_pos [Integer] Base pack position
|
|
479
|
+
# @param pack_idx [Integer] Starting pack index
|
|
480
|
+
# @return [String] Extracted data
|
|
481
|
+
def extract_bcj2_entry(io, entry, folder, pack_pos, pack_idx)
|
|
482
|
+
# Cache solid block to avoid re-decompressing for every file
|
|
483
|
+
full_data = @solid_cache[entry.folder_index]
|
|
484
|
+
|
|
485
|
+
unless full_data
|
|
486
|
+
# BCJ2 folders have multiple pack streams
|
|
487
|
+
num_pack_streams = folder.pack_stream_indices.size
|
|
488
|
+
pack_sizes = Array.new(num_pack_streams) do |i|
|
|
489
|
+
@stream_info.pack_sizes[pack_idx + i] || 0
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# Decompress the entire BCJ2 folder
|
|
493
|
+
decompressor = Bcj2StreamDecompressor.new(
|
|
494
|
+
io, folder, pack_pos, pack_sizes, @stream_info
|
|
495
|
+
)
|
|
496
|
+
full_data = decompressor.decompress(folder.uncompressed_size)
|
|
497
|
+
|
|
498
|
+
# Cache for other entries in the same folder
|
|
499
|
+
num_files_in_folder = @stream_info.num_unpack_streams_in_folders[entry.folder_index] || 1
|
|
500
|
+
if num_files_in_folder > 1
|
|
501
|
+
@solid_cache[entry.folder_index] = full_data
|
|
502
|
+
@solid_cache_extracted[entry.folder_index] = Set.new
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
# For solid archives, extract this file's portion
|
|
507
|
+
num_files_in_folder = @stream_info.num_unpack_streams_in_folders[entry.folder_index] || 1
|
|
508
|
+
|
|
509
|
+
if num_files_in_folder > 1
|
|
510
|
+
# Find offset of this file within the uncompressed stream
|
|
511
|
+
file_offset = 0
|
|
512
|
+
@entries.each do |e|
|
|
513
|
+
break if e.file_index == entry.file_index
|
|
514
|
+
|
|
515
|
+
file_offset += e.size if e.has_stream? && e.folder_index == entry.folder_index
|
|
516
|
+
end
|
|
517
|
+
data = full_data[file_offset, entry.size]
|
|
518
|
+
else
|
|
519
|
+
data = full_data[0, entry.size]
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
# Verify CRC if available
|
|
523
|
+
if entry.crc
|
|
524
|
+
crc = Omnizip::Checksums::Crc32.new
|
|
525
|
+
crc.update(data)
|
|
526
|
+
unless crc.value == entry.crc
|
|
527
|
+
raise Omnizip::ChecksumError, "CRC mismatch for #{entry.name}: expected 0x#{entry.crc.to_s(16)}, got 0x#{crc.value.to_s(16)}"
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
# Evict solid cache entry when all files in folder have been extracted
|
|
532
|
+
if @solid_cache_extracted.key?(entry.folder_index)
|
|
533
|
+
@solid_cache_extracted[entry.folder_index].add(entry.file_index)
|
|
534
|
+
if @solid_cache_extracted[entry.folder_index].size >= num_files_in_folder
|
|
535
|
+
@solid_cache.delete(entry.folder_index)
|
|
536
|
+
@solid_cache_extracted.delete(entry.folder_index)
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
data
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# Extract entry from regular (non-BCJ2) folder
|
|
544
|
+
#
|
|
545
|
+
# @param io [IO] Archive file handle
|
|
546
|
+
# @param entry [Models::FileEntry] Entry to extract
|
|
547
|
+
# @param folder [Models::Folder] Folder specification
|
|
548
|
+
# @param pack_pos [Integer] Base pack position
|
|
549
|
+
# @param pack_idx [Integer] Starting pack index
|
|
550
|
+
# @return [String] Extracted data
|
|
551
|
+
def extract_regular_entry(io, entry, folder, pack_pos, pack_idx)
|
|
552
|
+
pack_size = @stream_info.pack_sizes[pack_idx] || 0
|
|
553
|
+
|
|
554
|
+
# For solid archives, multiple files share one compressed stream
|
|
555
|
+
# We need to decompress the entire folder and extract the correct portion
|
|
556
|
+
num_files_in_folder = @stream_info.num_unpack_streams_in_folders[entry.folder_index] || 1
|
|
557
|
+
|
|
558
|
+
if num_files_in_folder > 1
|
|
559
|
+
# Cache solid block to avoid re-decompressing for every file
|
|
560
|
+
full_data = @solid_cache[entry.folder_index]
|
|
561
|
+
|
|
562
|
+
unless full_data
|
|
563
|
+
total_unpack_size = folder.uncompressed_size
|
|
564
|
+
decompressor = StreamDecompressor.new(io, folder, pack_pos,
|
|
565
|
+
pack_size)
|
|
566
|
+
full_data = decompressor.decompress(total_unpack_size)
|
|
567
|
+
@solid_cache[entry.folder_index] = full_data
|
|
568
|
+
@solid_cache_extracted[entry.folder_index] = Set.new
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
# Find offset of this file within the uncompressed stream
|
|
572
|
+
file_offset = 0
|
|
573
|
+
@entries.each do |e|
|
|
574
|
+
break if e.file_index == entry.file_index
|
|
575
|
+
|
|
576
|
+
file_offset += e.size if e.has_stream? && e.folder_index == entry.folder_index
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
# Extract this file's data
|
|
580
|
+
data = full_data[file_offset, entry.size]
|
|
581
|
+
|
|
582
|
+
# Verify CRC if available
|
|
583
|
+
if entry.crc
|
|
584
|
+
crc = Omnizip::Checksums::Crc32.new
|
|
585
|
+
crc.update(data)
|
|
586
|
+
unless crc.value == entry.crc
|
|
587
|
+
raise Omnizip::ChecksumError, "CRC mismatch for #{entry.name}: expected 0x#{entry.crc.to_s(16)}, got 0x#{crc.value.to_s(16)}"
|
|
588
|
+
end
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
# Evict solid cache entry when all files in folder have been extracted
|
|
592
|
+
if @solid_cache_extracted.key?(entry.folder_index)
|
|
593
|
+
@solid_cache_extracted[entry.folder_index].add(entry.file_index)
|
|
594
|
+
if @solid_cache_extracted[entry.folder_index].size >= num_files_in_folder
|
|
595
|
+
@solid_cache.delete(entry.folder_index)
|
|
596
|
+
@solid_cache_extracted.delete(entry.folder_index)
|
|
597
|
+
end
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
data
|
|
601
|
+
else
|
|
602
|
+
# Non-solid: each file has its own compressed stream
|
|
603
|
+
decompressor = StreamDecompressor.new(io, folder, pack_pos,
|
|
604
|
+
pack_size)
|
|
605
|
+
expected_crc = entry.crc
|
|
606
|
+
decompressor.decompress_and_verify(entry.size, expected_crc)
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
# Check if file path indicates a split archive
|
|
611
|
+
#
|
|
612
|
+
# @return [Boolean] true if appears to be split
|
|
613
|
+
def split_archive?
|
|
614
|
+
# Numeric pattern: .001, .002, etc.
|
|
615
|
+
return true if /\.\d{3}$/.match?(@file_path)
|
|
616
|
+
|
|
617
|
+
# Alpha pattern: .7z.aa, .tar.ab, etc. (but not .7z, .tar, .zip alone)
|
|
618
|
+
# Must have a known archive extension followed by alpha suffix
|
|
619
|
+
return true if /\.(7z|tar|zip|rar)\.[a-z]{2,}$/.match?(@file_path)
|
|
620
|
+
|
|
621
|
+
false
|
|
622
|
+
end
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
end
|
|
626
|
+
end
|