omnizip 0.3.1
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 +754 -0
- data/COPYING +502 -0
- data/Gemfile +17 -0
- data/LICENSE +12 -0
- data/README.adoc +1045 -0
- data/Rakefile +12 -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 +91 -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 +271 -0
- data/docs/getting-started/index.adoc +42 -0
- data/docs/getting-started/installation.adoc +138 -0
- data/docs/getting-started/quick-start.adoc +185 -0
- data/docs/getting-started/your-first-archive.adoc +218 -0
- data/docs/guides/advanced-features/encryption.adoc +300 -0
- data/docs/guides/advanced-features/index.adoc +49 -0
- data/docs/guides/advanced-features/parallel-processing.adoc +246 -0
- data/docs/guides/advanced-features/progress-tracking.adoc +320 -0
- data/docs/guides/advanced-features/streaming.adoc +212 -0
- data/docs/guides/archive-formats/gzip-format.adoc +107 -0
- data/docs/guides/archive-formats/index.adoc +130 -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/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 +113 -0
- data/docs/guides/compression-algorithms/deflate.adoc +319 -0
- data/docs/guides/compression-algorithms/index.adoc +190 -0
- data/docs/guides/compression-algorithms/lzma.adoc +398 -0
- data/docs/guides/compression-algorithms/lzma2.adoc +327 -0
- data/docs/guides/compression-algorithms/ppmd.adoc +316 -0
- data/docs/guides/compression-algorithms/zstandard.adoc +361 -0
- data/docs/guides/creating-archives.adoc +354 -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 +218 -0
- data/docs/lychee.toml +67 -0
- data/docs/reference/api/overview.adoc +188 -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/troubleshooting/index.adoc +208 -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 +130 -0
- data/lib/omnizip/algorithm_registry.rb +86 -0
- data/lib/omnizip/algorithms/.keep +0 -0
- data/lib/omnizip/algorithms/bzip2/bwt.rb +225 -0
- data/lib/omnizip/algorithms/bzip2/decoder.rb +193 -0
- data/lib/omnizip/algorithms/bzip2/encoder.rb +237 -0
- data/lib/omnizip/algorithms/bzip2/huffman.rb +206 -0
- data/lib/omnizip/algorithms/bzip2/mtf.rb +101 -0
- data/lib/omnizip/algorithms/bzip2/rle.rb +151 -0
- data/lib/omnizip/algorithms/bzip2.rb +130 -0
- data/lib/omnizip/algorithms/deflate/constants.rb +28 -0
- data/lib/omnizip/algorithms/deflate/decoder.rb +38 -0
- data/lib/omnizip/algorithms/deflate/encoder.rb +46 -0
- data/lib/omnizip/algorithms/deflate.rb +128 -0
- data/lib/omnizip/algorithms/deflate64/constants.rb +45 -0
- data/lib/omnizip/algorithms/deflate64/decoder.rb +153 -0
- data/lib/omnizip/algorithms/deflate64/encoder.rb +98 -0
- data/lib/omnizip/algorithms/deflate64/huffman_coder.rb +354 -0
- data/lib/omnizip/algorithms/deflate64/lz77_encoder.rb +142 -0
- data/lib/omnizip/algorithms/deflate64.rb +109 -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 +148 -0
- data/lib/omnizip/algorithms/lzma/dictionary.rb +69 -0
- data/lib/omnizip/algorithms/lzma/distance_coder.rb +415 -0
- data/lib/omnizip/algorithms/lzma/encoder.rb +142 -0
- data/lib/omnizip/algorithms/lzma/length_coder.rb +260 -0
- data/lib/omnizip/algorithms/lzma/literal_decoder.rb +320 -0
- data/lib/omnizip/algorithms/lzma/literal_encoder.rb +210 -0
- data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +341 -0
- data/lib/omnizip/algorithms/lzma/lzma_alone_decoder.rb +192 -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 +205 -0
- data/lib/omnizip/algorithms/lzma/match_finder_config.rb +142 -0
- data/lib/omnizip/algorithms/lzma/match_finder_factory.rb +88 -0
- data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +130 -0
- data/lib/omnizip/algorithms/lzma/probability_models.rb +72 -0
- data/lib/omnizip/algorithms/lzma/range_coder.rb +85 -0
- data/lib/omnizip/algorithms/lzma/range_decoder.rb +434 -0
- data/lib/omnizip/algorithms/lzma/range_encoder.rb +194 -0
- data/lib/omnizip/algorithms/lzma/state.rb +127 -0
- data/lib/omnizip/algorithms/lzma/xz_buffered_range_encoder.rb +325 -0
- data/lib/omnizip/algorithms/lzma/xz_encoder.rb +426 -0
- data/lib/omnizip/algorithms/lzma/xz_encoder_fast.rb +645 -0
- data/lib/omnizip/algorithms/lzma/xz_match_finder_adapter.rb +227 -0
- data/lib/omnizip/algorithms/lzma/xz_price_calculator.rb +169 -0
- data/lib/omnizip/algorithms/lzma/xz_probability_models.rb +261 -0
- data/lib/omnizip/algorithms/lzma/xz_range_encoder.rb +223 -0
- data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +331 -0
- data/lib/omnizip/algorithms/lzma/xz_state.rb +116 -0
- data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +2055 -0
- data/lib/omnizip/algorithms/lzma.rb +238 -0
- data/lib/omnizip/algorithms/lzma2/chunk_manager.rb +182 -0
- data/lib/omnizip/algorithms/lzma2/constants.rb +41 -0
- data/lib/omnizip/algorithms/lzma2/encoder.rb +147 -0
- data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +161 -0
- data/lib/omnizip/algorithms/lzma2/properties.rb +179 -0
- data/lib/omnizip/algorithms/lzma2/simple_lzma2_encoder.rb +127 -0
- data/lib/omnizip/algorithms/lzma2/xz_encoder_adapter.rb +85 -0
- data/lib/omnizip/algorithms/lzma2.rb +141 -0
- data/lib/omnizip/algorithms/ppmd7/constants.rb +74 -0
- data/lib/omnizip/algorithms/ppmd7/context.rb +154 -0
- data/lib/omnizip/algorithms/ppmd7/decoder.rb +126 -0
- data/lib/omnizip/algorithms/ppmd7/encoder.rb +163 -0
- data/lib/omnizip/algorithms/ppmd7/model.rb +248 -0
- data/lib/omnizip/algorithms/ppmd7/symbol_state.rb +57 -0
- data/lib/omnizip/algorithms/ppmd7.rb +116 -0
- data/lib/omnizip/algorithms/ppmd8/constants.rb +61 -0
- data/lib/omnizip/algorithms/ppmd8/context.rb +34 -0
- data/lib/omnizip/algorithms/ppmd8/decoder.rb +107 -0
- data/lib/omnizip/algorithms/ppmd8/encoder.rb +138 -0
- data/lib/omnizip/algorithms/ppmd8/model.rb +250 -0
- data/lib/omnizip/algorithms/ppmd8/restoration_method.rb +78 -0
- data/lib/omnizip/algorithms/ppmd8.rb +82 -0
- data/lib/omnizip/algorithms/ppmd_base.rb +138 -0
- data/lib/omnizip/algorithms/sevenzip_lzma2.rb +123 -0
- data/lib/omnizip/algorithms/xz_lzma2.rb +118 -0
- data/lib/omnizip/algorithms/zstandard/constants.rb +25 -0
- data/lib/omnizip/algorithms/zstandard/decoder.rb +46 -0
- data/lib/omnizip/algorithms/zstandard/encoder.rb +51 -0
- data/lib/omnizip/algorithms/zstandard.rb +138 -0
- data/lib/omnizip/buffer/memory_archive.rb +251 -0
- data/lib/omnizip/buffer/memory_extractor.rb +224 -0
- data/lib/omnizip/buffer.rb +176 -0
- data/lib/omnizip/checksum_registry.rb +114 -0
- data/lib/omnizip/checksums/crc32.rb +100 -0
- data/lib/omnizip/checksums/crc64.rb +101 -0
- data/lib/omnizip/checksums/crc_base.rb +158 -0
- data/lib/omnizip/checksums/verifier.rb +131 -0
- data/lib/omnizip/chunked/memory_manager.rb +194 -0
- data/lib/omnizip/chunked/reader.rb +78 -0
- data/lib/omnizip/chunked/writer.rb +120 -0
- data/lib/omnizip/chunked.rb +129 -0
- data/lib/omnizip/cli/output_formatter.rb +104 -0
- data/lib/omnizip/cli.rb +572 -0
- data/lib/omnizip/commands/.keep +0 -0
- data/lib/omnizip/commands/archive_create_command.rb +427 -0
- data/lib/omnizip/commands/archive_extract_command.rb +272 -0
- data/lib/omnizip/commands/archive_list_command.rb +218 -0
- data/lib/omnizip/commands/archive_repair_command.rb +131 -0
- data/lib/omnizip/commands/archive_verify_command.rb +117 -0
- data/lib/omnizip/commands/compress_command.rb +117 -0
- data/lib/omnizip/commands/decompress_command.rb +120 -0
- data/lib/omnizip/commands/list_command.rb +53 -0
- data/lib/omnizip/commands/metadata_command.rb +153 -0
- data/lib/omnizip/commands/parity_create_command.rb +122 -0
- data/lib/omnizip/commands/parity_repair_command.rb +122 -0
- data/lib/omnizip/commands/parity_verify_command.rb +124 -0
- data/lib/omnizip/commands/profile_list_command.rb +56 -0
- data/lib/omnizip/commands/profile_show_command.rb +44 -0
- data/lib/omnizip/convenience.rb +359 -0
- data/lib/omnizip/converter/conversion_registry.rb +49 -0
- data/lib/omnizip/converter/conversion_strategy.rb +121 -0
- data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +97 -0
- data/lib/omnizip/converter/zip_to_seven_zip_strategy.rb +112 -0
- data/lib/omnizip/converter.rb +105 -0
- data/lib/omnizip/crypto/aes256/cipher.rb +100 -0
- data/lib/omnizip/crypto/aes256/constants.rb +28 -0
- data/lib/omnizip/crypto/aes256/key_derivation.rb +101 -0
- data/lib/omnizip/crypto/aes256.rb +102 -0
- data/lib/omnizip/error.rb +106 -0
- data/lib/omnizip/eta/exponential_smoothing_estimator.rb +98 -0
- data/lib/omnizip/eta/moving_average_estimator.rb +99 -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 +63 -0
- data/lib/omnizip/extraction/filter_chain.rb +177 -0
- data/lib/omnizip/extraction/glob_pattern.rb +140 -0
- data/lib/omnizip/extraction/pattern_matcher.rb +70 -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 +240 -0
- data/lib/omnizip/extraction.rb +111 -0
- data/lib/omnizip/file_type/mime_classifier.rb +144 -0
- data/lib/omnizip/file_type.rb +113 -0
- data/lib/omnizip/filter.rb +139 -0
- data/lib/omnizip/filter_pipeline.rb +108 -0
- data/lib/omnizip/filter_registry.rb +166 -0
- data/lib/omnizip/filters/bcj.rb +279 -0
- data/lib/omnizip/filters/bcj2/constants.rb +53 -0
- data/lib/omnizip/filters/bcj2/decoder.rb +200 -0
- data/lib/omnizip/filters/bcj2/encoder.rb +61 -0
- data/lib/omnizip/filters/bcj2/stream_data.rb +93 -0
- data/lib/omnizip/filters/bcj2.rb +99 -0
- data/lib/omnizip/filters/bcj_arm.rb +176 -0
- data/lib/omnizip/filters/bcj_arm64.rb +244 -0
- data/lib/omnizip/filters/bcj_ia64.rb +196 -0
- data/lib/omnizip/filters/bcj_ppc.rb +190 -0
- data/lib/omnizip/filters/bcj_sparc.rb +176 -0
- data/lib/omnizip/filters/bcj_x86.rb +193 -0
- data/lib/omnizip/filters/delta.rb +196 -0
- data/lib/omnizip/filters/filter_base.rb +72 -0
- data/lib/omnizip/filters/registry.rb +123 -0
- data/lib/omnizip/filters/xz_delta.rb +258 -0
- data/lib/omnizip/format_detector.rb +162 -0
- data/lib/omnizip/format_registry.rb +59 -0
- data/lib/omnizip/formats/.keep +0 -0
- data/lib/omnizip/formats/bzip2_file.rb +172 -0
- data/lib/omnizip/formats/cpio/constants.rb +55 -0
- data/lib/omnizip/formats/cpio/entry.rb +385 -0
- data/lib/omnizip/formats/cpio/reader.rb +196 -0
- data/lib/omnizip/formats/cpio/writer.rb +234 -0
- data/lib/omnizip/formats/cpio.rb +140 -0
- data/lib/omnizip/formats/format_spec_loader.rb +230 -0
- data/lib/omnizip/formats/gzip.rb +238 -0
- data/lib/omnizip/formats/iso/directory_builder.rb +297 -0
- data/lib/omnizip/formats/iso/directory_record.rb +152 -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 +197 -0
- data/lib/omnizip/formats/iso/rock_ridge.rb +349 -0
- data/lib/omnizip/formats/iso/volume_builder.rb +320 -0
- data/lib/omnizip/formats/iso/volume_descriptor.rb +168 -0
- data/lib/omnizip/formats/iso/writer.rb +530 -0
- data/lib/omnizip/formats/iso.rb +140 -0
- data/lib/omnizip/formats/lzip.rb +175 -0
- data/lib/omnizip/formats/lzma_alone.rb +171 -0
- data/lib/omnizip/formats/rar/archive_repairer.rb +243 -0
- data/lib/omnizip/formats/rar/archive_verifier.rb +195 -0
- data/lib/omnizip/formats/rar/block_parser.rb +243 -0
- data/lib/omnizip/formats/rar/compression/bit_stream.rb +180 -0
- data/lib/omnizip/formats/rar/compression/dispatcher.rb +217 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/decoder.rb +216 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/encoder.rb +158 -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/ppmd/context.rb +105 -0
- data/lib/omnizip/formats/rar/compression/ppmd/decoder.rb +219 -0
- data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +262 -0
- data/lib/omnizip/formats/rar/compression_method_registry.rb +106 -0
- data/lib/omnizip/formats/rar/constants.rb +82 -0
- data/lib/omnizip/formats/rar/decompressor.rb +238 -0
- data/lib/omnizip/formats/rar/external_writer.rb +312 -0
- data/lib/omnizip/formats/rar/header.rb +192 -0
- data/lib/omnizip/formats/rar/license_validator.rb +109 -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/parity_handler.rb +292 -0
- data/lib/omnizip/formats/rar/rar5/compression/lzma.rb +202 -0
- data/lib/omnizip/formats/rar/rar5/compression/lzss.rb +578 -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 +97 -0
- data/lib/omnizip/formats/rar/rar5/encryption/encryption_header.rb +114 -0
- data/lib/omnizip/formats/rar/rar5/encryption/encryption_manager.rb +166 -0
- data/lib/omnizip/formats/rar/rar5/encryption/key_derivation.rb +97 -0
- data/lib/omnizip/formats/rar/rar5/header.rb +187 -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 +264 -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 +194 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_encoder.rb +109 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_manager.rb +142 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_stream.rb +121 -0
- data/lib/omnizip/formats/rar/rar5/vint.rb +65 -0
- data/lib/omnizip/formats/rar/rar5/writer.rb +466 -0
- data/lib/omnizip/formats/rar/rar_format_base.rb +241 -0
- data/lib/omnizip/formats/rar/reader.rb +366 -0
- data/lib/omnizip/formats/rar/recovery_record.rb +245 -0
- data/lib/omnizip/formats/rar/volume_manager.rb +168 -0
- data/lib/omnizip/formats/rar/writer.rb +431 -0
- data/lib/omnizip/formats/rar.rb +205 -0
- data/lib/omnizip/formats/rar3/compressor.rb +73 -0
- data/lib/omnizip/formats/rar3/decompressor.rb +66 -0
- data/lib/omnizip/formats/rar3/reader.rb +386 -0
- data/lib/omnizip/formats/rar3/writer.rb +219 -0
- data/lib/omnizip/formats/rar5/compressor.rb +73 -0
- data/lib/omnizip/formats/rar5/decompressor.rb +66 -0
- data/lib/omnizip/formats/rar5/reader.rb +342 -0
- data/lib/omnizip/formats/rar5/writer.rb +214 -0
- data/lib/omnizip/formats/seven_zip/coder_chain.rb +150 -0
- data/lib/omnizip/formats/seven_zip/constants.rb +126 -0
- data/lib/omnizip/formats/seven_zip/encoded_header.rb +114 -0
- data/lib/omnizip/formats/seven_zip/encrypted_header.rb +142 -0
- data/lib/omnizip/formats/seven_zip/file_collector.rb +144 -0
- data/lib/omnizip/formats/seven_zip/header.rb +106 -0
- data/lib/omnizip/formats/seven_zip/header_encryptor.rb +134 -0
- data/lib/omnizip/formats/seven_zip/header_writer.rb +466 -0
- data/lib/omnizip/formats/seven_zip/models/coder_info.rb +30 -0
- data/lib/omnizip/formats/seven_zip/models/file_entry.rb +58 -0
- data/lib/omnizip/formats/seven_zip/models/folder.rb +69 -0
- data/lib/omnizip/formats/seven_zip/models/stream_info.rb +42 -0
- data/lib/omnizip/formats/seven_zip/parser.rb +660 -0
- data/lib/omnizip/formats/seven_zip/reader.rb +458 -0
- data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +632 -0
- data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +315 -0
- data/lib/omnizip/formats/seven_zip/stream_compressor.rb +151 -0
- data/lib/omnizip/formats/seven_zip/stream_decompressor.rb +162 -0
- data/lib/omnizip/formats/seven_zip/writer.rb +740 -0
- data/lib/omnizip/formats/seven_zip.rb +93 -0
- data/lib/omnizip/formats/tar/constants.rb +73 -0
- data/lib/omnizip/formats/tar/entry.rb +94 -0
- data/lib/omnizip/formats/tar/header.rb +168 -0
- data/lib/omnizip/formats/tar/reader.rb +121 -0
- data/lib/omnizip/formats/tar/writer.rb +216 -0
- data/lib/omnizip/formats/tar.rb +84 -0
- data/lib/omnizip/formats/xz/reader.rb +116 -0
- data/lib/omnizip/formats/xz.rb +237 -0
- data/lib/omnizip/formats/xz_impl/block_decoder.rb +754 -0
- data/lib/omnizip/formats/xz_impl/block_encoder.rb +306 -0
- data/lib/omnizip/formats/xz_impl/block_header.rb +210 -0
- data/lib/omnizip/formats/xz_impl/block_header_parser.rb +186 -0
- data/lib/omnizip/formats/xz_impl/constants.rb +49 -0
- data/lib/omnizip/formats/xz_impl/index_decoder.rb +174 -0
- data/lib/omnizip/formats/xz_impl/index_encoder.rb +122 -0
- data/lib/omnizip/formats/xz_impl/stream_decoder.rb +468 -0
- data/lib/omnizip/formats/xz_impl/stream_encoder.rb +99 -0
- data/lib/omnizip/formats/xz_impl/stream_footer.rb +81 -0
- data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +117 -0
- data/lib/omnizip/formats/xz_impl/stream_header.rb +55 -0
- data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +108 -0
- data/lib/omnizip/formats/xz_impl/vli.rb +128 -0
- data/lib/omnizip/formats/xz_impl/writer.rb +421 -0
- data/lib/omnizip/formats/zip/central_directory_header.rb +195 -0
- data/lib/omnizip/formats/zip/constants.rb +69 -0
- data/lib/omnizip/formats/zip/end_of_central_directory.rb +133 -0
- data/lib/omnizip/formats/zip/local_file_header.rb +138 -0
- data/lib/omnizip/formats/zip/reader.rb +250 -0
- data/lib/omnizip/formats/zip/unix_extra_field.rb +153 -0
- data/lib/omnizip/formats/zip/writer.rb +375 -0
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +104 -0
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +66 -0
- data/lib/omnizip/formats/zip/zip64_extra_field.rb +114 -0
- data/lib/omnizip/formats/zip.rb +50 -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/seven_zip/lzma/decoder.rb +421 -0
- data/lib/omnizip/implementations/seven_zip/lzma/encoder.rb +465 -0
- data/lib/omnizip/implementations/seven_zip/lzma/match_finder.rb +288 -0
- data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +200 -0
- data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +197 -0
- data/lib/omnizip/implementations/seven_zip/lzma/state_machine.rb +141 -0
- data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +519 -0
- data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +723 -0
- data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +750 -0
- data/lib/omnizip/io/buffered_input.rb +146 -0
- data/lib/omnizip/io/buffered_output.rb +105 -0
- data/lib/omnizip/io/stream_manager.rb +115 -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 +124 -0
- data/lib/omnizip/metadata/archive_metadata.rb +114 -0
- data/lib/omnizip/metadata/entry_metadata.rb +146 -0
- data/lib/omnizip/metadata/metadata_editor.rb +171 -0
- data/lib/omnizip/metadata/metadata_registry.rb +64 -0
- data/lib/omnizip/metadata/metadata_validator.rb +99 -0
- data/lib/omnizip/metadata.rb +57 -0
- data/lib/omnizip/models/.keep +0 -0
- data/lib/omnizip/models/algorithm_metadata.rb +73 -0
- data/lib/omnizip/models/compression_options.rb +71 -0
- data/lib/omnizip/models/conversion_options.rb +87 -0
- data/lib/omnizip/models/conversion_result.rb +135 -0
- data/lib/omnizip/models/eta_result.rb +46 -0
- data/lib/omnizip/models/extraction_rule.rb +115 -0
- data/lib/omnizip/models/filter_chain.rb +144 -0
- data/lib/omnizip/models/filter_config.rb +183 -0
- data/lib/omnizip/models/match_result.rb +124 -0
- data/lib/omnizip/models/optimization_suggestion.rb +91 -0
- data/lib/omnizip/models/parallel_options.rb +104 -0
- data/lib/omnizip/models/performance_result.rb +79 -0
- data/lib/omnizip/models/profile_report.rb +82 -0
- data/lib/omnizip/models/progress_options.rb +38 -0
- data/lib/omnizip/models/split_options.rb +116 -0
- data/lib/omnizip/optimization_registry.rb +81 -0
- data/lib/omnizip/parallel/job_queue.rb +209 -0
- data/lib/omnizip/parallel/job_scheduler.rb +203 -0
- data/lib/omnizip/parallel/parallel_compressor.rb +347 -0
- data/lib/omnizip/parallel/parallel_extractor.rb +329 -0
- data/lib/omnizip/parallel/worker_pool.rb +223 -0
- data/lib/omnizip/parallel.rb +149 -0
- data/lib/omnizip/parity/chunked_block_processor.rb +196 -0
- data/lib/omnizip/parity/galois16.rb +145 -0
- data/lib/omnizip/parity/models/creator_packet.rb +73 -0
- data/lib/omnizip/parity/models/file_description_packet.rb +133 -0
- data/lib/omnizip/parity/models/ifsc_packet.rb +123 -0
- data/lib/omnizip/parity/models/main_packet.rb +128 -0
- data/lib/omnizip/parity/models/packet.rb +156 -0
- data/lib/omnizip/parity/models/packet_registry.rb +109 -0
- data/lib/omnizip/parity/models/recovery_slice_packet.rb +78 -0
- data/lib/omnizip/parity/par2_creator.rb +531 -0
- data/lib/omnizip/parity/par2_repairer.rb +407 -0
- data/lib/omnizip/parity/par2_verifier.rb +364 -0
- data/lib/omnizip/parity/par2cmdline_algorithm.rb +110 -0
- data/lib/omnizip/parity/par2cmdline_coefficients.rb +78 -0
- data/lib/omnizip/parity/reed_solomon_decoder.rb +266 -0
- data/lib/omnizip/parity/reed_solomon_encoder.rb +111 -0
- data/lib/omnizip/parity/reed_solomon_matrix.rb +342 -0
- data/lib/omnizip/parity.rb +186 -0
- data/lib/omnizip/password/encryption_registry.rb +65 -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 +192 -0
- data/lib/omnizip/password/zip_crypto_strategy.rb +141 -0
- data/lib/omnizip/password.rb +87 -0
- data/lib/omnizip/pipe/stream_compressor.rb +124 -0
- data/lib/omnizip/pipe/stream_decompressor.rb +174 -0
- data/lib/omnizip/pipe.rb +121 -0
- data/lib/omnizip/platform/ntfs_streams.rb +201 -0
- data/lib/omnizip/platform.rb +189 -0
- data/lib/omnizip/profile/archive_profile.rb +39 -0
- data/lib/omnizip/profile/balanced_profile.rb +33 -0
- data/lib/omnizip/profile/binary_profile.rb +36 -0
- data/lib/omnizip/profile/compression_profile.rb +158 -0
- data/lib/omnizip/profile/custom_profile.rb +157 -0
- data/lib/omnizip/profile/fast_profile.rb +33 -0
- data/lib/omnizip/profile/maximum_profile.rb +33 -0
- data/lib/omnizip/profile/profile_detector.rb +110 -0
- data/lib/omnizip/profile/profile_registry.rb +161 -0
- data/lib/omnizip/profile/text_profile.rb +36 -0
- data/lib/omnizip/profile.rb +190 -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 +169 -0
- data/lib/omnizip/profiler.rb +204 -0
- data/lib/omnizip/progress/callback_reporter.rb +36 -0
- data/lib/omnizip/progress/console_reporter.rb +62 -0
- data/lib/omnizip/progress/log_reporter.rb +91 -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 +190 -0
- data/lib/omnizip/progress/silent_reporter.rb +24 -0
- data/lib/omnizip/progress.rb +127 -0
- data/lib/omnizip/rubyzip_compat.rb +63 -0
- data/lib/omnizip/temp/safe_extract.rb +168 -0
- data/lib/omnizip/temp/temp_file.rb +124 -0
- data/lib/omnizip/temp/temp_file_pool.rb +109 -0
- data/lib/omnizip/temp.rb +181 -0
- data/lib/omnizip/version.rb +5 -0
- data/lib/omnizip/zip/entry.rb +156 -0
- data/lib/omnizip/zip/file.rb +485 -0
- data/lib/omnizip/zip/input_stream.rb +273 -0
- data/lib/omnizip/zip/output_stream.rb +324 -0
- data/lib/omnizip.rb +156 -0
- data/readme-docs/advanced-features.adoc +515 -0
- data/readme-docs/api-usage.adoc +444 -0
- data/readme-docs/architecture.adoc +449 -0
- data/readme-docs/archive-formats.adoc +479 -0
- data/readme-docs/cli-usage.adoc +222 -0
- data/readme-docs/compression-algorithms.adoc +442 -0
- data/readme-docs/compression-profiles.adoc +247 -0
- data/readme-docs/encryption-checksums.adoc +328 -0
- data/readme-docs/format-converter.adoc +325 -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/xz-file-format-1.2.1.txt +1174 -0
- metadata +617 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require "lutaml/model"
|
|
5
|
+
rescue LoadError, ArgumentError
|
|
6
|
+
# lutaml-model not available, using simple classes
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
require_relative "../rar/rar_format_base"
|
|
10
|
+
require_relative "decompressor"
|
|
11
|
+
|
|
12
|
+
module Omnizip
|
|
13
|
+
module Formats
|
|
14
|
+
module Rar3
|
|
15
|
+
# RAR v3 archive reader
|
|
16
|
+
#
|
|
17
|
+
# Reads RAR 3.x format archives, parsing headers and extracting file data
|
|
18
|
+
# according to the RAR v3 specification.
|
|
19
|
+
#
|
|
20
|
+
# @example Reading a RAR3 archive
|
|
21
|
+
# reader = Rar3::Reader.new
|
|
22
|
+
# File.open("archive.rar", "rb") do |file|
|
|
23
|
+
# entries = reader.read_archive(file)
|
|
24
|
+
# entries.each { |entry| puts entry.name }
|
|
25
|
+
# end
|
|
26
|
+
class Reader < Rar::RarFormatBase
|
|
27
|
+
# Initialize a RAR v3 reader
|
|
28
|
+
def initialize
|
|
29
|
+
super("rar3")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Read a RAR v3 archive
|
|
33
|
+
#
|
|
34
|
+
# @param io [IO] The input stream
|
|
35
|
+
# @return [Array<Entry>] The archive entries
|
|
36
|
+
# @raise [FormatError] If the archive format is invalid
|
|
37
|
+
def read_archive(io)
|
|
38
|
+
unless verify_magic_bytes(io)
|
|
39
|
+
raise FormatError, "Invalid RAR v3 signature"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
entries = []
|
|
43
|
+
|
|
44
|
+
# RAR4 marker is the signature itself (7 bytes): "Rar!\x1a\x07\x00"
|
|
45
|
+
# Skip past the marker to read the archive header
|
|
46
|
+
io.seek(7, ::IO::SEEK_SET)
|
|
47
|
+
|
|
48
|
+
# Read first block - could be archive header or file block (for minimal archives)
|
|
49
|
+
first_block = read_block_header(io)
|
|
50
|
+
|
|
51
|
+
if first_block.type == block_type_code(:archive)
|
|
52
|
+
# Standard archive with archive header
|
|
53
|
+
@archive_flags = first_block.flags
|
|
54
|
+
|
|
55
|
+
# Skip past archive header block (header + data)
|
|
56
|
+
# SIZE field contains total block size
|
|
57
|
+
block_end = first_block.header_start + first_block.size
|
|
58
|
+
io.seek(block_end, ::IO::SEEK_SET)
|
|
59
|
+
elsif first_block.type == block_type_code(:file)
|
|
60
|
+
# Minimal archive without archive header - process as file block
|
|
61
|
+
entry = read_file_entry(io, first_block)
|
|
62
|
+
entries << entry if entry
|
|
63
|
+
else
|
|
64
|
+
raise FormatError, "Expected archive header or file header, got type #{first_block.type}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Read file blocks until end
|
|
68
|
+
loop do
|
|
69
|
+
block = read_block_header(io)
|
|
70
|
+
break if block.type == block_type_code(:terminator)
|
|
71
|
+
|
|
72
|
+
case block.type
|
|
73
|
+
when block_type_code(:file)
|
|
74
|
+
entry = read_file_entry(io, block)
|
|
75
|
+
entries << entry if entry
|
|
76
|
+
when block_type_code(:comment)
|
|
77
|
+
read_comment_block(io, block)
|
|
78
|
+
when block_type_code(:recovery)
|
|
79
|
+
skip_block_data(io, block)
|
|
80
|
+
else
|
|
81
|
+
skip_block_data(io, block)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
entries
|
|
86
|
+
rescue EOFError, FormatError
|
|
87
|
+
# Handle truncated or malformed files gracefully
|
|
88
|
+
entries
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
# Read a block header
|
|
94
|
+
#
|
|
95
|
+
# @param io [IO] The input stream
|
|
96
|
+
# @return [BlockHeader] The block header
|
|
97
|
+
def read_block_header(io)
|
|
98
|
+
# Record position BEFORE reading header
|
|
99
|
+
header_start = io.pos
|
|
100
|
+
|
|
101
|
+
header_crc = io.read(2)&.unpack1("v")
|
|
102
|
+
type = io.read(1)&.unpack1("C")
|
|
103
|
+
flags = io.read(2)&.unpack1("v")
|
|
104
|
+
size = io.read(2)&.unpack1("v")
|
|
105
|
+
|
|
106
|
+
raise FormatError, "Unexpected EOF" unless size
|
|
107
|
+
|
|
108
|
+
# For FILE blocks, the SIZE field directly contains the total header size
|
|
109
|
+
# The file_header structure starts immediately after the 7-byte block header
|
|
110
|
+
# No additional 4-byte field needed for FILE blocks
|
|
111
|
+
header_size = 7 # CRC(2) + TYPE(1) + FLAGS(2) + SIZE(2)
|
|
112
|
+
|
|
113
|
+
BlockHeader.new(
|
|
114
|
+
crc: header_crc,
|
|
115
|
+
type: type,
|
|
116
|
+
flags: flags,
|
|
117
|
+
size: size,
|
|
118
|
+
header_start: header_start,
|
|
119
|
+
header_size: header_size,
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Skip to the data portion of a block (after header)
|
|
124
|
+
#
|
|
125
|
+
# @param io [IO] The input stream
|
|
126
|
+
# @param block [BlockHeader] The block header
|
|
127
|
+
def skip_to_block_data(io, block)
|
|
128
|
+
target_pos = block.header_start + block.header_size
|
|
129
|
+
current_pos = io.pos
|
|
130
|
+
if target_pos > current_pos
|
|
131
|
+
io.seek(target_pos, ::IO::SEEK_SET)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Read a file entry from archive
|
|
136
|
+
#
|
|
137
|
+
# @param io [IO] The input stream
|
|
138
|
+
# @param block [BlockHeader] The file block header
|
|
139
|
+
# @return [Entry] The file entry
|
|
140
|
+
def read_file_entry(io, block)
|
|
141
|
+
# The SIZE field contains the total size of the block header (including data after 7-byte prefix)
|
|
142
|
+
# The data portion after the 7-byte block header is: size - 7 bytes
|
|
143
|
+
block.header_start + 7 # Start of header data after 7-byte prefix
|
|
144
|
+
header_data_size = block.size - 7
|
|
145
|
+
|
|
146
|
+
# Read all header data at once
|
|
147
|
+
header_data = io.read(header_data_size)
|
|
148
|
+
raise FormatError, "Unexpected EOF reading file header" unless header_data
|
|
149
|
+
|
|
150
|
+
# Now parse the file_header from the start of header_data
|
|
151
|
+
pos = 0
|
|
152
|
+
|
|
153
|
+
packed_size = header_data[pos, 4].unpack1("V")
|
|
154
|
+
pos += 4
|
|
155
|
+
|
|
156
|
+
unpacked_size = header_data[pos, 4].unpack1("V")
|
|
157
|
+
pos += 4
|
|
158
|
+
|
|
159
|
+
host_os = header_data[pos, 1].unpack1("C")
|
|
160
|
+
pos += 1
|
|
161
|
+
|
|
162
|
+
file_crc = header_data[pos, 4].unpack1("V")
|
|
163
|
+
pos += 4
|
|
164
|
+
|
|
165
|
+
file_time = header_data[pos, 4].unpack1("V")
|
|
166
|
+
pos += 4
|
|
167
|
+
|
|
168
|
+
header_data[pos, 1].unpack1("C")
|
|
169
|
+
pos += 1
|
|
170
|
+
|
|
171
|
+
method = header_data[pos, 1].unpack1("C")
|
|
172
|
+
pos += 1
|
|
173
|
+
|
|
174
|
+
name_size = header_data[pos, 2].unpack1("v")
|
|
175
|
+
pos += 2
|
|
176
|
+
|
|
177
|
+
attr = header_data[pos, 4].unpack1("V")
|
|
178
|
+
pos += 4
|
|
179
|
+
|
|
180
|
+
# Read filename
|
|
181
|
+
name_bytes = header_data[pos, name_size]
|
|
182
|
+
pos += name_size
|
|
183
|
+
filename = decode_filename(name_bytes, block.flags)
|
|
184
|
+
|
|
185
|
+
# Handle large file sizes
|
|
186
|
+
if block.flags & 0x0100 != 0 # large_file flag
|
|
187
|
+
high_packed = header_data[pos, 4].unpack1("V")
|
|
188
|
+
high_unpacked = header_data[pos + 4, 4].unpack1("V")
|
|
189
|
+
packed_size |= (high_packed << 32)
|
|
190
|
+
unpacked_size |= (high_unpacked << 32)
|
|
191
|
+
pos += 8
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Read salt if encrypted
|
|
195
|
+
salt = nil
|
|
196
|
+
if block.flags & 0x0400 != 0 # salt flag
|
|
197
|
+
salt = header_data[pos, 8]
|
|
198
|
+
pos += 8
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Read extended time if present
|
|
202
|
+
mtime = parse_dos_time(file_time)
|
|
203
|
+
if block.flags & 0x1000 != 0 # ext_time flag
|
|
204
|
+
# Extended time format: mtime[4] + ctime[4] + atime[4] + arctime[4] + flags
|
|
205
|
+
# Plus additional 3 bytes for each present time
|
|
206
|
+
mtime, = read_extended_time_data(header_data, pos)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# We've already read all header data, no need to seek
|
|
210
|
+
# Just skip to end of block and then past file data
|
|
211
|
+
|
|
212
|
+
# Record data offset (start of file data)
|
|
213
|
+
data_offset = block.header_start + block.size
|
|
214
|
+
|
|
215
|
+
# Current position should be at end of header data
|
|
216
|
+
# Skip past file data to prepare for next block
|
|
217
|
+
# Use read instead of seek for better compatibility with non-seekable streams
|
|
218
|
+
if packed_size.positive?
|
|
219
|
+
begin
|
|
220
|
+
io.seek(packed_size, ::IO::SEEK_CUR)
|
|
221
|
+
rescue Errno::EINVAL, Errno::ESPIPE
|
|
222
|
+
# Stream doesn't support seeking - read and discard instead
|
|
223
|
+
remaining = packed_size
|
|
224
|
+
while remaining.positive?
|
|
225
|
+
chunk = io.read([remaining, 8192].min)
|
|
226
|
+
break unless chunk
|
|
227
|
+
|
|
228
|
+
remaining -= chunk.bytesize
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
Entry.new(
|
|
234
|
+
name: filename,
|
|
235
|
+
compressed_size: packed_size,
|
|
236
|
+
uncompressed_size: unpacked_size,
|
|
237
|
+
crc32: file_crc,
|
|
238
|
+
compression_method: compression_method_name(method),
|
|
239
|
+
modified_time: mtime,
|
|
240
|
+
attributes: attr,
|
|
241
|
+
encrypted: block.flags.anybits?(0x0004), # encrypted flag
|
|
242
|
+
data_offset: data_offset,
|
|
243
|
+
host_os: host_os,
|
|
244
|
+
salt: salt,
|
|
245
|
+
)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Read extended time from header data
|
|
249
|
+
#
|
|
250
|
+
# @param data [String] The header data
|
|
251
|
+
# @param pos [Integer] Current position in data
|
|
252
|
+
# @return [Array<Time, Integer>] The modification time and new position
|
|
253
|
+
def read_extended_time_data(data, pos)
|
|
254
|
+
return [Time.now, pos] if pos + 4 > data.bytesize
|
|
255
|
+
|
|
256
|
+
mtime = parse_dos_time(data[pos, 4].unpack1("V"))
|
|
257
|
+
pos += 4
|
|
258
|
+
|
|
259
|
+
# Skip ctime, atime, arctime if present
|
|
260
|
+
begin
|
|
261
|
+
data[pos, 1].unpack1("C")
|
|
262
|
+
rescue StandardError
|
|
263
|
+
0
|
|
264
|
+
end
|
|
265
|
+
pos += 1
|
|
266
|
+
|
|
267
|
+
# For now, just skip the remaining extended time data
|
|
268
|
+
# The format is complex and varies
|
|
269
|
+
|
|
270
|
+
[mtime, pos]
|
|
271
|
+
rescue ArgumentError
|
|
272
|
+
[Time.now, pos]
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Decode filename based on encoding flags
|
|
276
|
+
#
|
|
277
|
+
# @param bytes [String] The filename bytes
|
|
278
|
+
# @param flags [Integer] The file flags
|
|
279
|
+
# @return [String] The decoded filename
|
|
280
|
+
def decode_filename(bytes, flags)
|
|
281
|
+
if flags.nobits?(spec.format.file_flags[:unicode])
|
|
282
|
+
# Legacy encoding - assume CP437 or system encoding
|
|
283
|
+
bytes.force_encoding("CP437").encode("UTF-8", invalid: :replace)
|
|
284
|
+
else
|
|
285
|
+
# Unicode filename - decode UTF-8
|
|
286
|
+
bytes.force_encoding("UTF-8")
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Parse DOS date/time to Ruby Time
|
|
291
|
+
#
|
|
292
|
+
# @param dos_time [Integer] The DOS timestamp
|
|
293
|
+
# @return [Time] The parsed time
|
|
294
|
+
def parse_dos_time(dos_time)
|
|
295
|
+
second = (dos_time & 0x1F) * 2
|
|
296
|
+
minute = (dos_time >> 5) & 0x3F
|
|
297
|
+
hour = (dos_time >> 11) & 0x1F
|
|
298
|
+
day = (dos_time >> 16) & 0x1F
|
|
299
|
+
month = (dos_time >> 21) & 0x0F
|
|
300
|
+
year = ((dos_time >> 25) & 0x7F) + 1980
|
|
301
|
+
|
|
302
|
+
Time.new(year, month, day, hour, minute, second)
|
|
303
|
+
rescue ArgumentError
|
|
304
|
+
Time.now
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Read extended time information
|
|
308
|
+
#
|
|
309
|
+
# @param io [IO] The input stream
|
|
310
|
+
# @return [Time] The extended time
|
|
311
|
+
def read_extended_time(io)
|
|
312
|
+
io.read(2)&.unpack1("v")
|
|
313
|
+
# Extended time format varies, simplified implementation
|
|
314
|
+
Time.now
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Read comment block
|
|
318
|
+
#
|
|
319
|
+
# @param io [IO] The input stream
|
|
320
|
+
# @param block [BlockHeader] The block header
|
|
321
|
+
# @return [String] The comment text
|
|
322
|
+
def read_comment_block(io, block)
|
|
323
|
+
current_pos = io.pos
|
|
324
|
+
header_end = block.header_start + block.header_size
|
|
325
|
+
data_size = block.size - block.header_size
|
|
326
|
+
remaining = data_size - (current_pos - header_end)
|
|
327
|
+
comment = io.read(remaining) if remaining.positive?
|
|
328
|
+
comment&.force_encoding("UTF-8")
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Skip block data
|
|
332
|
+
#
|
|
333
|
+
# @param io [IO] The input stream
|
|
334
|
+
# @param block [BlockHeader] The block header
|
|
335
|
+
# @return [void]
|
|
336
|
+
def skip_block_data(io, block)
|
|
337
|
+
# block.size is the total size, calculate remaining bytes
|
|
338
|
+
current_pos = io.pos
|
|
339
|
+
header_end = block.header_start + block.header_size
|
|
340
|
+
data_size = block.size - block.header_size
|
|
341
|
+
remaining = data_size - (current_pos - header_end)
|
|
342
|
+
io.seek(remaining, ::IO::SEEK_CUR) if remaining.positive?
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# RAR v3 block header model
|
|
347
|
+
class BlockHeader
|
|
348
|
+
attr_accessor :crc, :type, :flags, :size, :header_start, :header_size
|
|
349
|
+
|
|
350
|
+
def initialize(crc: nil, type: nil, flags: nil, size: nil,
|
|
351
|
+
header_start: nil, header_size: 7)
|
|
352
|
+
@crc = crc
|
|
353
|
+
@type = type
|
|
354
|
+
@flags = flags
|
|
355
|
+
@size = size
|
|
356
|
+
@header_start = header_start
|
|
357
|
+
@header_size = header_size
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# RAR archive entry model
|
|
362
|
+
class Entry
|
|
363
|
+
attr_accessor :name, :compressed_size, :uncompressed_size, :crc32,
|
|
364
|
+
:compression_method, :modified_time, :attributes,
|
|
365
|
+
:encrypted, :data_offset, :host_os, :salt
|
|
366
|
+
|
|
367
|
+
def initialize(name: nil, compressed_size: nil, uncompressed_size: nil,
|
|
368
|
+
crc32: nil, compression_method: nil, modified_time: nil,
|
|
369
|
+
attributes: nil, encrypted: nil, data_offset: nil,
|
|
370
|
+
host_os: nil, salt: nil)
|
|
371
|
+
@name = name
|
|
372
|
+
@compressed_size = compressed_size
|
|
373
|
+
@uncompressed_size = uncompressed_size
|
|
374
|
+
@crc32 = crc32
|
|
375
|
+
@compression_method = compression_method
|
|
376
|
+
@modified_time = modified_time
|
|
377
|
+
@attributes = attributes
|
|
378
|
+
@encrypted = encrypted
|
|
379
|
+
@data_offset = data_offset
|
|
380
|
+
@host_os = host_os
|
|
381
|
+
@salt = salt
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require "lutaml/model"
|
|
5
|
+
rescue LoadError, ArgumentError
|
|
6
|
+
# lutaml-model not available, using simple classes
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
require "stringio"
|
|
10
|
+
require_relative "../rar/rar_format_base"
|
|
11
|
+
require_relative "compressor"
|
|
12
|
+
|
|
13
|
+
module Omnizip
|
|
14
|
+
module Formats
|
|
15
|
+
module Rar3
|
|
16
|
+
# RAR v3 archive writer
|
|
17
|
+
#
|
|
18
|
+
# Writes RAR 3.x format archives, creating headers and compressing file
|
|
19
|
+
# data according to the RAR v3 specification.
|
|
20
|
+
#
|
|
21
|
+
# @example Writing a RAR3 archive
|
|
22
|
+
# writer = Rar3::Writer.new
|
|
23
|
+
# File.open("archive.rar", "wb") do |file|
|
|
24
|
+
# entries = [
|
|
25
|
+
# {name: "file.txt", data: "content", time: Time.now}
|
|
26
|
+
# ]
|
|
27
|
+
# writer.write_archive(file, entries)
|
|
28
|
+
# end
|
|
29
|
+
class Writer < Rar::RarFormatBase
|
|
30
|
+
# Initialize a RAR v3 writer
|
|
31
|
+
def initialize
|
|
32
|
+
super("rar3")
|
|
33
|
+
@compressor = Compressor.new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Write a RAR v3 archive
|
|
37
|
+
#
|
|
38
|
+
# @param io [IO] The output stream
|
|
39
|
+
# @param entries [Array<Hash>] The entries to write
|
|
40
|
+
# @return [void]
|
|
41
|
+
def write_archive(io, entries)
|
|
42
|
+
# Write marker block
|
|
43
|
+
write_marker_block(io)
|
|
44
|
+
|
|
45
|
+
# Write archive header
|
|
46
|
+
write_archive_header(io)
|
|
47
|
+
|
|
48
|
+
# Write file entries
|
|
49
|
+
entries.each do |entry|
|
|
50
|
+
write_file_entry(io, entry)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Write terminator block
|
|
54
|
+
write_terminator_block(io)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# Write marker block (RAR signature)
|
|
60
|
+
#
|
|
61
|
+
# @param io [IO] The output stream
|
|
62
|
+
# @return [void]
|
|
63
|
+
def write_marker_block(io)
|
|
64
|
+
io.write(spec.magic_bytes.pack("C*"))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Write archive header block
|
|
68
|
+
#
|
|
69
|
+
# @param io [IO] The output stream
|
|
70
|
+
# @return [void]
|
|
71
|
+
def write_archive_header(io)
|
|
72
|
+
flags = 0
|
|
73
|
+
# Set default flags if needed
|
|
74
|
+
|
|
75
|
+
header_data = StringIO.new
|
|
76
|
+
# Reserved fields
|
|
77
|
+
header_data.write([0, 0].pack("vv"))
|
|
78
|
+
|
|
79
|
+
header = create_block_header(
|
|
80
|
+
type: block_type_code(:archive),
|
|
81
|
+
flags: flags,
|
|
82
|
+
data: header_data.string,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
write_block_header(io, header)
|
|
86
|
+
io.write(header_data.string)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Write a file entry
|
|
90
|
+
#
|
|
91
|
+
# @param io [IO] The output stream
|
|
92
|
+
# @param entry [Hash] The entry data
|
|
93
|
+
# @return [void]
|
|
94
|
+
def write_file_entry(io, entry)
|
|
95
|
+
name = entry[:name] || entry["name"]
|
|
96
|
+
data = entry[:data] || entry["data"]
|
|
97
|
+
mtime = entry[:time] || entry["time"] || Time.now
|
|
98
|
+
method = entry[:method] || entry["method"] || :normal
|
|
99
|
+
|
|
100
|
+
# Compress data
|
|
101
|
+
compressed_data = @compressor.compress(data, method: method)
|
|
102
|
+
|
|
103
|
+
# Calculate sizes and CRC
|
|
104
|
+
packed_size = compressed_data.bytesize
|
|
105
|
+
unpacked_size = data.bytesize
|
|
106
|
+
file_crc = calculate_crc32(data)
|
|
107
|
+
|
|
108
|
+
# Encode filename
|
|
109
|
+
name_bytes = name.encode("UTF-8")
|
|
110
|
+
name_size = name_bytes.bytesize
|
|
111
|
+
|
|
112
|
+
# Set flags
|
|
113
|
+
flags = spec.format.file_flags[:unicode]
|
|
114
|
+
if unpacked_size > 0xFFFFFFFF
|
|
115
|
+
flags |= spec.format.file_flags[:large_file]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Build file header data
|
|
119
|
+
header_data = StringIO.new
|
|
120
|
+
header_data.write([packed_size & 0xFFFFFFFF].pack("V"))
|
|
121
|
+
header_data.write([unpacked_size & 0xFFFFFFFF].pack("V"))
|
|
122
|
+
header_data.write([spec.format.host_os[:win32]].pack("C"))
|
|
123
|
+
header_data.write([file_crc].pack("V"))
|
|
124
|
+
header_data.write([encode_dos_time(mtime)].pack("V"))
|
|
125
|
+
header_data.write([0x14].pack("C")) # Version needed
|
|
126
|
+
header_data.write([compression_method_code(method)].pack("C"))
|
|
127
|
+
header_data.write([name_size].pack("v"))
|
|
128
|
+
header_data.write([0x20].pack("V")) # File attributes
|
|
129
|
+
header_data.write(name_bytes)
|
|
130
|
+
|
|
131
|
+
# Add high size fields if needed
|
|
132
|
+
if flags & spec.format.file_flags[:large_file] != 0
|
|
133
|
+
header_data.write([(packed_size >> 32) & 0xFFFFFFFF].pack("V"))
|
|
134
|
+
header_data.write([(unpacked_size >> 32) & 0xFFFFFFFF].pack("V"))
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Create and write block header
|
|
138
|
+
header = create_block_header(
|
|
139
|
+
type: block_type_code(:file),
|
|
140
|
+
flags: flags,
|
|
141
|
+
data: header_data.string,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
write_block_header(io, header)
|
|
145
|
+
io.write(header_data.string)
|
|
146
|
+
io.write(compressed_data)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Write terminator block
|
|
150
|
+
#
|
|
151
|
+
# @param io [IO] The output stream
|
|
152
|
+
# @return [void]
|
|
153
|
+
def write_terminator_block(io)
|
|
154
|
+
header = create_block_header(
|
|
155
|
+
type: block_type_code(:terminator),
|
|
156
|
+
flags: 0x4000, # Archive end flag
|
|
157
|
+
data: "",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
write_block_header(io, header)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Create a block header
|
|
164
|
+
#
|
|
165
|
+
# @param type [Integer] The block type
|
|
166
|
+
# @param flags [Integer] The block flags
|
|
167
|
+
# @param data [String] The block data
|
|
168
|
+
# @return [Hash] The block header
|
|
169
|
+
def create_block_header(type:, flags:, data:)
|
|
170
|
+
size = 7 + data.bytesize # Header size + data size
|
|
171
|
+
|
|
172
|
+
# Calculate header CRC
|
|
173
|
+
temp = StringIO.new
|
|
174
|
+
temp.write([type].pack("C"))
|
|
175
|
+
temp.write([flags].pack("v"))
|
|
176
|
+
temp.write([size].pack("v"))
|
|
177
|
+
temp.write(data) unless data.empty?
|
|
178
|
+
|
|
179
|
+
header_crc = calculate_crc32(temp.string) & 0xFFFF
|
|
180
|
+
|
|
181
|
+
{
|
|
182
|
+
crc: header_crc,
|
|
183
|
+
type: type,
|
|
184
|
+
flags: flags,
|
|
185
|
+
size: size,
|
|
186
|
+
}
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Write a block header to stream
|
|
190
|
+
#
|
|
191
|
+
# @param io [IO] The output stream
|
|
192
|
+
# @param header [Hash] The header data
|
|
193
|
+
# @return [void]
|
|
194
|
+
def write_block_header(io, header)
|
|
195
|
+
io.write([header[:crc]].pack("v"))
|
|
196
|
+
io.write([header[:type]].pack("C"))
|
|
197
|
+
io.write([header[:flags]].pack("v"))
|
|
198
|
+
io.write([header[:size]].pack("v"))
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Encode Ruby Time to DOS date/time format
|
|
202
|
+
#
|
|
203
|
+
# @param time [Time] The time to encode
|
|
204
|
+
# @return [Integer] The DOS timestamp
|
|
205
|
+
def encode_dos_time(time)
|
|
206
|
+
second = time.sec / 2
|
|
207
|
+
minute = time.min
|
|
208
|
+
hour = time.hour
|
|
209
|
+
day = time.day
|
|
210
|
+
month = time.month
|
|
211
|
+
year = time.year - 1980
|
|
212
|
+
|
|
213
|
+
(year << 25) | (month << 21) | (day << 16) |
|
|
214
|
+
(hour << 11) | (minute << 5) | second
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zlib"
|
|
4
|
+
require_relative "../rar/rar_format_base"
|
|
5
|
+
require_relative "../../error"
|
|
6
|
+
|
|
7
|
+
module Omnizip
|
|
8
|
+
module Formats
|
|
9
|
+
module Rar5
|
|
10
|
+
# RAR v5 compressor
|
|
11
|
+
#
|
|
12
|
+
# Compresses data using RAR v5 compression algorithms.
|
|
13
|
+
# This implementation uses DEFLATE as a compatible fallback
|
|
14
|
+
# for demonstration purposes. Full RAR5 compression requires
|
|
15
|
+
# proprietary algorithms.
|
|
16
|
+
#
|
|
17
|
+
# @example Compressing data
|
|
18
|
+
# compressor = Rar5::Compressor.new
|
|
19
|
+
# compressed = compressor.compress("Hello, World!", method: :normal)
|
|
20
|
+
class Compressor < Rar::RarFormatBase
|
|
21
|
+
# Initialize a RAR v5 compressor
|
|
22
|
+
def initialize
|
|
23
|
+
super("rar5")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Compress data using RAR v5 methods
|
|
27
|
+
#
|
|
28
|
+
# @param data [String] The data to compress
|
|
29
|
+
# @param method [Symbol] The compression method
|
|
30
|
+
# @param options [Hash] Compression options
|
|
31
|
+
# @return [String] The compressed data
|
|
32
|
+
def compress(data, method: :normal, **_options)
|
|
33
|
+
case method
|
|
34
|
+
when :store
|
|
35
|
+
compress_store(data)
|
|
36
|
+
when :fastest
|
|
37
|
+
compress_deflate(data, level: Zlib::BEST_SPEED)
|
|
38
|
+
when :fast
|
|
39
|
+
compress_deflate(data, level: 3)
|
|
40
|
+
when :normal
|
|
41
|
+
compress_deflate(data, level: Zlib::DEFAULT_COMPRESSION)
|
|
42
|
+
when :good
|
|
43
|
+
compress_deflate(data, level: 7)
|
|
44
|
+
when :best
|
|
45
|
+
compress_deflate(data, level: Zlib::BEST_COMPRESSION)
|
|
46
|
+
else
|
|
47
|
+
raise FormatError,
|
|
48
|
+
"Unsupported compression method: #{method}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# Store data without compression
|
|
55
|
+
#
|
|
56
|
+
# @param data [String] The data to store
|
|
57
|
+
# @return [String] The uncompressed data
|
|
58
|
+
def compress_store(data)
|
|
59
|
+
data
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Compress using DEFLATE (compatible fallback)
|
|
63
|
+
#
|
|
64
|
+
# @param data [String] The data to compress
|
|
65
|
+
# @param level [Integer] The compression level
|
|
66
|
+
# @return [String] The compressed data
|
|
67
|
+
def compress_deflate(data, level:)
|
|
68
|
+
Zlib::Deflate.deflate(data, level)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|