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.
Files changed (511) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +32 -0
  4. data/.rubocop_todo.yml +754 -0
  5. data/COPYING +502 -0
  6. data/Gemfile +17 -0
  7. data/LICENSE +12 -0
  8. data/README.adoc +1045 -0
  9. data/Rakefile +12 -0
  10. data/benchmark/README.md +260 -0
  11. data/benchmark/benchmark_suite.rb +125 -0
  12. data/benchmark/compression_bench.rb +181 -0
  13. data/benchmark/filter_bench.rb +180 -0
  14. data/benchmark/models/benchmark_result.rb +59 -0
  15. data/benchmark/models/comparison_result.rb +69 -0
  16. data/benchmark/profile_suite.rb +167 -0
  17. data/benchmark/reporter.rb +150 -0
  18. data/benchmark/run_benchmarks.rb +66 -0
  19. data/benchmark/test_data.rb +137 -0
  20. data/config/formats/rar3_spec.yml +91 -0
  21. data/config/formats/rar5_spec.yml +102 -0
  22. data/docs/.github/workflows/docs.yml +142 -0
  23. data/docs/.gitignore +21 -0
  24. data/docs/.lychee.toml +67 -0
  25. data/docs/Gemfile +13 -0
  26. data/docs/RAR_WRITE_SUPPORT.md +26 -0
  27. data/docs/README.md +101 -0
  28. data/docs/_config.yml +112 -0
  29. data/docs/assets/logo.svg +1 -0
  30. data/docs/assets/omnizip-logo.pdf +1540 -11
  31. data/docs/comparison/feature-matrix.adoc +694 -0
  32. data/docs/comparison/index.adoc +113 -0
  33. data/docs/comparison/vs-7zip.adoc +309 -0
  34. data/docs/comparison/vs-peazip.adoc +77 -0
  35. data/docs/comparison/vs-rubyzip.adoc +342 -0
  36. data/docs/comparison/vs-winrar.adoc +100 -0
  37. data/docs/compatibility.adoc +579 -0
  38. data/docs/concepts/index.adoc +129 -0
  39. data/docs/developer/architecture.adoc +256 -0
  40. data/docs/developer/contributing.adoc +158 -0
  41. data/docs/developer/index.adoc +25 -0
  42. data/docs/developer/testing.adoc +212 -0
  43. data/docs/getting-started/basic-usage.adoc +271 -0
  44. data/docs/getting-started/index.adoc +42 -0
  45. data/docs/getting-started/installation.adoc +138 -0
  46. data/docs/getting-started/quick-start.adoc +185 -0
  47. data/docs/getting-started/your-first-archive.adoc +218 -0
  48. data/docs/guides/advanced-features/encryption.adoc +300 -0
  49. data/docs/guides/advanced-features/index.adoc +49 -0
  50. data/docs/guides/advanced-features/parallel-processing.adoc +246 -0
  51. data/docs/guides/advanced-features/progress-tracking.adoc +320 -0
  52. data/docs/guides/advanced-features/streaming.adoc +212 -0
  53. data/docs/guides/archive-formats/gzip-format.adoc +107 -0
  54. data/docs/guides/archive-formats/index.adoc +130 -0
  55. data/docs/guides/archive-formats/rar-format.adoc +104 -0
  56. data/docs/guides/archive-formats/rar5.adoc +521 -0
  57. data/docs/guides/archive-formats/seven-zip-format.adoc +35 -0
  58. data/docs/guides/archive-formats/tar-format.adoc +106 -0
  59. data/docs/guides/archive-formats/xz-format.adoc +118 -0
  60. data/docs/guides/archive-formats/zip-format.adoc +35 -0
  61. data/docs/guides/compression-algorithms/bzip2.adoc +113 -0
  62. data/docs/guides/compression-algorithms/deflate.adoc +319 -0
  63. data/docs/guides/compression-algorithms/index.adoc +190 -0
  64. data/docs/guides/compression-algorithms/lzma.adoc +398 -0
  65. data/docs/guides/compression-algorithms/lzma2.adoc +327 -0
  66. data/docs/guides/compression-algorithms/ppmd.adoc +316 -0
  67. data/docs/guides/compression-algorithms/zstandard.adoc +361 -0
  68. data/docs/guides/creating-archives.adoc +354 -0
  69. data/docs/guides/extracting-archives.adoc +53 -0
  70. data/docs/guides/format-conversion.adoc +64 -0
  71. data/docs/guides/index.adoc +49 -0
  72. data/docs/guides/migration-rubyzip.adoc +217 -0
  73. data/docs/guides/parity-archives.adoc +605 -0
  74. data/docs/guides/performance-tuning.adoc +88 -0
  75. data/docs/index.adoc +218 -0
  76. data/docs/lychee.toml +67 -0
  77. data/docs/reference/api/overview.adoc +188 -0
  78. data/docs/reference/cli/compress-command.adoc +114 -0
  79. data/docs/reference/cli/overview.adoc +140 -0
  80. data/docs/reference/index.adoc +26 -0
  81. data/docs/resources/faq.adoc +185 -0
  82. data/docs/resources/quick-reference.adoc +222 -0
  83. data/docs/troubleshooting/index.adoc +208 -0
  84. data/examples/api_comparison.rb +205 -0
  85. data/examples/deflate64_example.rb +96 -0
  86. data/examples/par2_demo.rb +121 -0
  87. data/examples/quick_start_native.rb +150 -0
  88. data/examples/quick_start_rubyzip.rb +115 -0
  89. data/examples/rubyzip_compatibility_demo.rb +194 -0
  90. data/exe/omnizip +27 -0
  91. data/lib/omnizip/algorithm.rb +130 -0
  92. data/lib/omnizip/algorithm_registry.rb +86 -0
  93. data/lib/omnizip/algorithms/.keep +0 -0
  94. data/lib/omnizip/algorithms/bzip2/bwt.rb +225 -0
  95. data/lib/omnizip/algorithms/bzip2/decoder.rb +193 -0
  96. data/lib/omnizip/algorithms/bzip2/encoder.rb +237 -0
  97. data/lib/omnizip/algorithms/bzip2/huffman.rb +206 -0
  98. data/lib/omnizip/algorithms/bzip2/mtf.rb +101 -0
  99. data/lib/omnizip/algorithms/bzip2/rle.rb +151 -0
  100. data/lib/omnizip/algorithms/bzip2.rb +130 -0
  101. data/lib/omnizip/algorithms/deflate/constants.rb +28 -0
  102. data/lib/omnizip/algorithms/deflate/decoder.rb +38 -0
  103. data/lib/omnizip/algorithms/deflate/encoder.rb +46 -0
  104. data/lib/omnizip/algorithms/deflate.rb +128 -0
  105. data/lib/omnizip/algorithms/deflate64/constants.rb +45 -0
  106. data/lib/omnizip/algorithms/deflate64/decoder.rb +153 -0
  107. data/lib/omnizip/algorithms/deflate64/encoder.rb +98 -0
  108. data/lib/omnizip/algorithms/deflate64/huffman_coder.rb +354 -0
  109. data/lib/omnizip/algorithms/deflate64/lz77_encoder.rb +142 -0
  110. data/lib/omnizip/algorithms/deflate64.rb +109 -0
  111. data/lib/omnizip/algorithms/lzma/bit_model.rb +120 -0
  112. data/lib/omnizip/algorithms/lzma/constants.rb +112 -0
  113. data/lib/omnizip/algorithms/lzma/decoder.rb +148 -0
  114. data/lib/omnizip/algorithms/lzma/dictionary.rb +69 -0
  115. data/lib/omnizip/algorithms/lzma/distance_coder.rb +415 -0
  116. data/lib/omnizip/algorithms/lzma/encoder.rb +142 -0
  117. data/lib/omnizip/algorithms/lzma/length_coder.rb +260 -0
  118. data/lib/omnizip/algorithms/lzma/literal_decoder.rb +320 -0
  119. data/lib/omnizip/algorithms/lzma/literal_encoder.rb +210 -0
  120. data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +341 -0
  121. data/lib/omnizip/algorithms/lzma/lzma_alone_decoder.rb +192 -0
  122. data/lib/omnizip/algorithms/lzma/lzma_state.rb +128 -0
  123. data/lib/omnizip/algorithms/lzma/match.rb +32 -0
  124. data/lib/omnizip/algorithms/lzma/match_finder.rb +205 -0
  125. data/lib/omnizip/algorithms/lzma/match_finder_config.rb +142 -0
  126. data/lib/omnizip/algorithms/lzma/match_finder_factory.rb +88 -0
  127. data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +130 -0
  128. data/lib/omnizip/algorithms/lzma/probability_models.rb +72 -0
  129. data/lib/omnizip/algorithms/lzma/range_coder.rb +85 -0
  130. data/lib/omnizip/algorithms/lzma/range_decoder.rb +434 -0
  131. data/lib/omnizip/algorithms/lzma/range_encoder.rb +194 -0
  132. data/lib/omnizip/algorithms/lzma/state.rb +127 -0
  133. data/lib/omnizip/algorithms/lzma/xz_buffered_range_encoder.rb +325 -0
  134. data/lib/omnizip/algorithms/lzma/xz_encoder.rb +426 -0
  135. data/lib/omnizip/algorithms/lzma/xz_encoder_fast.rb +645 -0
  136. data/lib/omnizip/algorithms/lzma/xz_match_finder_adapter.rb +227 -0
  137. data/lib/omnizip/algorithms/lzma/xz_price_calculator.rb +169 -0
  138. data/lib/omnizip/algorithms/lzma/xz_probability_models.rb +261 -0
  139. data/lib/omnizip/algorithms/lzma/xz_range_encoder.rb +223 -0
  140. data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +331 -0
  141. data/lib/omnizip/algorithms/lzma/xz_state.rb +116 -0
  142. data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +2055 -0
  143. data/lib/omnizip/algorithms/lzma.rb +238 -0
  144. data/lib/omnizip/algorithms/lzma2/chunk_manager.rb +182 -0
  145. data/lib/omnizip/algorithms/lzma2/constants.rb +41 -0
  146. data/lib/omnizip/algorithms/lzma2/encoder.rb +147 -0
  147. data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +161 -0
  148. data/lib/omnizip/algorithms/lzma2/properties.rb +179 -0
  149. data/lib/omnizip/algorithms/lzma2/simple_lzma2_encoder.rb +127 -0
  150. data/lib/omnizip/algorithms/lzma2/xz_encoder_adapter.rb +85 -0
  151. data/lib/omnizip/algorithms/lzma2.rb +141 -0
  152. data/lib/omnizip/algorithms/ppmd7/constants.rb +74 -0
  153. data/lib/omnizip/algorithms/ppmd7/context.rb +154 -0
  154. data/lib/omnizip/algorithms/ppmd7/decoder.rb +126 -0
  155. data/lib/omnizip/algorithms/ppmd7/encoder.rb +163 -0
  156. data/lib/omnizip/algorithms/ppmd7/model.rb +248 -0
  157. data/lib/omnizip/algorithms/ppmd7/symbol_state.rb +57 -0
  158. data/lib/omnizip/algorithms/ppmd7.rb +116 -0
  159. data/lib/omnizip/algorithms/ppmd8/constants.rb +61 -0
  160. data/lib/omnizip/algorithms/ppmd8/context.rb +34 -0
  161. data/lib/omnizip/algorithms/ppmd8/decoder.rb +107 -0
  162. data/lib/omnizip/algorithms/ppmd8/encoder.rb +138 -0
  163. data/lib/omnizip/algorithms/ppmd8/model.rb +250 -0
  164. data/lib/omnizip/algorithms/ppmd8/restoration_method.rb +78 -0
  165. data/lib/omnizip/algorithms/ppmd8.rb +82 -0
  166. data/lib/omnizip/algorithms/ppmd_base.rb +138 -0
  167. data/lib/omnizip/algorithms/sevenzip_lzma2.rb +123 -0
  168. data/lib/omnizip/algorithms/xz_lzma2.rb +118 -0
  169. data/lib/omnizip/algorithms/zstandard/constants.rb +25 -0
  170. data/lib/omnizip/algorithms/zstandard/decoder.rb +46 -0
  171. data/lib/omnizip/algorithms/zstandard/encoder.rb +51 -0
  172. data/lib/omnizip/algorithms/zstandard.rb +138 -0
  173. data/lib/omnizip/buffer/memory_archive.rb +251 -0
  174. data/lib/omnizip/buffer/memory_extractor.rb +224 -0
  175. data/lib/omnizip/buffer.rb +176 -0
  176. data/lib/omnizip/checksum_registry.rb +114 -0
  177. data/lib/omnizip/checksums/crc32.rb +100 -0
  178. data/lib/omnizip/checksums/crc64.rb +101 -0
  179. data/lib/omnizip/checksums/crc_base.rb +158 -0
  180. data/lib/omnizip/checksums/verifier.rb +131 -0
  181. data/lib/omnizip/chunked/memory_manager.rb +194 -0
  182. data/lib/omnizip/chunked/reader.rb +78 -0
  183. data/lib/omnizip/chunked/writer.rb +120 -0
  184. data/lib/omnizip/chunked.rb +129 -0
  185. data/lib/omnizip/cli/output_formatter.rb +104 -0
  186. data/lib/omnizip/cli.rb +572 -0
  187. data/lib/omnizip/commands/.keep +0 -0
  188. data/lib/omnizip/commands/archive_create_command.rb +427 -0
  189. data/lib/omnizip/commands/archive_extract_command.rb +272 -0
  190. data/lib/omnizip/commands/archive_list_command.rb +218 -0
  191. data/lib/omnizip/commands/archive_repair_command.rb +131 -0
  192. data/lib/omnizip/commands/archive_verify_command.rb +117 -0
  193. data/lib/omnizip/commands/compress_command.rb +117 -0
  194. data/lib/omnizip/commands/decompress_command.rb +120 -0
  195. data/lib/omnizip/commands/list_command.rb +53 -0
  196. data/lib/omnizip/commands/metadata_command.rb +153 -0
  197. data/lib/omnizip/commands/parity_create_command.rb +122 -0
  198. data/lib/omnizip/commands/parity_repair_command.rb +122 -0
  199. data/lib/omnizip/commands/parity_verify_command.rb +124 -0
  200. data/lib/omnizip/commands/profile_list_command.rb +56 -0
  201. data/lib/omnizip/commands/profile_show_command.rb +44 -0
  202. data/lib/omnizip/convenience.rb +359 -0
  203. data/lib/omnizip/converter/conversion_registry.rb +49 -0
  204. data/lib/omnizip/converter/conversion_strategy.rb +121 -0
  205. data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +97 -0
  206. data/lib/omnizip/converter/zip_to_seven_zip_strategy.rb +112 -0
  207. data/lib/omnizip/converter.rb +105 -0
  208. data/lib/omnizip/crypto/aes256/cipher.rb +100 -0
  209. data/lib/omnizip/crypto/aes256/constants.rb +28 -0
  210. data/lib/omnizip/crypto/aes256/key_derivation.rb +101 -0
  211. data/lib/omnizip/crypto/aes256.rb +102 -0
  212. data/lib/omnizip/error.rb +106 -0
  213. data/lib/omnizip/eta/exponential_smoothing_estimator.rb +98 -0
  214. data/lib/omnizip/eta/moving_average_estimator.rb +99 -0
  215. data/lib/omnizip/eta/rate_calculator.rb +104 -0
  216. data/lib/omnizip/eta/sample_history.rb +143 -0
  217. data/lib/omnizip/eta/time_estimator.rb +106 -0
  218. data/lib/omnizip/eta.rb +63 -0
  219. data/lib/omnizip/extraction/filter_chain.rb +177 -0
  220. data/lib/omnizip/extraction/glob_pattern.rb +140 -0
  221. data/lib/omnizip/extraction/pattern_matcher.rb +70 -0
  222. data/lib/omnizip/extraction/predicate_pattern.rb +52 -0
  223. data/lib/omnizip/extraction/regex_pattern.rb +50 -0
  224. data/lib/omnizip/extraction/selective_extractor.rb +240 -0
  225. data/lib/omnizip/extraction.rb +111 -0
  226. data/lib/omnizip/file_type/mime_classifier.rb +144 -0
  227. data/lib/omnizip/file_type.rb +113 -0
  228. data/lib/omnizip/filter.rb +139 -0
  229. data/lib/omnizip/filter_pipeline.rb +108 -0
  230. data/lib/omnizip/filter_registry.rb +166 -0
  231. data/lib/omnizip/filters/bcj.rb +279 -0
  232. data/lib/omnizip/filters/bcj2/constants.rb +53 -0
  233. data/lib/omnizip/filters/bcj2/decoder.rb +200 -0
  234. data/lib/omnizip/filters/bcj2/encoder.rb +61 -0
  235. data/lib/omnizip/filters/bcj2/stream_data.rb +93 -0
  236. data/lib/omnizip/filters/bcj2.rb +99 -0
  237. data/lib/omnizip/filters/bcj_arm.rb +176 -0
  238. data/lib/omnizip/filters/bcj_arm64.rb +244 -0
  239. data/lib/omnizip/filters/bcj_ia64.rb +196 -0
  240. data/lib/omnizip/filters/bcj_ppc.rb +190 -0
  241. data/lib/omnizip/filters/bcj_sparc.rb +176 -0
  242. data/lib/omnizip/filters/bcj_x86.rb +193 -0
  243. data/lib/omnizip/filters/delta.rb +196 -0
  244. data/lib/omnizip/filters/filter_base.rb +72 -0
  245. data/lib/omnizip/filters/registry.rb +123 -0
  246. data/lib/omnizip/filters/xz_delta.rb +258 -0
  247. data/lib/omnizip/format_detector.rb +162 -0
  248. data/lib/omnizip/format_registry.rb +59 -0
  249. data/lib/omnizip/formats/.keep +0 -0
  250. data/lib/omnizip/formats/bzip2_file.rb +172 -0
  251. data/lib/omnizip/formats/cpio/constants.rb +55 -0
  252. data/lib/omnizip/formats/cpio/entry.rb +385 -0
  253. data/lib/omnizip/formats/cpio/reader.rb +196 -0
  254. data/lib/omnizip/formats/cpio/writer.rb +234 -0
  255. data/lib/omnizip/formats/cpio.rb +140 -0
  256. data/lib/omnizip/formats/format_spec_loader.rb +230 -0
  257. data/lib/omnizip/formats/gzip.rb +238 -0
  258. data/lib/omnizip/formats/iso/directory_builder.rb +297 -0
  259. data/lib/omnizip/formats/iso/directory_record.rb +152 -0
  260. data/lib/omnizip/formats/iso/joliet.rb +204 -0
  261. data/lib/omnizip/formats/iso/path_table.rb +125 -0
  262. data/lib/omnizip/formats/iso/reader.rb +197 -0
  263. data/lib/omnizip/formats/iso/rock_ridge.rb +349 -0
  264. data/lib/omnizip/formats/iso/volume_builder.rb +320 -0
  265. data/lib/omnizip/formats/iso/volume_descriptor.rb +168 -0
  266. data/lib/omnizip/formats/iso/writer.rb +530 -0
  267. data/lib/omnizip/formats/iso.rb +140 -0
  268. data/lib/omnizip/formats/lzip.rb +175 -0
  269. data/lib/omnizip/formats/lzma_alone.rb +171 -0
  270. data/lib/omnizip/formats/rar/archive_repairer.rb +243 -0
  271. data/lib/omnizip/formats/rar/archive_verifier.rb +195 -0
  272. data/lib/omnizip/formats/rar/block_parser.rb +243 -0
  273. data/lib/omnizip/formats/rar/compression/bit_stream.rb +180 -0
  274. data/lib/omnizip/formats/rar/compression/dispatcher.rb +217 -0
  275. data/lib/omnizip/formats/rar/compression/lz77_huffman/decoder.rb +216 -0
  276. data/lib/omnizip/formats/rar/compression/lz77_huffman/encoder.rb +158 -0
  277. data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_builder.rb +217 -0
  278. data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_coder.rb +189 -0
  279. data/lib/omnizip/formats/rar/compression/lz77_huffman/match_finder.rb +135 -0
  280. data/lib/omnizip/formats/rar/compression/lz77_huffman/sliding_window.rb +165 -0
  281. data/lib/omnizip/formats/rar/compression/ppmd/context.rb +105 -0
  282. data/lib/omnizip/formats/rar/compression/ppmd/decoder.rb +219 -0
  283. data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +262 -0
  284. data/lib/omnizip/formats/rar/compression_method_registry.rb +106 -0
  285. data/lib/omnizip/formats/rar/constants.rb +82 -0
  286. data/lib/omnizip/formats/rar/decompressor.rb +238 -0
  287. data/lib/omnizip/formats/rar/external_writer.rb +312 -0
  288. data/lib/omnizip/formats/rar/header.rb +192 -0
  289. data/lib/omnizip/formats/rar/license_validator.rb +109 -0
  290. data/lib/omnizip/formats/rar/models/rar_archive.rb +77 -0
  291. data/lib/omnizip/formats/rar/models/rar_entry.rb +65 -0
  292. data/lib/omnizip/formats/rar/models/rar_volume.rb +56 -0
  293. data/lib/omnizip/formats/rar/parity_handler.rb +292 -0
  294. data/lib/omnizip/formats/rar/rar5/compression/lzma.rb +202 -0
  295. data/lib/omnizip/formats/rar/rar5/compression/lzss.rb +578 -0
  296. data/lib/omnizip/formats/rar/rar5/compression/store.rb +60 -0
  297. data/lib/omnizip/formats/rar/rar5/crc32.rb +39 -0
  298. data/lib/omnizip/formats/rar/rar5/encryption/aes256_cbc.rb +97 -0
  299. data/lib/omnizip/formats/rar/rar5/encryption/encryption_header.rb +114 -0
  300. data/lib/omnizip/formats/rar/rar5/encryption/encryption_manager.rb +166 -0
  301. data/lib/omnizip/formats/rar/rar5/encryption/key_derivation.rb +97 -0
  302. data/lib/omnizip/formats/rar/rar5/header.rb +187 -0
  303. data/lib/omnizip/formats/rar/rar5/models/encryption_options.rb +74 -0
  304. data/lib/omnizip/formats/rar/rar5/models/recovery_options.rb +63 -0
  305. data/lib/omnizip/formats/rar/rar5/models/solid_options.rb +63 -0
  306. data/lib/omnizip/formats/rar/rar5/models/volume_options.rb +74 -0
  307. data/lib/omnizip/formats/rar/rar5/multi_volume/ARCHITECTURE.md +290 -0
  308. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_manager.rb +264 -0
  309. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_splitter.rb +155 -0
  310. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_writer.rb +194 -0
  311. data/lib/omnizip/formats/rar/rar5/solid/solid_encoder.rb +109 -0
  312. data/lib/omnizip/formats/rar/rar5/solid/solid_manager.rb +142 -0
  313. data/lib/omnizip/formats/rar/rar5/solid/solid_stream.rb +121 -0
  314. data/lib/omnizip/formats/rar/rar5/vint.rb +65 -0
  315. data/lib/omnizip/formats/rar/rar5/writer.rb +466 -0
  316. data/lib/omnizip/formats/rar/rar_format_base.rb +241 -0
  317. data/lib/omnizip/formats/rar/reader.rb +366 -0
  318. data/lib/omnizip/formats/rar/recovery_record.rb +245 -0
  319. data/lib/omnizip/formats/rar/volume_manager.rb +168 -0
  320. data/lib/omnizip/formats/rar/writer.rb +431 -0
  321. data/lib/omnizip/formats/rar.rb +205 -0
  322. data/lib/omnizip/formats/rar3/compressor.rb +73 -0
  323. data/lib/omnizip/formats/rar3/decompressor.rb +66 -0
  324. data/lib/omnizip/formats/rar3/reader.rb +386 -0
  325. data/lib/omnizip/formats/rar3/writer.rb +219 -0
  326. data/lib/omnizip/formats/rar5/compressor.rb +73 -0
  327. data/lib/omnizip/formats/rar5/decompressor.rb +66 -0
  328. data/lib/omnizip/formats/rar5/reader.rb +342 -0
  329. data/lib/omnizip/formats/rar5/writer.rb +214 -0
  330. data/lib/omnizip/formats/seven_zip/coder_chain.rb +150 -0
  331. data/lib/omnizip/formats/seven_zip/constants.rb +126 -0
  332. data/lib/omnizip/formats/seven_zip/encoded_header.rb +114 -0
  333. data/lib/omnizip/formats/seven_zip/encrypted_header.rb +142 -0
  334. data/lib/omnizip/formats/seven_zip/file_collector.rb +144 -0
  335. data/lib/omnizip/formats/seven_zip/header.rb +106 -0
  336. data/lib/omnizip/formats/seven_zip/header_encryptor.rb +134 -0
  337. data/lib/omnizip/formats/seven_zip/header_writer.rb +466 -0
  338. data/lib/omnizip/formats/seven_zip/models/coder_info.rb +30 -0
  339. data/lib/omnizip/formats/seven_zip/models/file_entry.rb +58 -0
  340. data/lib/omnizip/formats/seven_zip/models/folder.rb +69 -0
  341. data/lib/omnizip/formats/seven_zip/models/stream_info.rb +42 -0
  342. data/lib/omnizip/formats/seven_zip/parser.rb +660 -0
  343. data/lib/omnizip/formats/seven_zip/reader.rb +458 -0
  344. data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +632 -0
  345. data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +315 -0
  346. data/lib/omnizip/formats/seven_zip/stream_compressor.rb +151 -0
  347. data/lib/omnizip/formats/seven_zip/stream_decompressor.rb +162 -0
  348. data/lib/omnizip/formats/seven_zip/writer.rb +740 -0
  349. data/lib/omnizip/formats/seven_zip.rb +93 -0
  350. data/lib/omnizip/formats/tar/constants.rb +73 -0
  351. data/lib/omnizip/formats/tar/entry.rb +94 -0
  352. data/lib/omnizip/formats/tar/header.rb +168 -0
  353. data/lib/omnizip/formats/tar/reader.rb +121 -0
  354. data/lib/omnizip/formats/tar/writer.rb +216 -0
  355. data/lib/omnizip/formats/tar.rb +84 -0
  356. data/lib/omnizip/formats/xz/reader.rb +116 -0
  357. data/lib/omnizip/formats/xz.rb +237 -0
  358. data/lib/omnizip/formats/xz_impl/block_decoder.rb +754 -0
  359. data/lib/omnizip/formats/xz_impl/block_encoder.rb +306 -0
  360. data/lib/omnizip/formats/xz_impl/block_header.rb +210 -0
  361. data/lib/omnizip/formats/xz_impl/block_header_parser.rb +186 -0
  362. data/lib/omnizip/formats/xz_impl/constants.rb +49 -0
  363. data/lib/omnizip/formats/xz_impl/index_decoder.rb +174 -0
  364. data/lib/omnizip/formats/xz_impl/index_encoder.rb +122 -0
  365. data/lib/omnizip/formats/xz_impl/stream_decoder.rb +468 -0
  366. data/lib/omnizip/formats/xz_impl/stream_encoder.rb +99 -0
  367. data/lib/omnizip/formats/xz_impl/stream_footer.rb +81 -0
  368. data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +117 -0
  369. data/lib/omnizip/formats/xz_impl/stream_header.rb +55 -0
  370. data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +108 -0
  371. data/lib/omnizip/formats/xz_impl/vli.rb +128 -0
  372. data/lib/omnizip/formats/xz_impl/writer.rb +421 -0
  373. data/lib/omnizip/formats/zip/central_directory_header.rb +195 -0
  374. data/lib/omnizip/formats/zip/constants.rb +69 -0
  375. data/lib/omnizip/formats/zip/end_of_central_directory.rb +133 -0
  376. data/lib/omnizip/formats/zip/local_file_header.rb +138 -0
  377. data/lib/omnizip/formats/zip/reader.rb +250 -0
  378. data/lib/omnizip/formats/zip/unix_extra_field.rb +153 -0
  379. data/lib/omnizip/formats/zip/writer.rb +375 -0
  380. data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +104 -0
  381. data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +66 -0
  382. data/lib/omnizip/formats/zip/zip64_extra_field.rb +114 -0
  383. data/lib/omnizip/formats/zip.rb +50 -0
  384. data/lib/omnizip/implementations/base/lzma2_decoder_base.rb +75 -0
  385. data/lib/omnizip/implementations/base/lzma2_encoder_base.rb +128 -0
  386. data/lib/omnizip/implementations/base/lzma_decoder_base.rb +83 -0
  387. data/lib/omnizip/implementations/base/lzma_encoder_base.rb +108 -0
  388. data/lib/omnizip/implementations/base/state_machine_base.rb +182 -0
  389. data/lib/omnizip/implementations/seven_zip/lzma/decoder.rb +421 -0
  390. data/lib/omnizip/implementations/seven_zip/lzma/encoder.rb +465 -0
  391. data/lib/omnizip/implementations/seven_zip/lzma/match_finder.rb +288 -0
  392. data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +200 -0
  393. data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +197 -0
  394. data/lib/omnizip/implementations/seven_zip/lzma/state_machine.rb +141 -0
  395. data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +519 -0
  396. data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +723 -0
  397. data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +750 -0
  398. data/lib/omnizip/io/buffered_input.rb +146 -0
  399. data/lib/omnizip/io/buffered_output.rb +105 -0
  400. data/lib/omnizip/io/stream_manager.rb +115 -0
  401. data/lib/omnizip/link_handler/hard_link.rb +79 -0
  402. data/lib/omnizip/link_handler/symbolic_link.rb +74 -0
  403. data/lib/omnizip/link_handler.rb +124 -0
  404. data/lib/omnizip/metadata/archive_metadata.rb +114 -0
  405. data/lib/omnizip/metadata/entry_metadata.rb +146 -0
  406. data/lib/omnizip/metadata/metadata_editor.rb +171 -0
  407. data/lib/omnizip/metadata/metadata_registry.rb +64 -0
  408. data/lib/omnizip/metadata/metadata_validator.rb +99 -0
  409. data/lib/omnizip/metadata.rb +57 -0
  410. data/lib/omnizip/models/.keep +0 -0
  411. data/lib/omnizip/models/algorithm_metadata.rb +73 -0
  412. data/lib/omnizip/models/compression_options.rb +71 -0
  413. data/lib/omnizip/models/conversion_options.rb +87 -0
  414. data/lib/omnizip/models/conversion_result.rb +135 -0
  415. data/lib/omnizip/models/eta_result.rb +46 -0
  416. data/lib/omnizip/models/extraction_rule.rb +115 -0
  417. data/lib/omnizip/models/filter_chain.rb +144 -0
  418. data/lib/omnizip/models/filter_config.rb +183 -0
  419. data/lib/omnizip/models/match_result.rb +124 -0
  420. data/lib/omnizip/models/optimization_suggestion.rb +91 -0
  421. data/lib/omnizip/models/parallel_options.rb +104 -0
  422. data/lib/omnizip/models/performance_result.rb +79 -0
  423. data/lib/omnizip/models/profile_report.rb +82 -0
  424. data/lib/omnizip/models/progress_options.rb +38 -0
  425. data/lib/omnizip/models/split_options.rb +116 -0
  426. data/lib/omnizip/optimization_registry.rb +81 -0
  427. data/lib/omnizip/parallel/job_queue.rb +209 -0
  428. data/lib/omnizip/parallel/job_scheduler.rb +203 -0
  429. data/lib/omnizip/parallel/parallel_compressor.rb +347 -0
  430. data/lib/omnizip/parallel/parallel_extractor.rb +329 -0
  431. data/lib/omnizip/parallel/worker_pool.rb +223 -0
  432. data/lib/omnizip/parallel.rb +149 -0
  433. data/lib/omnizip/parity/chunked_block_processor.rb +196 -0
  434. data/lib/omnizip/parity/galois16.rb +145 -0
  435. data/lib/omnizip/parity/models/creator_packet.rb +73 -0
  436. data/lib/omnizip/parity/models/file_description_packet.rb +133 -0
  437. data/lib/omnizip/parity/models/ifsc_packet.rb +123 -0
  438. data/lib/omnizip/parity/models/main_packet.rb +128 -0
  439. data/lib/omnizip/parity/models/packet.rb +156 -0
  440. data/lib/omnizip/parity/models/packet_registry.rb +109 -0
  441. data/lib/omnizip/parity/models/recovery_slice_packet.rb +78 -0
  442. data/lib/omnizip/parity/par2_creator.rb +531 -0
  443. data/lib/omnizip/parity/par2_repairer.rb +407 -0
  444. data/lib/omnizip/parity/par2_verifier.rb +364 -0
  445. data/lib/omnizip/parity/par2cmdline_algorithm.rb +110 -0
  446. data/lib/omnizip/parity/par2cmdline_coefficients.rb +78 -0
  447. data/lib/omnizip/parity/reed_solomon_decoder.rb +266 -0
  448. data/lib/omnizip/parity/reed_solomon_encoder.rb +111 -0
  449. data/lib/omnizip/parity/reed_solomon_matrix.rb +342 -0
  450. data/lib/omnizip/parity.rb +186 -0
  451. data/lib/omnizip/password/encryption_registry.rb +65 -0
  452. data/lib/omnizip/password/encryption_strategy.rb +96 -0
  453. data/lib/omnizip/password/password_validator.rb +129 -0
  454. data/lib/omnizip/password/winzip_aes_strategy.rb +192 -0
  455. data/lib/omnizip/password/zip_crypto_strategy.rb +141 -0
  456. data/lib/omnizip/password.rb +87 -0
  457. data/lib/omnizip/pipe/stream_compressor.rb +124 -0
  458. data/lib/omnizip/pipe/stream_decompressor.rb +174 -0
  459. data/lib/omnizip/pipe.rb +121 -0
  460. data/lib/omnizip/platform/ntfs_streams.rb +201 -0
  461. data/lib/omnizip/platform.rb +189 -0
  462. data/lib/omnizip/profile/archive_profile.rb +39 -0
  463. data/lib/omnizip/profile/balanced_profile.rb +33 -0
  464. data/lib/omnizip/profile/binary_profile.rb +36 -0
  465. data/lib/omnizip/profile/compression_profile.rb +158 -0
  466. data/lib/omnizip/profile/custom_profile.rb +157 -0
  467. data/lib/omnizip/profile/fast_profile.rb +33 -0
  468. data/lib/omnizip/profile/maximum_profile.rb +33 -0
  469. data/lib/omnizip/profile/profile_detector.rb +110 -0
  470. data/lib/omnizip/profile/profile_registry.rb +161 -0
  471. data/lib/omnizip/profile/text_profile.rb +36 -0
  472. data/lib/omnizip/profile.rb +190 -0
  473. data/lib/omnizip/profiler/memory_profiler.rb +66 -0
  474. data/lib/omnizip/profiler/method_profiler.rb +49 -0
  475. data/lib/omnizip/profiler/report_generator.rb +169 -0
  476. data/lib/omnizip/profiler.rb +204 -0
  477. data/lib/omnizip/progress/callback_reporter.rb +36 -0
  478. data/lib/omnizip/progress/console_reporter.rb +62 -0
  479. data/lib/omnizip/progress/log_reporter.rb +91 -0
  480. data/lib/omnizip/progress/operation_progress.rb +118 -0
  481. data/lib/omnizip/progress/progress_bar.rb +156 -0
  482. data/lib/omnizip/progress/progress_reporter.rb +40 -0
  483. data/lib/omnizip/progress/progress_tracker.rb +190 -0
  484. data/lib/omnizip/progress/silent_reporter.rb +24 -0
  485. data/lib/omnizip/progress.rb +127 -0
  486. data/lib/omnizip/rubyzip_compat.rb +63 -0
  487. data/lib/omnizip/temp/safe_extract.rb +168 -0
  488. data/lib/omnizip/temp/temp_file.rb +124 -0
  489. data/lib/omnizip/temp/temp_file_pool.rb +109 -0
  490. data/lib/omnizip/temp.rb +181 -0
  491. data/lib/omnizip/version.rb +5 -0
  492. data/lib/omnizip/zip/entry.rb +156 -0
  493. data/lib/omnizip/zip/file.rb +485 -0
  494. data/lib/omnizip/zip/input_stream.rb +273 -0
  495. data/lib/omnizip/zip/output_stream.rb +324 -0
  496. data/lib/omnizip.rb +156 -0
  497. data/readme-docs/advanced-features.adoc +515 -0
  498. data/readme-docs/api-usage.adoc +444 -0
  499. data/readme-docs/architecture.adoc +449 -0
  500. data/readme-docs/archive-formats.adoc +479 -0
  501. data/readme-docs/cli-usage.adoc +222 -0
  502. data/readme-docs/compression-algorithms.adoc +442 -0
  503. data/readme-docs/compression-profiles.adoc +247 -0
  504. data/readme-docs/encryption-checksums.adoc +328 -0
  505. data/readme-docs/format-converter.adoc +325 -0
  506. data/readme-docs/installation.adoc +228 -0
  507. data/readme-docs/par2-archives.adoc +608 -0
  508. data/readme-docs/performance-profiler.adoc +389 -0
  509. data/readme-docs/preprocessing-filters.adoc +280 -0
  510. data/xz-file-format-1.2.1.txt +1174 -0
  511. metadata +617 -0
