omnizip 0.4.7-x86_64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +32 -0
- data/.rubocop_todo.yml +293 -0
- data/CHANGELOG.md +1647 -0
- data/CONTEXT.md +68 -0
- data/COPYING +502 -0
- data/Gemfile +19 -0
- data/LICENSE +12 -0
- data/README.adoc +1327 -0
- data/Rakefile +15 -0
- data/TODO.refactor/00-overview.md +67 -0
- data/TODO.refactor/01-registry-base-class.md +81 -0
- data/TODO.refactor/02-error-hierarchy.md +57 -0
- data/TODO.refactor/03-autoload-entry-point.md +69 -0
- data/TODO.refactor/04-filter-base-consolidation.md +49 -0
- data/TODO.refactor/05-send-private-methods.md +64 -0
- data/TODO.refactor/06-instance-variable-access.md +69 -0
- data/TODO.refactor/07-respond-to-replacement.md +237 -0
- data/TODO.refactor/08-spec-doubles.md +27 -0
- data/TODO.refactor/09-cli-shared-module.md +35 -0
- data/TODO.refactor/10-format-detector-ocp.md +34 -0
- data/TODO.refactor/11-convenience-decoupling.md +38 -0
- data/TODO.refactor/12-thread-safety.md +8 -0
- data/TODO.refactor/13-lutaml-model-migration.md +158 -0
- data/TODO.refactor/14-add-missing-specs.md +25 -0
- data/benchmark/README.md +260 -0
- data/benchmark/benchmark_suite.rb +125 -0
- data/benchmark/compression_bench.rb +181 -0
- data/benchmark/filter_bench.rb +180 -0
- data/benchmark/models/benchmark_result.rb +59 -0
- data/benchmark/models/comparison_result.rb +69 -0
- data/benchmark/profile_suite.rb +167 -0
- data/benchmark/reporter.rb +150 -0
- data/benchmark/run_benchmarks.rb +66 -0
- data/benchmark/test_data.rb +137 -0
- data/config/formats/rar3_spec.yml +92 -0
- data/config/formats/rar5_spec.yml +102 -0
- data/docs/.github/workflows/docs.yml +142 -0
- data/docs/.gitignore +21 -0
- data/docs/.lychee.toml +67 -0
- data/docs/Gemfile +13 -0
- data/docs/RAR_WRITE_SUPPORT.md +26 -0
- data/docs/README.md +101 -0
- data/docs/_config.yml +112 -0
- data/docs/assets/logo.svg +1 -0
- data/docs/assets/omnizip-logo.pdf +1540 -11
- data/docs/comparison/feature-matrix.adoc +694 -0
- data/docs/comparison/index.adoc +113 -0
- data/docs/comparison/vs-7zip.adoc +309 -0
- data/docs/comparison/vs-peazip.adoc +77 -0
- data/docs/comparison/vs-rubyzip.adoc +342 -0
- data/docs/comparison/vs-winrar.adoc +100 -0
- data/docs/compatibility.adoc +579 -0
- data/docs/concepts/index.adoc +129 -0
- data/docs/developer/architecture.adoc +256 -0
- data/docs/developer/contributing.adoc +158 -0
- data/docs/developer/index.adoc +25 -0
- data/docs/developer/testing.adoc +212 -0
- data/docs/getting-started/basic-usage.adoc +263 -0
- data/docs/getting-started/index.adoc +42 -0
- data/docs/getting-started/installation.adoc +138 -0
- data/docs/getting-started/quick-start.adoc +183 -0
- data/docs/getting-started/your-first-archive.adoc +219 -0
- data/docs/guides/advanced-features/encryption.adoc +277 -0
- data/docs/guides/advanced-features/index.adoc +59 -0
- data/docs/guides/advanced-features/parallel-processing.adoc +144 -0
- data/docs/guides/advanced-features/progress-tracking.adoc +124 -0
- data/docs/guides/advanced-features/streaming.adoc +142 -0
- data/docs/guides/archive-formats/gzip-format.adoc +105 -0
- data/docs/guides/archive-formats/index.adoc +160 -0
- data/docs/guides/archive-formats/ole-format.adoc +151 -0
- data/docs/guides/archive-formats/rar-format.adoc +104 -0
- data/docs/guides/archive-formats/rar5.adoc +521 -0
- data/docs/guides/archive-formats/rpm-format.adoc +108 -0
- data/docs/guides/archive-formats/seven-zip-format.adoc +35 -0
- data/docs/guides/archive-formats/tar-format.adoc +106 -0
- data/docs/guides/archive-formats/xz-format.adoc +118 -0
- data/docs/guides/archive-formats/zip-format.adoc +35 -0
- data/docs/guides/compression-algorithms/bzip2.adoc +114 -0
- data/docs/guides/compression-algorithms/deflate.adoc +313 -0
- data/docs/guides/compression-algorithms/index.adoc +190 -0
- data/docs/guides/compression-algorithms/lzma.adoc +394 -0
- data/docs/guides/compression-algorithms/lzma2.adoc +244 -0
- data/docs/guides/compression-algorithms/ppmd.adoc +313 -0
- data/docs/guides/compression-algorithms/zstandard.adoc +296 -0
- data/docs/guides/creating-archives.adoc +348 -0
- data/docs/guides/extracting-archives.adoc +53 -0
- data/docs/guides/format-conversion.adoc +64 -0
- data/docs/guides/index.adoc +49 -0
- data/docs/guides/migration-rubyzip.adoc +217 -0
- data/docs/guides/parity-archives.adoc +605 -0
- data/docs/guides/performance-tuning.adoc +88 -0
- data/docs/index.adoc +228 -0
- data/docs/lychee.toml +67 -0
- data/docs/reference/api/overview.adoc +187 -0
- data/docs/reference/cli/compress-command.adoc +114 -0
- data/docs/reference/cli/overview.adoc +140 -0
- data/docs/reference/index.adoc +26 -0
- data/docs/resources/faq.adoc +185 -0
- data/docs/resources/quick-reference.adoc +222 -0
- data/docs/rust-backend.md +0 -0
- data/docs/troubleshooting/index.adoc +207 -0
- data/docs/xar_format.md +216 -0
- data/examples/api_comparison.rb +205 -0
- data/examples/deflate64_example.rb +96 -0
- data/examples/par2_demo.rb +121 -0
- data/examples/quick_start_native.rb +150 -0
- data/examples/quick_start_rubyzip.rb +115 -0
- data/examples/rubyzip_compatibility_demo.rb +194 -0
- data/exe/omnizip +27 -0
- data/lib/omnizip/algorithm.rb +176 -0
- data/lib/omnizip/algorithm_registry.rb +65 -0
- data/lib/omnizip/algorithms/.keep +0 -0
- data/lib/omnizip/algorithms/bzip2/bwt.rb +236 -0
- data/lib/omnizip/algorithms/bzip2/bz2.rb +687 -0
- data/lib/omnizip/algorithms/bzip2/rle.rb +146 -0
- data/lib/omnizip/algorithms/bzip2.rb +108 -0
- data/lib/omnizip/algorithms/deflate/constants.rb +28 -0
- data/lib/omnizip/algorithms/deflate/decoder.rb +37 -0
- data/lib/omnizip/algorithms/deflate/encoder.rb +45 -0
- data/lib/omnizip/algorithms/deflate.rb +135 -0
- data/lib/omnizip/algorithms/deflate64/constants.rb +45 -0
- data/lib/omnizip/algorithms/deflate64/decoder.rb +151 -0
- data/lib/omnizip/algorithms/deflate64/encoder.rb +95 -0
- data/lib/omnizip/algorithms/deflate64/huffman_coder.rb +352 -0
- data/lib/omnizip/algorithms/deflate64/lz77_encoder.rb +140 -0
- data/lib/omnizip/algorithms/deflate64.rb +118 -0
- data/lib/omnizip/algorithms/lzma/bit_model.rb +120 -0
- data/lib/omnizip/algorithms/lzma/constants.rb +112 -0
- data/lib/omnizip/algorithms/lzma/decoder.rb +146 -0
- data/lib/omnizip/algorithms/lzma/dictionary.rb +70 -0
- data/lib/omnizip/algorithms/lzma/distance_coder.rb +326 -0
- data/lib/omnizip/algorithms/lzma/encoder.rb +139 -0
- data/lib/omnizip/algorithms/lzma/length_coder.rb +172 -0
- data/lib/omnizip/algorithms/lzma/literal_decoder.rb +204 -0
- data/lib/omnizip/algorithms/lzma/literal_encoder.rb +208 -0
- data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +368 -0
- data/lib/omnizip/algorithms/lzma/lzma1_encoder.rb +105 -0
- data/lib/omnizip/algorithms/lzma/lzma_alone_decoder.rb +194 -0
- data/lib/omnizip/algorithms/lzma/lzma_state.rb +128 -0
- data/lib/omnizip/algorithms/lzma/match.rb +32 -0
- data/lib/omnizip/algorithms/lzma/match_finder.rb +233 -0
- data/lib/omnizip/algorithms/lzma/match_finder_config.rb +142 -0
- data/lib/omnizip/algorithms/lzma/match_finder_factory.rb +84 -0
- data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +159 -0
- data/lib/omnizip/algorithms/lzma/probability_models.rb +72 -0
- data/lib/omnizip/algorithms/lzma/range_coder.rb +82 -0
- data/lib/omnizip/algorithms/lzma/range_decoder.rb +274 -0
- data/lib/omnizip/algorithms/lzma/range_encoder.rb +202 -0
- data/lib/omnizip/algorithms/lzma/state.rb +127 -0
- data/lib/omnizip/algorithms/lzma/xz_buffered_range_encoder.rb +323 -0
- data/lib/omnizip/algorithms/lzma/xz_encoder.rb +420 -0
- data/lib/omnizip/algorithms/lzma/xz_encoder_fast.rb +640 -0
- data/lib/omnizip/algorithms/lzma/xz_match_finder_adapter.rb +224 -0
- data/lib/omnizip/algorithms/lzma/xz_price_calculator.rb +167 -0
- data/lib/omnizip/algorithms/lzma/xz_probability_models.rb +258 -0
- data/lib/omnizip/algorithms/lzma/xz_range_encoder.rb +223 -0
- data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +314 -0
- data/lib/omnizip/algorithms/lzma/xz_state.rb +108 -0
- data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +1311 -0
- data/lib/omnizip/algorithms/lzma.rb +266 -0
- data/lib/omnizip/algorithms/lzma2/chunk_manager.rb +180 -0
- data/lib/omnizip/algorithms/lzma2/constants.rb +38 -0
- data/lib/omnizip/algorithms/lzma2/encoder.rb +145 -0
- data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +165 -0
- data/lib/omnizip/algorithms/lzma2/properties.rb +179 -0
- data/lib/omnizip/algorithms/lzma2/simple_lzma2_encoder.rb +122 -0
- data/lib/omnizip/algorithms/lzma2/xz_encoder_adapter.rb +85 -0
- data/lib/omnizip/algorithms/lzma2.rb +133 -0
- data/lib/omnizip/algorithms/ppmd7/constants.rb +74 -0
- data/lib/omnizip/algorithms/ppmd7/context.rb +151 -0
- data/lib/omnizip/algorithms/ppmd7/decoder.rb +127 -0
- data/lib/omnizip/algorithms/ppmd7/encoder.rb +153 -0
- data/lib/omnizip/algorithms/ppmd7/model.rb +245 -0
- data/lib/omnizip/algorithms/ppmd7/symbol_state.rb +57 -0
- data/lib/omnizip/algorithms/ppmd7.rb +146 -0
- data/lib/omnizip/algorithms/ppmd8/constants.rb +61 -0
- data/lib/omnizip/algorithms/ppmd8/context.rb +32 -0
- data/lib/omnizip/algorithms/ppmd8/decoder.rb +104 -0
- data/lib/omnizip/algorithms/ppmd8/encoder.rb +135 -0
- data/lib/omnizip/algorithms/ppmd8/model.rb +246 -0
- data/lib/omnizip/algorithms/ppmd8/restoration_method.rb +78 -0
- data/lib/omnizip/algorithms/ppmd8.rb +122 -0
- data/lib/omnizip/algorithms/ppmd_base.rb +142 -0
- data/lib/omnizip/algorithms/registration.rb +28 -0
- data/lib/omnizip/algorithms/zstandard/constants.rb +158 -0
- data/lib/omnizip/algorithms/zstandard/decoder.rb +249 -0
- data/lib/omnizip/algorithms/zstandard/dictionary.rb +73 -0
- data/lib/omnizip/algorithms/zstandard/encoder.rb +294 -0
- data/lib/omnizip/algorithms/zstandard/frame/block.rb +143 -0
- data/lib/omnizip/algorithms/zstandard/frame/header.rb +233 -0
- data/lib/omnizip/algorithms/zstandard/frame.rb +33 -0
- data/lib/omnizip/algorithms/zstandard/fse/bitstream.rb +183 -0
- data/lib/omnizip/algorithms/zstandard/fse/encoder.rb +534 -0
- data/lib/omnizip/algorithms/zstandard/fse/interleaved.rb +92 -0
- data/lib/omnizip/algorithms/zstandard/fse/table.rb +188 -0
- data/lib/omnizip/algorithms/zstandard/fse/table_description.rb +203 -0
- data/lib/omnizip/algorithms/zstandard/fse.rb +53 -0
- data/lib/omnizip/algorithms/zstandard/huffman.rb +276 -0
- data/lib/omnizip/algorithms/zstandard/huffman_encoder.rb +327 -0
- data/lib/omnizip/algorithms/zstandard/ldm.rb +121 -0
- data/lib/omnizip/algorithms/zstandard/literals.rb +241 -0
- data/lib/omnizip/algorithms/zstandard/literals_encoder.rb +96 -0
- data/lib/omnizip/algorithms/zstandard/match_finder.rb +483 -0
- data/lib/omnizip/algorithms/zstandard/sequences.rb +350 -0
- data/lib/omnizip/algorithms/zstandard/sequences_encoder.rb +333 -0
- data/lib/omnizip/algorithms/zstandard/xxhash.rb +159 -0
- data/lib/omnizip/algorithms/zstandard.rb +210 -0
- data/lib/omnizip/algorithms.rb +35 -0
- data/lib/omnizip/archive/builder.rb +48 -0
- data/lib/omnizip/archive/reader_session.rb +67 -0
- data/lib/omnizip/archive.rb +86 -0
- data/lib/omnizip/archive_handler.rb +82 -0
- data/lib/omnizip/archive_handlers/cpio_handler.rb +47 -0
- data/lib/omnizip/archive_handlers/iso_handler.rb +54 -0
- data/lib/omnizip/archive_handlers/ole_handler.rb +37 -0
- data/lib/omnizip/archive_handlers/rar_handler.rb +96 -0
- data/lib/omnizip/archive_handlers/rpm_handler.rb +45 -0
- data/lib/omnizip/archive_handlers/seven_zip_handler.rb +89 -0
- data/lib/omnizip/archive_handlers/tar_handler.rb +75 -0
- data/lib/omnizip/archive_handlers/xar_handler.rb +49 -0
- data/lib/omnizip/archive_handlers/zip_handler.rb +115 -0
- data/lib/omnizip/archive_handlers.rb +18 -0
- data/lib/omnizip/backends.rb +160 -0
- data/lib/omnizip/buffer/memory_archive.rb +246 -0
- data/lib/omnizip/buffer/memory_extractor.rb +234 -0
- data/lib/omnizip/buffer/seven_zip_bridge.rb +171 -0
- data/lib/omnizip/buffer.rb +187 -0
- data/lib/omnizip/checksum_registry.rb +41 -0
- data/lib/omnizip/checksums/crc32.rb +101 -0
- data/lib/omnizip/checksums/crc64.rb +102 -0
- data/lib/omnizip/checksums/crc_base.rb +158 -0
- data/lib/omnizip/checksums/registration.rb +16 -0
- data/lib/omnizip/checksums/verifier.rb +130 -0
- data/lib/omnizip/checksums.rb +11 -0
- data/lib/omnizip/chunked/memory_manager.rb +194 -0
- data/lib/omnizip/chunked/reader.rb +78 -0
- data/lib/omnizip/chunked/writer.rb +123 -0
- data/lib/omnizip/chunked.rb +123 -0
- data/lib/omnizip/cli/output_formatter.rb +112 -0
- data/lib/omnizip/cli.rb +604 -0
- data/lib/omnizip/commands/.keep +0 -0
- data/lib/omnizip/commands/archive_create_command.rb +245 -0
- data/lib/omnizip/commands/archive_extract_command.rb +231 -0
- data/lib/omnizip/commands/archive_list_command.rb +204 -0
- data/lib/omnizip/commands/archive_repair_command.rb +129 -0
- data/lib/omnizip/commands/archive_verify_command.rb +115 -0
- data/lib/omnizip/commands/compress_command.rb +115 -0
- data/lib/omnizip/commands/decompress_command.rb +149 -0
- data/lib/omnizip/commands/list_command.rb +51 -0
- data/lib/omnizip/commands/metadata_command.rb +215 -0
- data/lib/omnizip/commands/parity_create_command.rb +114 -0
- data/lib/omnizip/commands/parity_repair_command.rb +120 -0
- data/lib/omnizip/commands/parity_verify_command.rb +122 -0
- data/lib/omnizip/commands/profile_list_command.rb +54 -0
- data/lib/omnizip/commands/profile_show_command.rb +44 -0
- data/lib/omnizip/commands.rb +21 -0
- data/lib/omnizip/convenience.rb +389 -0
- data/lib/omnizip/converter/conversion_registry.rb +50 -0
- data/lib/omnizip/converter/conversion_strategy.rb +108 -0
- data/lib/omnizip/converter/extract_repack_strategy.rb +51 -0
- data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +43 -0
- data/lib/omnizip/converter/zip_to_seven_zip_strategy.rb +54 -0
- data/lib/omnizip/converter.rb +124 -0
- data/lib/omnizip/crypto/aes256/cipher.rb +104 -0
- data/lib/omnizip/crypto/aes256/constants.rb +28 -0
- data/lib/omnizip/crypto/aes256/key_derivation.rb +100 -0
- data/lib/omnizip/crypto/aes256.rb +102 -0
- data/lib/omnizip/crypto.rb +11 -0
- data/lib/omnizip/entry.rb +44 -0
- data/lib/omnizip/error.rb +110 -0
- data/lib/omnizip/eta/exponential_smoothing_estimator.rb +95 -0
- data/lib/omnizip/eta/moving_average_estimator.rb +96 -0
- data/lib/omnizip/eta/rate_calculator.rb +104 -0
- data/lib/omnizip/eta/sample_history.rb +143 -0
- data/lib/omnizip/eta/time_estimator.rb +106 -0
- data/lib/omnizip/eta.rb +68 -0
- data/lib/omnizip/extraction/filter_chain.rb +179 -0
- data/lib/omnizip/extraction/glob_pattern.rb +140 -0
- data/lib/omnizip/extraction/pattern_matcher.rb +66 -0
- data/lib/omnizip/extraction/predicate_pattern.rb +52 -0
- data/lib/omnizip/extraction/regex_pattern.rb +50 -0
- data/lib/omnizip/extraction/selective_extractor.rb +247 -0
- data/lib/omnizip/extraction.rb +115 -0
- data/lib/omnizip/file_type/mime_classifier.rb +144 -0
- data/lib/omnizip/file_type.rb +117 -0
- data/lib/omnizip/filter.rb +141 -0
- data/lib/omnizip/filter_pipeline.rb +108 -0
- data/lib/omnizip/filter_registry.rb +113 -0
- data/lib/omnizip/filters/bcj.rb +277 -0
- data/lib/omnizip/filters/bcj2/constants.rb +53 -0
- data/lib/omnizip/filters/bcj2/decoder.rb +263 -0
- data/lib/omnizip/filters/bcj2/encoder.rb +58 -0
- data/lib/omnizip/filters/bcj2/stream_data.rb +91 -0
- data/lib/omnizip/filters/bcj2.rb +93 -0
- data/lib/omnizip/filters/bcj_arm.rb +174 -0
- data/lib/omnizip/filters/bcj_arm64.rb +242 -0
- data/lib/omnizip/filters/bcj_ia64.rb +194 -0
- data/lib/omnizip/filters/bcj_ppc.rb +188 -0
- data/lib/omnizip/filters/bcj_sparc.rb +174 -0
- data/lib/omnizip/filters/bcj_x86.rb +191 -0
- data/lib/omnizip/filters/delta.rb +195 -0
- data/lib/omnizip/filters/xz_delta.rb +258 -0
- data/lib/omnizip/filters.rb +20 -0
- data/lib/omnizip/format_detector.rb +166 -0
- data/lib/omnizip/formats/.keep +0 -0
- data/lib/omnizip/formats/bzip2_file.rb +159 -0
- data/lib/omnizip/formats/cpio/bounded_io.rb +66 -0
- data/lib/omnizip/formats/cpio/constants.rb +55 -0
- data/lib/omnizip/formats/cpio/entry.rb +389 -0
- data/lib/omnizip/formats/cpio/reader.rb +194 -0
- data/lib/omnizip/formats/cpio/writer.rb +232 -0
- data/lib/omnizip/formats/cpio.rb +132 -0
- data/lib/omnizip/formats/format_spec_loader.rb +228 -0
- data/lib/omnizip/formats/gzip.rb +236 -0
- data/lib/omnizip/formats/iso/directory_builder.rb +321 -0
- data/lib/omnizip/formats/iso/directory_record.rb +162 -0
- data/lib/omnizip/formats/iso/joliet.rb +204 -0
- data/lib/omnizip/formats/iso/path_table.rb +125 -0
- data/lib/omnizip/formats/iso/reader.rb +194 -0
- data/lib/omnizip/formats/iso/rock_ridge.rb +349 -0
- data/lib/omnizip/formats/iso/volume_builder.rb +319 -0
- data/lib/omnizip/formats/iso/volume_descriptor.rb +168 -0
- data/lib/omnizip/formats/iso/writer.rb +543 -0
- data/lib/omnizip/formats/iso.rb +133 -0
- data/lib/omnizip/formats/lzip.rb +161 -0
- data/lib/omnizip/formats/lzma_alone.rb +149 -0
- data/lib/omnizip/formats/msi/cab_extractor.rb +181 -0
- data/lib/omnizip/formats/msi/constants.rb +157 -0
- data/lib/omnizip/formats/msi/directory_resolver.rb +188 -0
- data/lib/omnizip/formats/msi/entry.rb +134 -0
- data/lib/omnizip/formats/msi/reader.rb +301 -0
- data/lib/omnizip/formats/msi/string_pool.rb +246 -0
- data/lib/omnizip/formats/msi/table_parser.rb +244 -0
- data/lib/omnizip/formats/msi.rb +73 -0
- data/lib/omnizip/formats/ole/allocation_table.rb +263 -0
- data/lib/omnizip/formats/ole/constants.rb +61 -0
- data/lib/omnizip/formats/ole/dirent.rb +439 -0
- data/lib/omnizip/formats/ole/header.rb +199 -0
- data/lib/omnizip/formats/ole/ranges_io.rb +347 -0
- data/lib/omnizip/formats/ole/storage.rb +482 -0
- data/lib/omnizip/formats/ole/types/variant.rb +338 -0
- data/lib/omnizip/formats/ole.rb +143 -0
- data/lib/omnizip/formats/rar/archive_repairer.rb +240 -0
- data/lib/omnizip/formats/rar/archive_verifier.rb +192 -0
- data/lib/omnizip/formats/rar/block_parser.rb +282 -0
- data/lib/omnizip/formats/rar/compression/bit_stream.rb +180 -0
- data/lib/omnizip/formats/rar/compression/dispatcher.rb +210 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/decoder.rb +212 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/encoder.rb +154 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_builder.rb +217 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_coder.rb +189 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/match_finder.rb +135 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/sliding_window.rb +165 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman.rb +25 -0
- data/lib/omnizip/formats/rar/compression/ppmd/context.rb +103 -0
- data/lib/omnizip/formats/rar/compression/ppmd/decoder.rb +238 -0
- data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +250 -0
- data/lib/omnizip/formats/rar/compression/ppmd.rb +16 -0
- data/lib/omnizip/formats/rar/compression.rb +17 -0
- data/lib/omnizip/formats/rar/compression_method_registry.rb +106 -0
- data/lib/omnizip/formats/rar/constants.rb +88 -0
- data/lib/omnizip/formats/rar/decompressor.rb +316 -0
- data/lib/omnizip/formats/rar/external_writer.rb +311 -0
- data/lib/omnizip/formats/rar/header.rb +209 -0
- data/lib/omnizip/formats/rar/license_validator.rb +111 -0
- data/lib/omnizip/formats/rar/models/rar_archive.rb +77 -0
- data/lib/omnizip/formats/rar/models/rar_entry.rb +65 -0
- data/lib/omnizip/formats/rar/models/rar_volume.rb +56 -0
- data/lib/omnizip/formats/rar/models.rb +14 -0
- data/lib/omnizip/formats/rar/parity_handler.rb +299 -0
- data/lib/omnizip/formats/rar/rar5/compression/lzma.rb +200 -0
- data/lib/omnizip/formats/rar/rar5/compression/lzss.rb +584 -0
- data/lib/omnizip/formats/rar/rar5/compression/store.rb +60 -0
- data/lib/omnizip/formats/rar/rar5/crc32.rb +39 -0
- data/lib/omnizip/formats/rar/rar5/encryption/aes256_cbc.rb +96 -0
- data/lib/omnizip/formats/rar/rar5/encryption/encryption_header.rb +114 -0
- data/lib/omnizip/formats/rar/rar5/encryption/encryption_manager.rb +162 -0
- data/lib/omnizip/formats/rar/rar5/encryption/key_derivation.rb +97 -0
- data/lib/omnizip/formats/rar/rar5/header.rb +208 -0
- data/lib/omnizip/formats/rar/rar5/models/encryption_options.rb +74 -0
- data/lib/omnizip/formats/rar/rar5/models/recovery_options.rb +63 -0
- data/lib/omnizip/formats/rar/rar5/models/solid_options.rb +63 -0
- data/lib/omnizip/formats/rar/rar5/models/volume_options.rb +74 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/ARCHITECTURE.md +290 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_manager.rb +260 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_splitter.rb +155 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_writer.rb +176 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_encoder.rb +108 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_manager.rb +139 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_stream.rb +121 -0
- data/lib/omnizip/formats/rar/rar5/vint.rb +53 -0
- data/lib/omnizip/formats/rar/rar5/writer.rb +540 -0
- data/lib/omnizip/formats/rar/rar_format_base.rb +239 -0
- data/lib/omnizip/formats/rar/reader.rb +377 -0
- data/lib/omnizip/formats/rar/recovery_record.rb +245 -0
- data/lib/omnizip/formats/rar/volume_manager.rb +166 -0
- data/lib/omnizip/formats/rar/writer.rb +437 -0
- data/lib/omnizip/formats/rar.rb +193 -0
- data/lib/omnizip/formats/rar3/compressor.rb +71 -0
- data/lib/omnizip/formats/rar3/decompressor.rb +64 -0
- data/lib/omnizip/formats/rar3/reader.rb +107 -0
- data/lib/omnizip/formats/rar3/writer.rb +220 -0
- data/lib/omnizip/formats/rar3.rb +15 -0
- data/lib/omnizip/formats/rar5/compressor.rb +71 -0
- data/lib/omnizip/formats/rar5/decompressor.rb +64 -0
- data/lib/omnizip/formats/rar5/reader.rb +130 -0
- data/lib/omnizip/formats/rar5/writer.rb +65 -0
- data/lib/omnizip/formats/rar5.rb +15 -0
- data/lib/omnizip/formats/rpm/constants.rb +58 -0
- data/lib/omnizip/formats/rpm/entry.rb +106 -0
- data/lib/omnizip/formats/rpm/header.rb +106 -0
- data/lib/omnizip/formats/rpm/lead.rb +132 -0
- data/lib/omnizip/formats/rpm/tag.rb +228 -0
- data/lib/omnizip/formats/rpm/writer.rb +652 -0
- data/lib/omnizip/formats/rpm.rb +458 -0
- data/lib/omnizip/formats/seven_zip/bcj2_stream_decompressor.rb +274 -0
- data/lib/omnizip/formats/seven_zip/coder_chain.rb +169 -0
- data/lib/omnizip/formats/seven_zip/constants.rb +126 -0
- data/lib/omnizip/formats/seven_zip/encoded_header.rb +110 -0
- data/lib/omnizip/formats/seven_zip/encrypted_header.rb +139 -0
- data/lib/omnizip/formats/seven_zip/file_collector.rb +143 -0
- data/lib/omnizip/formats/seven_zip/header.rb +102 -0
- data/lib/omnizip/formats/seven_zip/header_encryptor.rb +132 -0
- data/lib/omnizip/formats/seven_zip/header_writer.rb +425 -0
- data/lib/omnizip/formats/seven_zip/models/coder_info.rb +30 -0
- data/lib/omnizip/formats/seven_zip/models/file_entry.rb +65 -0
- data/lib/omnizip/formats/seven_zip/models/folder.rb +67 -0
- data/lib/omnizip/formats/seven_zip/models/stream_info.rb +42 -0
- data/lib/omnizip/formats/seven_zip/models.rb +20 -0
- data/lib/omnizip/formats/seven_zip/parser.rb +671 -0
- data/lib/omnizip/formats/seven_zip/reader.rb +626 -0
- data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +611 -0
- data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +308 -0
- data/lib/omnizip/formats/seven_zip/stream_compressor.rb +151 -0
- data/lib/omnizip/formats/seven_zip/stream_decompressor.rb +166 -0
- data/lib/omnizip/formats/seven_zip/writer.rb +801 -0
- data/lib/omnizip/formats/seven_zip.rb +179 -0
- data/lib/omnizip/formats/tar/constants.rb +73 -0
- data/lib/omnizip/formats/tar/entry.rb +98 -0
- data/lib/omnizip/formats/tar/header.rb +206 -0
- data/lib/omnizip/formats/tar/reader.rb +119 -0
- data/lib/omnizip/formats/tar/writer.rb +212 -0
- data/lib/omnizip/formats/tar.rb +74 -0
- data/lib/omnizip/formats/xar/constants.rb +119 -0
- data/lib/omnizip/formats/xar/entry.rb +268 -0
- data/lib/omnizip/formats/xar/header.rb +223 -0
- data/lib/omnizip/formats/xar/reader.rb +400 -0
- data/lib/omnizip/formats/xar/toc.rb +570 -0
- data/lib/omnizip/formats/xar/writer.rb +461 -0
- data/lib/omnizip/formats/xar.rb +145 -0
- data/lib/omnizip/formats/xz/reader.rb +121 -0
- data/lib/omnizip/formats/xz/writer.rb +416 -0
- data/lib/omnizip/formats/xz.rb +210 -0
- data/lib/omnizip/formats/xz_const.rb +49 -0
- data/lib/omnizip/formats/xz_impl/block_decoder.rb +761 -0
- data/lib/omnizip/formats/xz_impl/block_encoder.rb +304 -0
- data/lib/omnizip/formats/xz_impl/block_header.rb +210 -0
- data/lib/omnizip/formats/xz_impl/block_header_parser.rb +182 -0
- data/lib/omnizip/formats/xz_impl/index_decoder.rb +174 -0
- data/lib/omnizip/formats/xz_impl/index_encoder.rb +120 -0
- data/lib/omnizip/formats/xz_impl/stream_decoder.rb +470 -0
- data/lib/omnizip/formats/xz_impl/stream_encoder.rb +95 -0
- data/lib/omnizip/formats/xz_impl/stream_footer.rb +79 -0
- data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +115 -0
- data/lib/omnizip/formats/xz_impl/stream_header.rb +53 -0
- data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +106 -0
- data/lib/omnizip/formats/xz_impl/vli.rb +128 -0
- data/lib/omnizip/formats/xz_impl.rb +52 -0
- data/lib/omnizip/formats/zip/central_directory_header.rb +223 -0
- data/lib/omnizip/formats/zip/constants.rb +69 -0
- data/lib/omnizip/formats/zip/end_of_central_directory.rb +132 -0
- data/lib/omnizip/formats/zip/local_file_header.rb +136 -0
- data/lib/omnizip/formats/zip/reader.rb +376 -0
- data/lib/omnizip/formats/zip/unix_extra_field.rb +153 -0
- data/lib/omnizip/formats/zip/writer.rb +489 -0
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +102 -0
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +64 -0
- data/lib/omnizip/formats/zip/zip64_extra_field.rb +112 -0
- data/lib/omnizip/formats/zip.rb +50 -0
- data/lib/omnizip/formats.rb +29 -0
- data/lib/omnizip/implementations/base/lzma2_decoder_base.rb +75 -0
- data/lib/omnizip/implementations/base/lzma2_encoder_base.rb +128 -0
- data/lib/omnizip/implementations/base/lzma_decoder_base.rb +83 -0
- data/lib/omnizip/implementations/base/lzma_encoder_base.rb +108 -0
- data/lib/omnizip/implementations/base/state_machine_base.rb +182 -0
- data/lib/omnizip/implementations/base.rb +19 -0
- data/lib/omnizip/implementations/rust/archive.rb +93 -0
- data/lib/omnizip/implementations/rust/library.rb +221 -0
- data/lib/omnizip/implementations/rust.rb +26 -0
- data/lib/omnizip/implementations/seven_zip/lzma/decoder.rb +428 -0
- data/lib/omnizip/implementations/seven_zip/lzma/encoder.rb +456 -0
- data/lib/omnizip/implementations/seven_zip/lzma/match_finder.rb +285 -0
- data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +197 -0
- data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +194 -0
- data/lib/omnizip/implementations/seven_zip/lzma/state_machine.rb +139 -0
- data/lib/omnizip/implementations/seven_zip/lzma.rb +21 -0
- data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +56 -0
- data/lib/omnizip/implementations/seven_zip/lzma2.rb +12 -0
- data/lib/omnizip/implementations/seven_zip.rb +11 -0
- data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +547 -0
- data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +748 -0
- data/lib/omnizip/implementations/xz_utils/lzma2.rb +13 -0
- data/lib/omnizip/implementations/xz_utils.rb +10 -0
- data/lib/omnizip/implementations.rb +25 -0
- data/lib/omnizip/io/buffered_input.rb +147 -0
- data/lib/omnizip/io/buffered_output.rb +106 -0
- data/lib/omnizip/io/source.rb +143 -0
- data/lib/omnizip/io/stream_manager.rb +117 -0
- data/lib/omnizip/io.rb +15 -0
- data/lib/omnizip/link_handler/hard_link.rb +79 -0
- data/lib/omnizip/link_handler/symbolic_link.rb +74 -0
- data/lib/omnizip/link_handler.rb +129 -0
- data/lib/omnizip/metadata/archive_metadata.rb +114 -0
- data/lib/omnizip/metadata/entry_metadata.rb +153 -0
- data/lib/omnizip/metadata/metadata_editor.rb +169 -0
- data/lib/omnizip/metadata/metadata_registry.rb +71 -0
- data/lib/omnizip/metadata/metadata_validator.rb +101 -0
- data/lib/omnizip/metadata.rb +58 -0
- data/lib/omnizip/models/.keep +0 -0
- data/lib/omnizip/models/algorithm_metadata.rb +41 -0
- data/lib/omnizip/models/attribute_apply.rb +40 -0
- data/lib/omnizip/models/compression_options.rb +53 -0
- data/lib/omnizip/models/conversion_options.rb +79 -0
- data/lib/omnizip/models/conversion_result.rb +141 -0
- data/lib/omnizip/models/eta_result.rb +45 -0
- data/lib/omnizip/models/extraction_rule.rb +115 -0
- data/lib/omnizip/models/filter_chain.rb +142 -0
- data/lib/omnizip/models/filter_config.rb +148 -0
- data/lib/omnizip/models/match_result.rb +141 -0
- data/lib/omnizip/models/optimization_suggestion.rb +95 -0
- data/lib/omnizip/models/parallel_options.rb +116 -0
- data/lib/omnizip/models/performance_result.rb +85 -0
- data/lib/omnizip/models/profile_report.rb +116 -0
- data/lib/omnizip/models/progress_options.rb +39 -0
- data/lib/omnizip/models/split_options.rb +116 -0
- data/lib/omnizip/optimization_registry.rb +45 -0
- data/lib/omnizip/parallel/engine.rb +52 -0
- data/lib/omnizip/parallel/job_queue.rb +209 -0
- data/lib/omnizip/parallel/job_scheduler.rb +225 -0
- data/lib/omnizip/parallel/parallel_compressor.rb +326 -0
- data/lib/omnizip/parallel/parallel_extractor.rb +238 -0
- data/lib/omnizip/parallel/thread_pool.rb +60 -0
- data/lib/omnizip/parallel/worker_pool.rb +227 -0
- data/lib/omnizip/parallel.rb +156 -0
- data/lib/omnizip/parity/chunked_block_processor.rb +194 -0
- data/lib/omnizip/parity/galois16.rb +145 -0
- data/lib/omnizip/parity/models/creator_packet.rb +71 -0
- data/lib/omnizip/parity/models/file_description_packet.rb +131 -0
- data/lib/omnizip/parity/models/ifsc_packet.rb +111 -0
- data/lib/omnizip/parity/models/main_packet.rb +126 -0
- data/lib/omnizip/parity/models/packet.rb +156 -0
- data/lib/omnizip/parity/models/packet_registry.rb +102 -0
- data/lib/omnizip/parity/models/recovery_slice_packet.rb +76 -0
- data/lib/omnizip/parity/models.rb +27 -0
- data/lib/omnizip/parity/par2_creator.rb +496 -0
- data/lib/omnizip/parity/par2_repairer.rb +409 -0
- data/lib/omnizip/parity/par2_verifier.rb +373 -0
- data/lib/omnizip/parity/par2cmdline_algorithm.rb +108 -0
- data/lib/omnizip/parity/par2cmdline_coefficients.rb +78 -0
- data/lib/omnizip/parity/reed_solomon_decoder.rb +263 -0
- data/lib/omnizip/parity/reed_solomon_encoder.rb +108 -0
- data/lib/omnizip/parity/reed_solomon_matrix.rb +339 -0
- data/lib/omnizip/parity.rb +207 -0
- data/lib/omnizip/password/encryption_registry.rb +26 -0
- data/lib/omnizip/password/encryption_strategy.rb +96 -0
- data/lib/omnizip/password/password_validator.rb +129 -0
- data/lib/omnizip/password/winzip_aes_strategy.rb +190 -0
- data/lib/omnizip/password/zip_crypto_strategy.rb +135 -0
- data/lib/omnizip/password.rb +88 -0
- data/lib/omnizip/pipe/stream_compressor.rb +124 -0
- data/lib/omnizip/pipe/stream_decompressor.rb +173 -0
- data/lib/omnizip/pipe.rb +120 -0
- data/lib/omnizip/platform/ntfs_streams.rb +200 -0
- data/lib/omnizip/platform.rb +189 -0
- data/lib/omnizip/profile/archive_profile.rb +36 -0
- data/lib/omnizip/profile/balanced_profile.rb +31 -0
- data/lib/omnizip/profile/binary_profile.rb +33 -0
- data/lib/omnizip/profile/compression_profile.rb +157 -0
- data/lib/omnizip/profile/custom_profile.rb +155 -0
- data/lib/omnizip/profile/fast_profile.rb +31 -0
- data/lib/omnizip/profile/maximum_profile.rb +31 -0
- data/lib/omnizip/profile/profile_detector.rb +108 -0
- data/lib/omnizip/profile/profile_registry.rb +160 -0
- data/lib/omnizip/profile/text_profile.rb +33 -0
- data/lib/omnizip/profile.rb +261 -0
- data/lib/omnizip/profiler/memory_profiler.rb +66 -0
- data/lib/omnizip/profiler/method_profiler.rb +49 -0
- data/lib/omnizip/profiler/report_generator.rb +158 -0
- data/lib/omnizip/profiler.rb +192 -0
- data/lib/omnizip/progress/callback_reporter.rb +34 -0
- data/lib/omnizip/progress/console_reporter.rb +59 -0
- data/lib/omnizip/progress/log_reporter.rb +89 -0
- data/lib/omnizip/progress/operation_progress.rb +118 -0
- data/lib/omnizip/progress/progress_bar.rb +156 -0
- data/lib/omnizip/progress/progress_reporter.rb +40 -0
- data/lib/omnizip/progress/progress_tracker.rb +215 -0
- data/lib/omnizip/progress/silent_reporter.rb +22 -0
- data/lib/omnizip/progress.rb +131 -0
- data/lib/omnizip/registry.rb +111 -0
- data/lib/omnizip/rubyzip_compat.rb +61 -0
- data/lib/omnizip/temp/safe_extract.rb +124 -0
- data/lib/omnizip/temp/temp_file.rb +123 -0
- data/lib/omnizip/temp/temp_file_pool.rb +109 -0
- data/lib/omnizip/temp.rb +184 -0
- data/lib/omnizip/version.rb +5 -0
- data/lib/omnizip/zip/entry.rb +160 -0
- data/lib/omnizip/zip/file.rb +485 -0
- data/lib/omnizip/zip/input_stream.rb +270 -0
- data/lib/omnizip/zip/output_stream.rb +320 -0
- data/lib/omnizip/zip.rb +23 -0
- data/lib/omnizip.rb +157 -0
- data/lib/tasks/msi_test_files.rake +66 -0
- data/lib/tasks/platform_gems.rake +147 -0
- data/lib/tasks/rust_backend.rake +48 -0
- data/readme-docs/advanced-features.adoc +514 -0
- data/readme-docs/api-usage.adoc +445 -0
- data/readme-docs/architecture.adoc +450 -0
- data/readme-docs/archive-formats.adoc +479 -0
- data/readme-docs/cli-usage.adoc +222 -0
- data/readme-docs/compression-algorithms.adoc +446 -0
- data/readme-docs/compression-profiles.adoc +247 -0
- data/readme-docs/encryption-checksums.adoc +328 -0
- data/readme-docs/format-converter.adoc +316 -0
- data/readme-docs/installation.adoc +228 -0
- data/readme-docs/par2-archives.adoc +608 -0
- data/readme-docs/performance-profiler.adoc +389 -0
- data/readme-docs/preprocessing-filters.adoc +280 -0
- data/vendor/libomnizip_ffi.so +0 -0
- data/xz-file-format-1.2.1.txt +1174 -0
- metadata +768 -0
|
@@ -0,0 +1,239 @@
|
|
|
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
|
+
module Omnizip
|
|
10
|
+
module Formats
|
|
11
|
+
module Rar
|
|
12
|
+
# Base class for RAR format implementations
|
|
13
|
+
#
|
|
14
|
+
# This class provides common functionality for RAR v3 and RAR v5 formats,
|
|
15
|
+
# following the Strategy pattern where each version has its own
|
|
16
|
+
# implementation strategy.
|
|
17
|
+
#
|
|
18
|
+
# @abstract Subclass and override {#read_archive}, {#write_archive},
|
|
19
|
+
# {#compress}, and {#decompress} to implement a RAR version strategy.
|
|
20
|
+
class RarFormatBase
|
|
21
|
+
attr_reader :spec, :version
|
|
22
|
+
|
|
23
|
+
# Initialize a RAR format handler
|
|
24
|
+
#
|
|
25
|
+
# @param spec_name [String] The format specification name
|
|
26
|
+
# (e.g., "rar3", "rar5")
|
|
27
|
+
def initialize(spec_name)
|
|
28
|
+
@spec = FormatSpecLoader.load(spec_name)
|
|
29
|
+
@version = @spec.version
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Read a RAR archive
|
|
33
|
+
#
|
|
34
|
+
# @param io [IO] The input stream
|
|
35
|
+
# @return [Array<Entry>] The archive entries
|
|
36
|
+
# @raise [NotImplementedError] Must be implemented by subclasses
|
|
37
|
+
def read_archive(io)
|
|
38
|
+
raise NotImplementedError,
|
|
39
|
+
"#{self.class} must implement #read_archive"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Write a RAR archive
|
|
43
|
+
#
|
|
44
|
+
# @param io [IO] The output stream
|
|
45
|
+
# @param entries [Array<Entry>] The entries to write
|
|
46
|
+
# @return [void]
|
|
47
|
+
# @raise [NotImplementedError] Must be implemented by subclasses
|
|
48
|
+
def write_archive(io, entries)
|
|
49
|
+
raise NotImplementedError,
|
|
50
|
+
"#{self.class} must implement #write_archive"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Compress data
|
|
54
|
+
#
|
|
55
|
+
# @param data [String] The data to compress
|
|
56
|
+
# @param method [Symbol] The compression method
|
|
57
|
+
# @param options [Hash] Compression options
|
|
58
|
+
# @return [String] The compressed data
|
|
59
|
+
# @raise [NotImplementedError] Must be implemented by subclasses
|
|
60
|
+
def compress(data, method: :normal, **options)
|
|
61
|
+
raise NotImplementedError,
|
|
62
|
+
"#{self.class} must implement #compress"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Decompress data
|
|
66
|
+
#
|
|
67
|
+
# @param data [String] The compressed data
|
|
68
|
+
# @param method [Symbol] The compression method
|
|
69
|
+
# @param options [Hash] Decompression options
|
|
70
|
+
# @return [String] The decompressed data
|
|
71
|
+
# @raise [NotImplementedError] Must be implemented by subclasses
|
|
72
|
+
def decompress(data, method: :normal, **options)
|
|
73
|
+
raise NotImplementedError,
|
|
74
|
+
"#{self.class} must implement #decompress"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Verify magic bytes match this format
|
|
78
|
+
#
|
|
79
|
+
# @param io [IO] The input stream
|
|
80
|
+
# @return [Boolean] True if magic bytes match
|
|
81
|
+
def verify_magic_bytes(io)
|
|
82
|
+
magic = spec.magic_bytes.pack("C*")
|
|
83
|
+
bytes = io.read(magic.bytesize)
|
|
84
|
+
io.rewind
|
|
85
|
+
bytes == magic
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Get compression method code from symbol
|
|
89
|
+
#
|
|
90
|
+
# @param method [Symbol] The method name (e.g., :normal, :best)
|
|
91
|
+
# @return [Integer] The method code
|
|
92
|
+
# @raise [FormatError] If method is not supported
|
|
93
|
+
def compression_method_code(method)
|
|
94
|
+
code = spec.format.compression_methods[method]
|
|
95
|
+
return code if code
|
|
96
|
+
|
|
97
|
+
raise FormatError,
|
|
98
|
+
"Unsupported compression method: #{method}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Get compression method symbol from code
|
|
102
|
+
#
|
|
103
|
+
# @param code [Integer] The method code
|
|
104
|
+
# @return [Symbol] The method name
|
|
105
|
+
# @raise [FormatError] If code is not recognized
|
|
106
|
+
def compression_method_name(code)
|
|
107
|
+
methods = spec.format.compression_methods
|
|
108
|
+
name = methods.key(code)
|
|
109
|
+
return name if name
|
|
110
|
+
|
|
111
|
+
# Handle unknown method codes gracefully
|
|
112
|
+
# RAR can have version-specific or PPMd methods not in standard list
|
|
113
|
+
case code
|
|
114
|
+
when 0x00..0x2F then :store # Very old or stored
|
|
115
|
+
when 0x30 then :store
|
|
116
|
+
when 0x31 then :fastest
|
|
117
|
+
when 0x32 then :fast
|
|
118
|
+
when 0x33 then :normal
|
|
119
|
+
when 0x34 then :good
|
|
120
|
+
when 0x35 then :best
|
|
121
|
+
when 0x36..0x40 then :normal # Extended range
|
|
122
|
+
when 0x80..0xFF then :ppmd # PPMd or version-specific
|
|
123
|
+
else :unknown
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Get block type code from symbol
|
|
128
|
+
#
|
|
129
|
+
# @param type [Symbol] The block type name
|
|
130
|
+
# @return [Integer] The block type code
|
|
131
|
+
# @raise [FormatError] If type is not supported
|
|
132
|
+
def block_type_code(type)
|
|
133
|
+
code = spec.format.block_types[type]
|
|
134
|
+
return code if code
|
|
135
|
+
|
|
136
|
+
raise FormatError, "Unknown block type: #{type}"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Get block type symbol from code
|
|
140
|
+
#
|
|
141
|
+
# @param code [Integer] The block type code
|
|
142
|
+
# @return [Symbol] The block type name
|
|
143
|
+
# @raise [FormatError] If code is not recognized
|
|
144
|
+
def block_type_name(code)
|
|
145
|
+
types = spec.format.block_types
|
|
146
|
+
name = types.key(code)
|
|
147
|
+
return name if name
|
|
148
|
+
|
|
149
|
+
raise FormatError, "Unknown block type code: #{code}"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Check if format supports a feature
|
|
153
|
+
#
|
|
154
|
+
# @param feature [Symbol] The feature name
|
|
155
|
+
# @return [Boolean] True if supported
|
|
156
|
+
def supports_feature?(feature)
|
|
157
|
+
features = spec.format.features || {}
|
|
158
|
+
compression_features = spec.format.compression_features || {}
|
|
159
|
+
advanced_features = spec.format.advanced_features || {}
|
|
160
|
+
|
|
161
|
+
features[feature] ||
|
|
162
|
+
compression_features[feature] ||
|
|
163
|
+
advanced_features[feature] ||
|
|
164
|
+
false
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Get encryption algorithm
|
|
168
|
+
#
|
|
169
|
+
# @return [String, nil] The encryption algorithm or nil
|
|
170
|
+
def encryption_algorithm
|
|
171
|
+
return nil unless spec.format.encryption&.supported
|
|
172
|
+
|
|
173
|
+
algorithms = spec.format.encryption.algorithms
|
|
174
|
+
algorithms&.first
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Get dictionary size for compression level
|
|
178
|
+
#
|
|
179
|
+
# @param level [Symbol] The compression level
|
|
180
|
+
# @return [Integer] The dictionary size code
|
|
181
|
+
def dictionary_size_code(level)
|
|
182
|
+
spec.format.dictionary_sizes[level] ||
|
|
183
|
+
spec.format.dictionary_sizes[:auto]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
protected
|
|
187
|
+
|
|
188
|
+
# Read a vint (variable-length integer) from stream
|
|
189
|
+
#
|
|
190
|
+
# Used in RAR 5 format for variable-length encoding
|
|
191
|
+
#
|
|
192
|
+
# @param io [IO] The input stream
|
|
193
|
+
# @return [Integer] The decoded integer
|
|
194
|
+
def read_vint(io)
|
|
195
|
+
result = 0
|
|
196
|
+
shift = 0
|
|
197
|
+
|
|
198
|
+
loop do
|
|
199
|
+
byte = io.read(1)&.unpack1("C")
|
|
200
|
+
raise FormatError, "Unexpected EOF" unless byte
|
|
201
|
+
|
|
202
|
+
result |= (byte & 0x7F) << shift
|
|
203
|
+
break if byte.nobits?(0x80)
|
|
204
|
+
|
|
205
|
+
shift += 7
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
result
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Write a vint (variable-length integer) to stream
|
|
212
|
+
#
|
|
213
|
+
# @param io [IO] The output stream
|
|
214
|
+
# @param value [Integer] The value to encode
|
|
215
|
+
# @return [void]
|
|
216
|
+
def write_vint(io, value)
|
|
217
|
+
loop do
|
|
218
|
+
byte = value & 0x7F
|
|
219
|
+
value >>= 7
|
|
220
|
+
|
|
221
|
+
byte |= 0x80 if value.positive?
|
|
222
|
+
io.write([byte].pack("C"))
|
|
223
|
+
|
|
224
|
+
break if value.zero?
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Calculate CRC32 checksum
|
|
229
|
+
#
|
|
230
|
+
# @param data [String] The data to checksum
|
|
231
|
+
# @return [Integer] The CRC32 value
|
|
232
|
+
def calculate_crc32(data)
|
|
233
|
+
require "zlib"
|
|
234
|
+
Zlib.crc32(data)
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "stringio"
|
|
5
|
+
require "zlib"
|
|
6
|
+
|
|
7
|
+
module Omnizip
|
|
8
|
+
module Formats
|
|
9
|
+
module Rar
|
|
10
|
+
# RAR archive reader
|
|
11
|
+
# Provides read-only access to RAR archives (single and multi-volume)
|
|
12
|
+
class Reader
|
|
13
|
+
include Omnizip::Formats::Rar::Constants
|
|
14
|
+
|
|
15
|
+
attr_reader :file_path, :header, :archive_info,
|
|
16
|
+
:volume_manager
|
|
17
|
+
|
|
18
|
+
# Initialize reader with file path
|
|
19
|
+
#
|
|
20
|
+
# @param file_path [String] Path to RAR file
|
|
21
|
+
def initialize(file_path)
|
|
22
|
+
@file_path = file_path
|
|
23
|
+
@header = nil
|
|
24
|
+
@entries = []
|
|
25
|
+
@archive_info = Models::RarArchive.new(file_path)
|
|
26
|
+
@volume_manager = VolumeManager.new(file_path)
|
|
27
|
+
@use_native = true # Prefer native decompression
|
|
28
|
+
@opened = false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Open and parse RAR archive
|
|
32
|
+
#
|
|
33
|
+
# @raise [RuntimeError] if file cannot be opened or parsed
|
|
34
|
+
def open
|
|
35
|
+
File.open(@file_path, "rb") do |io|
|
|
36
|
+
parse_archive(io)
|
|
37
|
+
end
|
|
38
|
+
@opened = true
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# All entries, parsing on first use — a Reader is always
|
|
43
|
+
# usable; #open remains for eager loading
|
|
44
|
+
#
|
|
45
|
+
# @return [Array<Models::RarEntry>] File entries
|
|
46
|
+
def entries
|
|
47
|
+
ensure_open
|
|
48
|
+
@entries
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# List all files in archive
|
|
52
|
+
#
|
|
53
|
+
# @return [Array<Models::RarEntry>] File entries
|
|
54
|
+
def list_files
|
|
55
|
+
entries
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Extract file to output path
|
|
59
|
+
#
|
|
60
|
+
# @param entry_name [String] File name to extract
|
|
61
|
+
# @param output_path [String] Destination path
|
|
62
|
+
# @param password [String, nil] Optional password
|
|
63
|
+
# @raise [RuntimeError] if entry not found or extraction fails
|
|
64
|
+
def extract_entry(entry_name, output_path, password: nil)
|
|
65
|
+
ensure_open
|
|
66
|
+
entry = @entries.find { |e| e.name == entry_name }
|
|
67
|
+
raise Errno::ENOENT, "Entry not found: #{entry_name}" unless entry
|
|
68
|
+
|
|
69
|
+
# Create directory if needed
|
|
70
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
|
71
|
+
|
|
72
|
+
# Extract file
|
|
73
|
+
if entry.directory?
|
|
74
|
+
FileUtils.mkdir_p(output_path)
|
|
75
|
+
else
|
|
76
|
+
# Try native decompression first, fall back to external
|
|
77
|
+
if @use_native && native_decompression_available?(entry)
|
|
78
|
+
extract_entry_native(entry, output_path)
|
|
79
|
+
else
|
|
80
|
+
extract_entry_external(entry_name, output_path, password)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Set timestamp if available
|
|
84
|
+
File.utime(entry.mtime, entry.mtime, output_path) if entry.mtime
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Extract all files to directory
|
|
89
|
+
#
|
|
90
|
+
# @param output_dir [String] Destination directory
|
|
91
|
+
# @param password [String, nil] Optional password
|
|
92
|
+
# @raise [RuntimeError] on extraction error
|
|
93
|
+
def extract_all(output_dir, password: nil)
|
|
94
|
+
ensure_open
|
|
95
|
+
FileUtils.mkdir_p(output_dir)
|
|
96
|
+
|
|
97
|
+
# Use decompressor to extract all
|
|
98
|
+
base_path = @volume_manager.first_volume&.path || @file_path
|
|
99
|
+
Decompressor.extract(base_path, output_dir, password: password)
|
|
100
|
+
|
|
101
|
+
# Set timestamps for extracted files
|
|
102
|
+
@entries.each do |entry|
|
|
103
|
+
next unless entry.mtime
|
|
104
|
+
|
|
105
|
+
output_path = File.join(output_dir, entry.name)
|
|
106
|
+
next unless File.exist?(output_path)
|
|
107
|
+
|
|
108
|
+
File.utime(entry.mtime, entry.mtime, output_path)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Check if archive is valid RAR format
|
|
113
|
+
#
|
|
114
|
+
# @return [Boolean] true if valid
|
|
115
|
+
def valid?
|
|
116
|
+
!@header.nil? && @header.valid?
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Get volumes in multi-volume archive
|
|
120
|
+
#
|
|
121
|
+
# @return [Array<String>] Volume paths
|
|
122
|
+
def volumes
|
|
123
|
+
@volume_manager.volume_paths
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Get total number of volumes
|
|
127
|
+
#
|
|
128
|
+
# @return [Integer] Number of volumes
|
|
129
|
+
def total_volumes
|
|
130
|
+
@volume_manager.volume_count
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Check if multi-volume archive
|
|
134
|
+
#
|
|
135
|
+
# @return [Boolean] true if multi-volume
|
|
136
|
+
def multi_volume?
|
|
137
|
+
@volume_manager.multi_volume? || @header&.is_multi_volume
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
# Parse the archive once, on demand
|
|
143
|
+
def ensure_open
|
|
144
|
+
open unless @opened
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Parse RAR archive structure
|
|
148
|
+
#
|
|
149
|
+
# @param io [IO] Input stream
|
|
150
|
+
def parse_archive(io)
|
|
151
|
+
# Read and validate header
|
|
152
|
+
@header = Header.read(io)
|
|
153
|
+
raise Omnizip::InvalidArchiveError, "Invalid RAR archive" unless @header.valid?
|
|
154
|
+
|
|
155
|
+
# Update archive info
|
|
156
|
+
@archive_info.version = @header.version
|
|
157
|
+
@archive_info.flags = @header.flags
|
|
158
|
+
@archive_info.is_multi_volume = @header.is_multi_volume
|
|
159
|
+
@archive_info.volumes = @volume_manager.volumes
|
|
160
|
+
|
|
161
|
+
# Detect recovery records
|
|
162
|
+
detect_recovery_records
|
|
163
|
+
|
|
164
|
+
# Parse entries natively first (full metadata: sizes,
|
|
165
|
+
# directory flags); fall back to the external decompressor
|
|
166
|
+
# when native parsing yields nothing (e.g. encrypted
|
|
167
|
+
# headers).
|
|
168
|
+
parse_entries_from_header
|
|
169
|
+
parse_entries_with_decompressor if @entries.empty?
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Detect recovery records in archive
|
|
173
|
+
def detect_recovery_records
|
|
174
|
+
recovery = RecoveryRecord.new(@header.version)
|
|
175
|
+
|
|
176
|
+
# Check for integrated recovery
|
|
177
|
+
File.open(@file_path, "rb") do |io|
|
|
178
|
+
recovery.parse_from_archive(io, @header.flags)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Check for external .rev files
|
|
182
|
+
rev_files = recovery.detect_external_files(@file_path)
|
|
183
|
+
recovery.load_external_files(rev_files) if rev_files.any?
|
|
184
|
+
|
|
185
|
+
# Update archive info
|
|
186
|
+
@archive_info.has_recovery = recovery.available?
|
|
187
|
+
@archive_info.recovery_percent = recovery.protection_percent
|
|
188
|
+
@archive_info.recovery_files = recovery.external_files
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Parse entries using decompressor
|
|
192
|
+
#
|
|
193
|
+
# This uses the external decompressor to list archive contents
|
|
194
|
+
# Falls back to native parser if decompressor fails or unavailable
|
|
195
|
+
def parse_entries_with_decompressor
|
|
196
|
+
unless Decompressor.available?
|
|
197
|
+
# If decompressor not available, create minimal entries from header
|
|
198
|
+
parse_entries_from_header
|
|
199
|
+
return
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Try to list archive contents with external tool
|
|
203
|
+
begin
|
|
204
|
+
entry_info = Decompressor.list(@file_path)
|
|
205
|
+
entry_info.each do |info|
|
|
206
|
+
entry = Models::RarEntry.new
|
|
207
|
+
entry.name = info[:name]
|
|
208
|
+
entry.size = info[:size]
|
|
209
|
+
entry.compressed_size = info[:compressed_size]
|
|
210
|
+
entry.is_dir = info[:is_dir]
|
|
211
|
+
entry.mtime = info[:mtime]
|
|
212
|
+
entry.version = @header.version
|
|
213
|
+
|
|
214
|
+
@entries << entry
|
|
215
|
+
@archive_info.total_size += entry.size
|
|
216
|
+
@archive_info.compressed_size += entry.compressed_size
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
@archive_info.entries = @entries
|
|
220
|
+
rescue StandardError => e
|
|
221
|
+
# Fall back to native parser if external decompressor fails
|
|
222
|
+
warn "External decompressor failed: #{e.message}"
|
|
223
|
+
warn "Falling back to native block parser"
|
|
224
|
+
parse_entries_from_header
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Parse entries from header (fallback when decompressor unavailable)
|
|
229
|
+
#
|
|
230
|
+
# This provides basic information but cannot extract files
|
|
231
|
+
def parse_entries_from_header
|
|
232
|
+
# Reset to after header
|
|
233
|
+
File.open(@file_path, "rb") do |io|
|
|
234
|
+
Header.read(io) # Skip header
|
|
235
|
+
|
|
236
|
+
# Try to parse file blocks
|
|
237
|
+
parser = BlockParser.new(@header.version)
|
|
238
|
+
|
|
239
|
+
loop do
|
|
240
|
+
pos = io.pos
|
|
241
|
+
break if io.eof?
|
|
242
|
+
|
|
243
|
+
begin
|
|
244
|
+
entry = parser.parse_file_block(io)
|
|
245
|
+
break unless entry
|
|
246
|
+
|
|
247
|
+
@entries << entry
|
|
248
|
+
@archive_info.total_size += entry.size
|
|
249
|
+
@archive_info.compressed_size += entry.compressed_size
|
|
250
|
+
rescue StandardError => e
|
|
251
|
+
warn "Failed to parse block at #{pos}: #{e.message}"
|
|
252
|
+
break
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
@archive_info.entries = @entries
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Check if native decompression is available for entry
|
|
261
|
+
#
|
|
262
|
+
# @param entry [Models::RarEntry] File entry
|
|
263
|
+
# @return [Boolean] true if native decompression supported
|
|
264
|
+
def native_decompression_available?(entry)
|
|
265
|
+
# Native decompression only for RAR4 for now
|
|
266
|
+
return false unless @header.version == 4
|
|
267
|
+
|
|
268
|
+
!entry.nil? && !entry.method.nil?
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Extract entry using native decompression
|
|
272
|
+
#
|
|
273
|
+
# @param entry [Models::RarEntry] File entry
|
|
274
|
+
# @param output_path [String] Destination path
|
|
275
|
+
def extract_entry_native(entry, output_path)
|
|
276
|
+
compressed_data = read_compressed_data(entry)
|
|
277
|
+
method = entry.method || 0x30
|
|
278
|
+
|
|
279
|
+
output = StringIO.new(+"")
|
|
280
|
+
Compression::Dispatcher.decompress(method, compressed_data, output)
|
|
281
|
+
|
|
282
|
+
# The native LZ/PPMd decoders understand Omnizip's own
|
|
283
|
+
# encoder output, not official RAR streams — without this
|
|
284
|
+
# check, real WinRAR archives would silently extract as
|
|
285
|
+
# garbage. The header CRC is authoritative: on mismatch,
|
|
286
|
+
# fall back to unrar.
|
|
287
|
+
data = output.string
|
|
288
|
+
if entry.crc && Zlib.crc32(data) != entry.crc
|
|
289
|
+
raise Compression::Dispatcher::DecompressionError,
|
|
290
|
+
"CRC mismatch for #{entry.name}"
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
File.binwrite(output_path, data)
|
|
294
|
+
rescue StandardError => e
|
|
295
|
+
# Fall back to external decompressor on error
|
|
296
|
+
warn "Native decompression failed for #{entry.name}: #{e.message}"
|
|
297
|
+
warn "Falling back to external decompressor"
|
|
298
|
+
extract_entry_external(entry.name, output_path, nil)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Extract entry using external decompressor
|
|
302
|
+
#
|
|
303
|
+
# @param entry_name [String] Entry name
|
|
304
|
+
# @param output_path [String] Destination path
|
|
305
|
+
# @param password [String, nil] Optional password
|
|
306
|
+
def extract_entry_external(entry_name, output_path, password)
|
|
307
|
+
base_path = @volume_manager.first_volume&.path || @file_path
|
|
308
|
+
Decompressor.extract_entry(base_path, entry_name,
|
|
309
|
+
output_path, password: password)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Read compressed data for entry
|
|
313
|
+
#
|
|
314
|
+
# @param entry [Models::RarEntry] File entry
|
|
315
|
+
# @return [StringIO] Compressed data stream
|
|
316
|
+
def read_compressed_data(entry)
|
|
317
|
+
# The parser records each entry's data offset, so native
|
|
318
|
+
# extraction is a single seek + read
|
|
319
|
+
if entry.data_offset && entry.compressed_size
|
|
320
|
+
File.open(@file_path, "rb") do |io|
|
|
321
|
+
io.seek(entry.data_offset)
|
|
322
|
+
return StringIO.new(io.read(entry.compressed_size) || "")
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Fallback for entries parsed without offsets (e.g. by older
|
|
327
|
+
# parsers): walk the blocks to locate the entry
|
|
328
|
+
File.open(@file_path, "rb") do |io|
|
|
329
|
+
Header.read(io)
|
|
330
|
+
|
|
331
|
+
parser = BlockParser.new(@header.version)
|
|
332
|
+
|
|
333
|
+
loop do
|
|
334
|
+
block_start = io.pos
|
|
335
|
+
break if io.eof?
|
|
336
|
+
|
|
337
|
+
crc_bytes = io.read(2)
|
|
338
|
+
break unless crc_bytes
|
|
339
|
+
|
|
340
|
+
type_byte = io.read(1)
|
|
341
|
+
break unless type_byte
|
|
342
|
+
|
|
343
|
+
head_type = type_byte.ord
|
|
344
|
+
|
|
345
|
+
break if head_type == BLOCK_ENDARC
|
|
346
|
+
|
|
347
|
+
io.seek(block_start)
|
|
348
|
+
|
|
349
|
+
unless head_type == BLOCK_FILE
|
|
350
|
+
io.read(2) # CRC
|
|
351
|
+
io.read(1) # TYPE
|
|
352
|
+
io.read(2) # FLAGS
|
|
353
|
+
size = io.read(2)&.unpack1("v") || 0
|
|
354
|
+
|
|
355
|
+
# 7 bytes consumed (CRC/TYPE/FLAGS/SIZE); skip the rest
|
|
356
|
+
remaining = size - 7
|
|
357
|
+
io.read(remaining) if remaining.positive?
|
|
358
|
+
next
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
test_entry = parser.parse_file_block(io)
|
|
362
|
+
|
|
363
|
+
if test_entry && test_entry.name == entry.name
|
|
364
|
+
data_start = io.pos - entry.compressed_size
|
|
365
|
+
|
|
366
|
+
io.seek(data_start)
|
|
367
|
+
return StringIO.new(io.read(entry.compressed_size) || "")
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
StringIO.new("")
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
end
|