@@ -0,0 +1,366 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "constants"
4
+ require_relative "header"
5
+ require_relative "block_parser"
6
+ require_relative "decompressor"
7
+ require_relative "volume_manager"
8
+ require_relative "recovery_record"
9
+ require_relative "models/rar_entry"
10
+ require_relative "models/rar_archive"
11
+ require_relative "compression/dispatcher"
12
+ require "fileutils"
13
+
14
+ module Omnizip
15
+ module Formats
16
+ module Rar
17
+ # RAR archive reader
18
+ # Provides read-only access to RAR archives (single and multi-volume)
19
+ class Reader
20
+ include Constants
21
+
22
+ attr_reader :file_path, :header, :entries, :archive_info,
23
+ :volume_manager
24
+
25
+ # Initialize reader with file path
26
+ #
27
+ # @param file_path [String] Path to RAR file
28
+ def initialize(file_path)
29
+ @file_path = file_path
30
+ @header = nil
31
+ @entries = []
32
+ @archive_info = Models::RarArchive.new(file_path)
33
+ @volume_manager = VolumeManager.new(file_path)
34
+ @use_native = true # Prefer native decompression
35
+ end
36
+
37
+ # Open and parse RAR archive
38
+ #
39
+ # @raise [RuntimeError] if file cannot be opened or parsed
40
+ def open
41
+ File.open(@file_path, "rb") do |io|
42
+ parse_archive(io)
43
+ end
44
+ self
45
+ end
46
+
47
+ # List all files in archive
48
+ #
49
+ # @return [Array<Models::RarEntry>] File entries
50
+ def list_files
51
+ @entries
52
+ end
53
+
54
+ # Extract file to output path
55
+ #
56
+ # @param entry_name [String] File name to extract
57
+ # @param output_path [String] Destination path
58
+ # @param password [String, nil] Optional password
59
+ # @raise [RuntimeError] if entry not found or extraction fails
60
+ def extract_entry(entry_name, output_path, password: nil)
61
+ entry = @entries.find { |e| e.name == entry_name }
62
+ raise "Entry not found: #{entry_name}" unless entry
63
+
64
+ # Create directory if needed
65
+ FileUtils.mkdir_p(File.dirname(output_path))
66
+
67
+ # Extract file
68
+ if entry.directory?
69
+ FileUtils.mkdir_p(output_path)
70
+ else
71
+ # Try native decompression first, fall back to external
72
+ if @use_native && native_decompression_available?(entry)
73
+ extract_entry_native(entry, output_path)
74
+ else
75
+ extract_entry_external(entry_name, output_path, password)
76
+ end
77
+
78
+ # Set timestamp if available
79
+ File.utime(entry.mtime, entry.mtime, output_path) if entry.mtime
80
+ end
81
+ end
82
+
83
+ # Extract all files to directory
84
+ #
85
+ # @param output_dir [String] Destination directory
86
+ # @param password [String, nil] Optional password
87
+ # @raise [RuntimeError] on extraction error
88
+ def extract_all(output_dir, password: nil)
89
+ FileUtils.mkdir_p(output_dir)
90
+
91
+ # Use decompressor to extract all
92
+ base_path = @volume_manager.first_volume&.path || @file_path
93
+ Decompressor.extract(base_path, output_dir, password: password)
94
+
95
+ # Set timestamps for extracted files
96
+ @entries.each do |entry|
97
+ next unless entry.mtime
98
+
99
+ output_path = File.join(output_dir, entry.name)
100
+ next unless File.exist?(output_path)
101
+
102
+ File.utime(entry.mtime, entry.mtime, output_path)
103
+ end
104
+ end
105
+
106
+ # Check if archive is valid RAR format
107
+ #
108
+ # @return [Boolean] true if valid
109
+ def valid?
110
+ !@header.nil? && @header.valid?
111
+ end
112
+
113
+ # Get volumes in multi-volume archive
114
+ #
115
+ # @return [Array<String>] Volume paths
116
+ def volumes
117
+ @volume_manager.volume_paths
118
+ end
119
+
120
+ # Get total number of volumes
121
+ #
122
+ # @return [Integer] Number of volumes
123
+ def total_volumes
124
+ @volume_manager.volume_count
125
+ end
126
+
127
+ # Check if multi-volume archive
128
+ #
129
+ # @return [Boolean] true if multi-volume
130
+ def multi_volume?
131
+ @volume_manager.multi_volume? || @header&.is_multi_volume
132
+ end
133
+
134
+ private
135
+
136
+ # Parse RAR archive structure
137
+ #
138
+ # @param io [IO] Input stream
139
+ def parse_archive(io)
140
+ # Read and validate header
141
+ @header = Header.read(io)
142
+ raise "Invalid RAR archive" unless @header.valid?
143
+
144
+ # Update archive info
145
+ @archive_info.version = @header.version
146
+ @archive_info.flags = @header.flags
147
+ @archive_info.is_multi_volume = @header.is_multi_volume
148
+ @archive_info.volumes = @volume_manager.volumes
149
+
150
+ # Detect recovery records
151
+ detect_recovery_records
152
+
153
+ # Parse entries using decompressor
154
+ parse_entries_with_decompressor
155
+ end
156
+
157
+ # Detect recovery records in archive
158
+ def detect_recovery_records
159
+ recovery = RecoveryRecord.new(@header.version)
160
+
161
+ # Check for integrated recovery
162
+ File.open(@file_path, "rb") do |io|
163
+ recovery.parse_from_archive(io, @header.flags)
164
+ end
165
+
166
+ # Check for external .rev files
167
+ rev_files = recovery.detect_external_files(@file_path)
168
+ recovery.load_external_files(rev_files) if rev_files.any?
169
+
170
+ # Update archive info
171
+ @archive_info.has_recovery = recovery.available?
172
+ @archive_info.recovery_percent = recovery.protection_percent
173
+ @archive_info.recovery_files = recovery.external_files
174
+ end
175
+
176
+ # Parse entries using decompressor
177
+ #
178
+ # This uses the external decompressor to list archive contents
179
+ # Falls back to native parser if decompressor fails or unavailable
180
+ def parse_entries_with_decompressor
181
+ unless Decompressor.available?
182
+ # If decompressor not available, create minimal entries from header
183
+ parse_entries_from_header
184
+ return
185
+ end
186
+
187
+ # Try to list archive contents with external tool
188
+ begin
189
+ entry_info = Decompressor.list(@file_path)
190
+ entry_info.each do |info|
191
+ entry = Models::RarEntry.new
192
+ entry.name = info[:name]
193
+ entry.size = info[:size]
194
+ entry.compressed_size = info[:compressed_size]
195
+ entry.is_dir = info[:is_dir]
196
+ entry.mtime = info[:mtime]
197
+ entry.version = @header.version
198
+
199
+ @entries << entry
200
+ @archive_info.total_size += entry.size
201
+ @archive_info.compressed_size += entry.compressed_size
202
+ end
203
+
204
+ @archive_info.entries = @entries
205
+ rescue StandardError => e
206
+ # Fall back to native parser if external decompressor fails
207
+ warn "External decompressor failed: #{e.message}"
208
+ warn "Falling back to native block parser"
209
+ parse_entries_from_header
210
+ end
211
+ end
212
+
213
+ # Parse entries from header (fallback when decompressor unavailable)
214
+ #
215
+ # This provides basic information but cannot extract files
216
+ def parse_entries_from_header
217
+ # Reset to after header
218
+ File.open(@file_path, "rb") do |io|
219
+ Header.read(io) # Skip header
220
+
221
+ # Try to parse file blocks
222
+ parser = BlockParser.new(@header.version)
223
+
224
+ loop do
225
+ pos = io.pos
226
+ break if io.eof?
227
+
228
+ begin
229
+ entry = parser.parse_file_block(io)
230
+ break unless entry
231
+
232
+ @entries << entry
233
+ @archive_info.total_size += entry.size
234
+ @archive_info.compressed_size += entry.compressed_size
235
+ rescue StandardError => e
236
+ warn "Failed to parse block at #{pos}: #{e.message}"
237
+ break
238
+ end
239
+ end
240
+ end
241
+
242
+ @archive_info.entries = @entries
243
+ end
244
+
245
+ # Check if native decompression is available for entry
246
+ #
247
+ # @param entry [Models::RarEntry] File entry
248
+ # @return [Boolean] true if native decompression supported
249
+ def native_decompression_available?(entry)
250
+ # Native decompression only for RAR4 for now
251
+ return false unless @header.version == 4
252
+
253
+ # Check if we have the compression method
254
+ return false unless entry.respond_to?(:method) && entry.method
255
+
256
+ # All RAR4 methods are supported by our Dispatcher
257
+ true
258
+ end
259
+
260
+ # Extract entry using native decompression
261
+ #
262
+ # @param entry [Models::RarEntry] File entry
263
+ # @param output_path [String] Destination path
264
+ def extract_entry_native(entry, output_path)
265
+ # Read compressed data from archive
266
+ compressed_data = read_compressed_data(entry)
267
+
268
+ # Decompress using Dispatcher
269
+ File.open(output_path, "wb") do |output|
270
+ # For now, assume METHOD_STORE (0x30)
271
+ # Real implementation would get method from entry
272
+ method = entry.respond_to?(:method) ? entry.method : 0x30
273
+
274
+ Compression::Dispatcher.decompress(method, compressed_data, output)
275
+ end
276
+ rescue StandardError => e
277
+ # Fall back to external decompressor on error
278
+ warn "Native decompression failed for #{entry.name}: #{e.message}"
279
+ warn "Falling back to external decompressor"
280
+ extract_entry_external(entry.name, output_path, nil)
281
+ end
282
+
283
+ # Extract entry using external decompressor
284
+ #
285
+ # @param entry_name [String] Entry name
286
+ # @param output_path [String] Destination path
287
+ # @param password [String, nil] Optional password
288
+ def extract_entry_external(entry_name, output_path, password)
289
+ base_path = @volume_manager.first_volume&.path || @file_path
290
+ Decompressor.extract_entry(base_path, entry_name,
291
+ output_path, password: password)
292
+ end
293
+
294
+ # Read compressed data for entry
295
+ #
296
+ # @param entry [Models::RarEntry] File entry
297
+ # @return [StringIO] Compressed data stream
298
+ def read_compressed_data(entry)
299
+ require "stringio"
300
+
301
+ # Find the entry's data offset in the archive
302
+ File.open(@file_path, "rb") do |io|
303
+ # Skip signature and headers
304
+ Header.read(io)
305
+
306
+ # Parse file blocks to find our entry
307
+ parser = BlockParser.new(@header.version)
308
+
309
+ loop do
310
+ block_start = io.pos
311
+ break if io.eof?
312
+
313
+ # Peek at block type
314
+ crc_bytes = io.read(2)
315
+ break unless crc_bytes
316
+
317
+ type_byte = io.read(1)
318
+ break unless type_byte
319
+
320
+ head_type = type_byte.ord
321
+
322
+ # If end block, stop
323
+ break if head_type == BLOCK_ENDARC
324
+
325
+ # Reset to block start
326
+ io.seek(block_start)
327
+
328
+ # If not a file block, read and skip it
329
+ unless head_type == BLOCK_FILE
330
+ # Read header
331
+ io.read(2) # CRC
332
+ io.read(1) # TYPE
333
+ io.read(2)&.unpack1("v") || 0
334
+ size = io.read(2)&.unpack1("v") || 0
335
+
336
+ # Skip rest of block (size includes TYPE+FLAGS+SIZE = 5 bytes)
337
+ remaining = size - 5
338
+ io.read(remaining) if remaining.positive?
339
+ next
340
+ end
341
+
342
+ # Parse this file block
343
+ test_entry = parser.parse_file_block(io)
344
+
345
+ # Check if this is our entry
346
+ if test_entry && test_entry.name == entry.name
347
+ # BlockParser positions us right after the compressed data
348
+ # So we need to back up and read it
349
+ data_end = io.pos
350
+ data_start = data_end - entry.compressed_size
351
+
352
+ io.seek(data_start)
353
+ compressed = io.read(entry.compressed_size)
354
+
355
+ return StringIO.new(compressed)
356
+ end
357
+ end
358
+ end
359
+
360
+ # If we didn't find it, return empty
361
+ StringIO.new("")
362
+ end
363
+ end
364
+ end
365
+ end
366
+ end
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omnizip
4
+ module Formats
5
+ module Rar
6
+ # RAR recovery record handling
7
+ # Parses and manages recovery records for error correction
8
+ class RecoveryRecord
9
+ attr_reader :version, :type, :protection_percent, :block_size,
10
+ :recovery_blocks, :protected_size, :recovery_size,
11
+ :reed_solomon_params, :external_files
12
+
13
+ # Recovery record types
14
+ TYPE_INTEGRATED = :integrated # Inside archive
15
+ TYPE_EXTERNAL = :external # Separate .rev files
16
+
17
+ # Initialize recovery record
18
+ #
19
+ # @param version [Integer] RAR version (4 or 5)
20
+ def initialize(version)
21
+ @version = version
22
+ @type = TYPE_INTEGRATED
23
+ @protection_percent = 0
24
+ @block_size = 0
25
+ @recovery_blocks = 0
26
+ @protected_size = 0
27
+ @recovery_size = 0
28
+ @reed_solomon_params = {}
29
+ @external_files = []
30
+ end
31
+
32
+ # Check if recovery records are available
33
+ #
34
+ # @return [Boolean] true if recovery available
35
+ def available?
36
+ @recovery_blocks.positive? || @external_files.any?
37
+ end
38
+
39
+ # Check if using external .rev files
40
+ #
41
+ # @return [Boolean] true if external recovery
42
+ def external?
43
+ @type == TYPE_EXTERNAL
44
+ end
45
+
46
+ # Parse recovery record from archive
47
+ #
48
+ # @param io [IO] Input stream
49
+ # @param flags [Integer] Archive flags
50
+ # @return [Boolean] true if recovery record found
51
+ def parse_from_archive(io, flags)
52
+ return false unless recovery_flag_set?(flags)
53
+
54
+ if @version == 5
55
+ parse_rar5_recovery(io)
56
+ else
57
+ parse_rar4_recovery(io)
58
+ end
59
+ end
60
+
61
+ # Detect external .rev files
62
+ #
63
+ # @param archive_path [String] Path to main archive
64
+ # @return [Array<String>] Paths to .rev files
65
+ def detect_external_files(archive_path)
66
+ dir = File.dirname(archive_path)
67
+ basename = File.basename(archive_path, ".*")
68
+
69
+ # Check for RAR5 naming (.part01.rar.rev)
70
+ if archive_path.match?(/\.part\d+\.rar$/i)
71
+ detect_rar5_rev_files(dir, archive_path)
72
+ else
73
+ # RAR4 naming (.r00.rev, .r01.rev)
74
+ detect_rar4_rev_files(dir, basename)
75
+ end
76
+ end
77
+
78
+ # Load external recovery files
79
+ #
80
+ # @param rev_files [Array<String>] Paths to .rev files
81
+ def load_external_files(rev_files)
82
+ @external_files = rev_files.select { |f| File.exist?(f) }
83
+ @type = TYPE_EXTERNAL if @external_files.any?
84
+ end
85
+
86
+ # Get total recovery data size
87
+ #
88
+ # @return [Integer] Total recovery bytes
89
+ def total_recovery_size
90
+ if external?
91
+ @external_files.sum { |f| File.size(f) }
92
+ else
93
+ @recovery_size
94
+ end
95
+ end
96
+
97
+ # Calculate protection level
98
+ #
99
+ # @return [Float] Protection percentage
100
+ def protection_level
101
+ return 0.0 if @protected_size.zero?
102
+
103
+ (@recovery_size.to_f / @protected_size * 100).round(2)
104
+ end
105
+
106
+ private
107
+
108
+ # Check if recovery flag is set in archive flags
109
+ #
110
+ # @param flags [Integer] Archive flags
111
+ # @return [Boolean] true if recovery flag set
112
+ def recovery_flag_set?(flags)
113
+ flags.anybits?(Constants::ARCHIVE_RECOVERY)
114
+ end
115
+
116
+ # Parse RAR5 recovery record
117
+ #
118
+ # @param io [IO] Input stream
119
+ # @return [Boolean] true if parsed successfully
120
+ def parse_rar5_recovery(io)
121
+ # RAR5 recovery record structure
122
+ # Read block header
123
+ block_type = read_vint(io)
124
+ return false unless block_type == Constants::RAR5_HEADER_SERVICE
125
+
126
+ block_flags = read_vint(io)
127
+ _extra_size = read_vint(io) if block_flags.anybits?(0x0001)
128
+
129
+ # Read recovery parameters
130
+ @block_size = read_vint(io)
131
+ @recovery_blocks = read_vint(io)
132
+ @protected_size = read_vint(io)
133
+
134
+ # Reed-Solomon parameters for RAR5
135
+ @reed_solomon_params[:data_blocks] = read_vint(io)
136
+ @reed_solomon_params[:parity_blocks] = read_vint(io)
137
+
138
+ @recovery_size = @block_size * @recovery_blocks
139
+ @protection_percent = protection_level.to_i
140
+
141
+ true
142
+ rescue StandardError
143
+ false
144
+ end
145
+
146
+ # Parse RAR4 recovery record
147
+ #
148
+ # @param io [IO] Input stream
149
+ # @return [Boolean] true if parsed successfully
150
+ def parse_rar4_recovery(io)
151
+ # RAR4 old recovery record structure
152
+ # Skip to recovery block
153
+ block_type = io.read(1)&.unpack1("C")
154
+ return false unless block_type == Constants::BLOCK_OLD_RECOVERY
155
+
156
+ # Read block header
157
+ _block_flags = io.read(2)&.unpack1("v")
158
+ io.read(2)&.unpack1("v")
159
+
160
+ # Read recovery data size
161
+ @recovery_size = io.read(4)&.unpack1("V") || 0
162
+ @protected_size = io.read(4)&.unpack1("V") || 0
163
+ @recovery_blocks = io.read(2)&.unpack1("v") || 0
164
+
165
+ if @recovery_blocks.positive?
166
+ @block_size = @recovery_size / @recovery_blocks
167
+ end
168
+ @protection_percent = protection_level.to_i
169
+
170
+ true
171
+ rescue StandardError
172
+ false
173
+ end
174
+
175
+ # Detect RAR5 external .rev files
176
+ #
177
+ # @param dir [String] Directory path
178
+ # @param archive_path [String] Archive path
179
+ # @return [Array<String>] .rev file paths
180
+ def detect_rar5_rev_files(_dir, archive_path)
181
+ rev_files = []
182
+
183
+ # Check for .partNN.rar.rev files
184
+ if archive_path =~ /^(.+)(\.part\d+\.rar)$/i
185
+ base_name = Regexp.last_match(1)
186
+ Regexp.last_match(2)
187
+
188
+ (1..999).each do |i|
189
+ rev_path = format("%s.part%02d.rar.rev", base_name, i)
190
+ break unless File.exist?(rev_path)
191
+
192
+ rev_files << rev_path
193
+ end
194
+ end
195
+
196
+ rev_files
197
+ end
198
+
199
+ # Detect RAR4 external .rev files
200
+ #
201
+ # @param dir [String] Directory path
202
+ # @param basename [String] Archive base name
203
+ # @return [Array<String>] .rev file paths
204
+ def detect_rar4_rev_files(dir, basename)
205
+ rev_files = []
206
+
207
+ # Check for archive.rev
208
+ main_rev = File.join(dir, "#{basename}.rev")
209
+ rev_files << main_rev if File.exist?(main_rev)
210
+
211
+ # Check for .rNN.rev files
212
+ (0..99).each do |i|
213
+ rev_path = File.join(dir, format("%s.r%02d.rev", basename, i))
214
+ break unless File.exist?(rev_path)
215
+
216
+ rev_files << rev_path
217
+ end
218
+
219
+ rev_files
220
+ end
221
+
222
+ # Read variable-length integer (RAR5)
223
+ #
224
+ # @param io [IO] Input stream
225
+ # @return [Integer] Decoded integer
226
+ def read_vint(io)
227
+ result = 0
228
+ shift = 0
229
+
230
+ loop do
231
+ byte = io.read(1)&.unpack1("C")
232
+ raise "Unexpected EOF" unless byte
233
+
234
+ result |= (byte & 0x7F) << shift
235
+ break if byte.nobits?(0x80)
236
+
237
+ shift += 7
238
+ end
239
+
240
+ result
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end