omnizip 0.4.7-arm-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.
Files changed (632) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +32 -0
  4. data/.rubocop_todo.yml +293 -0
  5. data/CHANGELOG.md +1647 -0
  6. data/CONTEXT.md +68 -0
  7. data/COPYING +502 -0
  8. data/Gemfile +19 -0
  9. data/LICENSE +12 -0
  10. data/README.adoc +1327 -0
  11. data/Rakefile +15 -0
  12. data/TODO.refactor/00-overview.md +67 -0
  13. data/TODO.refactor/01-registry-base-class.md +81 -0
  14. data/TODO.refactor/02-error-hierarchy.md +57 -0
  15. data/TODO.refactor/03-autoload-entry-point.md +69 -0
  16. data/TODO.refactor/04-filter-base-consolidation.md +49 -0
  17. data/TODO.refactor/05-send-private-methods.md +64 -0
  18. data/TODO.refactor/06-instance-variable-access.md +69 -0
  19. data/TODO.refactor/07-respond-to-replacement.md +237 -0
  20. data/TODO.refactor/08-spec-doubles.md +27 -0
  21. data/TODO.refactor/09-cli-shared-module.md +35 -0
  22. data/TODO.refactor/10-format-detector-ocp.md +34 -0
  23. data/TODO.refactor/11-convenience-decoupling.md +38 -0
  24. data/TODO.refactor/12-thread-safety.md +8 -0
  25. data/TODO.refactor/13-lutaml-model-migration.md +158 -0
  26. data/TODO.refactor/14-add-missing-specs.md +25 -0
  27. data/benchmark/README.md +260 -0
  28. data/benchmark/benchmark_suite.rb +125 -0
  29. data/benchmark/compression_bench.rb +181 -0
  30. data/benchmark/filter_bench.rb +180 -0
  31. data/benchmark/models/benchmark_result.rb +59 -0
  32. data/benchmark/models/comparison_result.rb +69 -0
  33. data/benchmark/profile_suite.rb +167 -0
  34. data/benchmark/reporter.rb +150 -0
  35. data/benchmark/run_benchmarks.rb +66 -0
  36. data/benchmark/test_data.rb +137 -0
  37. data/config/formats/rar3_spec.yml +92 -0
  38. data/config/formats/rar5_spec.yml +102 -0
  39. data/docs/.github/workflows/docs.yml +142 -0
  40. data/docs/.gitignore +21 -0
  41. data/docs/.lychee.toml +67 -0
  42. data/docs/Gemfile +13 -0
  43. data/docs/RAR_WRITE_SUPPORT.md +26 -0
  44. data/docs/README.md +101 -0
  45. data/docs/_config.yml +112 -0
  46. data/docs/assets/logo.svg +1 -0
  47. data/docs/assets/omnizip-logo.pdf +1540 -11
  48. data/docs/comparison/feature-matrix.adoc +694 -0
  49. data/docs/comparison/index.adoc +113 -0
  50. data/docs/comparison/vs-7zip.adoc +309 -0
  51. data/docs/comparison/vs-peazip.adoc +77 -0
  52. data/docs/comparison/vs-rubyzip.adoc +342 -0
  53. data/docs/comparison/vs-winrar.adoc +100 -0
  54. data/docs/compatibility.adoc +579 -0
  55. data/docs/concepts/index.adoc +129 -0
  56. data/docs/developer/architecture.adoc +256 -0
  57. data/docs/developer/contributing.adoc +158 -0
  58. data/docs/developer/index.adoc +25 -0
  59. data/docs/developer/testing.adoc +212 -0
  60. data/docs/getting-started/basic-usage.adoc +263 -0
  61. data/docs/getting-started/index.adoc +42 -0
  62. data/docs/getting-started/installation.adoc +138 -0
  63. data/docs/getting-started/quick-start.adoc +183 -0
  64. data/docs/getting-started/your-first-archive.adoc +219 -0
  65. data/docs/guides/advanced-features/encryption.adoc +277 -0
  66. data/docs/guides/advanced-features/index.adoc +59 -0
  67. data/docs/guides/advanced-features/parallel-processing.adoc +144 -0
  68. data/docs/guides/advanced-features/progress-tracking.adoc +124 -0
  69. data/docs/guides/advanced-features/streaming.adoc +142 -0
  70. data/docs/guides/archive-formats/gzip-format.adoc +105 -0
  71. data/docs/guides/archive-formats/index.adoc +160 -0
  72. data/docs/guides/archive-formats/ole-format.adoc +151 -0
  73. data/docs/guides/archive-formats/rar-format.adoc +104 -0
  74. data/docs/guides/archive-formats/rar5.adoc +521 -0
  75. data/docs/guides/archive-formats/rpm-format.adoc +108 -0
  76. data/docs/guides/archive-formats/seven-zip-format.adoc +35 -0
  77. data/docs/guides/archive-formats/tar-format.adoc +106 -0
  78. data/docs/guides/archive-formats/xz-format.adoc +118 -0
  79. data/docs/guides/archive-formats/zip-format.adoc +35 -0
  80. data/docs/guides/compression-algorithms/bzip2.adoc +114 -0
  81. data/docs/guides/compression-algorithms/deflate.adoc +313 -0
  82. data/docs/guides/compression-algorithms/index.adoc +190 -0
  83. data/docs/guides/compression-algorithms/lzma.adoc +394 -0
  84. data/docs/guides/compression-algorithms/lzma2.adoc +244 -0
  85. data/docs/guides/compression-algorithms/ppmd.adoc +313 -0
  86. data/docs/guides/compression-algorithms/zstandard.adoc +296 -0
  87. data/docs/guides/creating-archives.adoc +348 -0
  88. data/docs/guides/extracting-archives.adoc +53 -0
  89. data/docs/guides/format-conversion.adoc +64 -0
  90. data/docs/guides/index.adoc +49 -0
  91. data/docs/guides/migration-rubyzip.adoc +217 -0
  92. data/docs/guides/parity-archives.adoc +605 -0
  93. data/docs/guides/performance-tuning.adoc +88 -0
  94. data/docs/index.adoc +228 -0
  95. data/docs/lychee.toml +67 -0
  96. data/docs/reference/api/overview.adoc +187 -0
  97. data/docs/reference/cli/compress-command.adoc +114 -0
  98. data/docs/reference/cli/overview.adoc +140 -0
  99. data/docs/reference/index.adoc +26 -0
  100. data/docs/resources/faq.adoc +185 -0
  101. data/docs/resources/quick-reference.adoc +222 -0
  102. data/docs/rust-backend.md +0 -0
  103. data/docs/troubleshooting/index.adoc +207 -0
  104. data/docs/xar_format.md +216 -0
  105. data/examples/api_comparison.rb +205 -0
  106. data/examples/deflate64_example.rb +96 -0
  107. data/examples/par2_demo.rb +121 -0
  108. data/examples/quick_start_native.rb +150 -0
  109. data/examples/quick_start_rubyzip.rb +115 -0
  110. data/examples/rubyzip_compatibility_demo.rb +194 -0
  111. data/exe/omnizip +27 -0
  112. data/lib/omnizip/algorithm.rb +176 -0
  113. data/lib/omnizip/algorithm_registry.rb +65 -0
  114. data/lib/omnizip/algorithms/.keep +0 -0
  115. data/lib/omnizip/algorithms/bzip2/bwt.rb +236 -0
  116. data/lib/omnizip/algorithms/bzip2/bz2.rb +687 -0
  117. data/lib/omnizip/algorithms/bzip2/rle.rb +146 -0
  118. data/lib/omnizip/algorithms/bzip2.rb +108 -0
  119. data/lib/omnizip/algorithms/deflate/constants.rb +28 -0
  120. data/lib/omnizip/algorithms/deflate/decoder.rb +37 -0
  121. data/lib/omnizip/algorithms/deflate/encoder.rb +45 -0
  122. data/lib/omnizip/algorithms/deflate.rb +135 -0
  123. data/lib/omnizip/algorithms/deflate64/constants.rb +45 -0
  124. data/lib/omnizip/algorithms/deflate64/decoder.rb +151 -0
  125. data/lib/omnizip/algorithms/deflate64/encoder.rb +95 -0
  126. data/lib/omnizip/algorithms/deflate64/huffman_coder.rb +352 -0
  127. data/lib/omnizip/algorithms/deflate64/lz77_encoder.rb +140 -0
  128. data/lib/omnizip/algorithms/deflate64.rb +118 -0
  129. data/lib/omnizip/algorithms/lzma/bit_model.rb +120 -0
  130. data/lib/omnizip/algorithms/lzma/constants.rb +112 -0
  131. data/lib/omnizip/algorithms/lzma/decoder.rb +146 -0
  132. data/lib/omnizip/algorithms/lzma/dictionary.rb +70 -0
  133. data/lib/omnizip/algorithms/lzma/distance_coder.rb +326 -0
  134. data/lib/omnizip/algorithms/lzma/encoder.rb +139 -0
  135. data/lib/omnizip/algorithms/lzma/length_coder.rb +172 -0
  136. data/lib/omnizip/algorithms/lzma/literal_decoder.rb +204 -0
  137. data/lib/omnizip/algorithms/lzma/literal_encoder.rb +208 -0
  138. data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +368 -0
  139. data/lib/omnizip/algorithms/lzma/lzma1_encoder.rb +105 -0
  140. data/lib/omnizip/algorithms/lzma/lzma_alone_decoder.rb +194 -0
  141. data/lib/omnizip/algorithms/lzma/lzma_state.rb +128 -0
  142. data/lib/omnizip/algorithms/lzma/match.rb +32 -0
  143. data/lib/omnizip/algorithms/lzma/match_finder.rb +233 -0
  144. data/lib/omnizip/algorithms/lzma/match_finder_config.rb +142 -0
  145. data/lib/omnizip/algorithms/lzma/match_finder_factory.rb +84 -0
  146. data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +159 -0
  147. data/lib/omnizip/algorithms/lzma/probability_models.rb +72 -0
  148. data/lib/omnizip/algorithms/lzma/range_coder.rb +82 -0
  149. data/lib/omnizip/algorithms/lzma/range_decoder.rb +274 -0
  150. data/lib/omnizip/algorithms/lzma/range_encoder.rb +202 -0
  151. data/lib/omnizip/algorithms/lzma/state.rb +127 -0
  152. data/lib/omnizip/algorithms/lzma/xz_buffered_range_encoder.rb +323 -0
  153. data/lib/omnizip/algorithms/lzma/xz_encoder.rb +420 -0
  154. data/lib/omnizip/algorithms/lzma/xz_encoder_fast.rb +640 -0
  155. data/lib/omnizip/algorithms/lzma/xz_match_finder_adapter.rb +224 -0
  156. data/lib/omnizip/algorithms/lzma/xz_price_calculator.rb +167 -0
  157. data/lib/omnizip/algorithms/lzma/xz_probability_models.rb +258 -0
  158. data/lib/omnizip/algorithms/lzma/xz_range_encoder.rb +223 -0
  159. data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +314 -0
  160. data/lib/omnizip/algorithms/lzma/xz_state.rb +108 -0
  161. data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +1311 -0
  162. data/lib/omnizip/algorithms/lzma.rb +266 -0
  163. data/lib/omnizip/algorithms/lzma2/chunk_manager.rb +180 -0
  164. data/lib/omnizip/algorithms/lzma2/constants.rb +38 -0
  165. data/lib/omnizip/algorithms/lzma2/encoder.rb +145 -0
  166. data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +165 -0
  167. data/lib/omnizip/algorithms/lzma2/properties.rb +179 -0
  168. data/lib/omnizip/algorithms/lzma2/simple_lzma2_encoder.rb +122 -0
  169. data/lib/omnizip/algorithms/lzma2/xz_encoder_adapter.rb +85 -0
  170. data/lib/omnizip/algorithms/lzma2.rb +133 -0
  171. data/lib/omnizip/algorithms/ppmd7/constants.rb +74 -0
  172. data/lib/omnizip/algorithms/ppmd7/context.rb +151 -0
  173. data/lib/omnizip/algorithms/ppmd7/decoder.rb +127 -0
  174. data/lib/omnizip/algorithms/ppmd7/encoder.rb +153 -0
  175. data/lib/omnizip/algorithms/ppmd7/model.rb +245 -0
  176. data/lib/omnizip/algorithms/ppmd7/symbol_state.rb +57 -0
  177. data/lib/omnizip/algorithms/ppmd7.rb +146 -0
  178. data/lib/omnizip/algorithms/ppmd8/constants.rb +61 -0
  179. data/lib/omnizip/algorithms/ppmd8/context.rb +32 -0
  180. data/lib/omnizip/algorithms/ppmd8/decoder.rb +104 -0
  181. data/lib/omnizip/algorithms/ppmd8/encoder.rb +135 -0
  182. data/lib/omnizip/algorithms/ppmd8/model.rb +246 -0
  183. data/lib/omnizip/algorithms/ppmd8/restoration_method.rb +78 -0
  184. data/lib/omnizip/algorithms/ppmd8.rb +122 -0
  185. data/lib/omnizip/algorithms/ppmd_base.rb +142 -0
  186. data/lib/omnizip/algorithms/registration.rb +28 -0
  187. data/lib/omnizip/algorithms/zstandard/constants.rb +158 -0
  188. data/lib/omnizip/algorithms/zstandard/decoder.rb +249 -0
  189. data/lib/omnizip/algorithms/zstandard/dictionary.rb +73 -0
  190. data/lib/omnizip/algorithms/zstandard/encoder.rb +294 -0
  191. data/lib/omnizip/algorithms/zstandard/frame/block.rb +143 -0
  192. data/lib/omnizip/algorithms/zstandard/frame/header.rb +233 -0
  193. data/lib/omnizip/algorithms/zstandard/frame.rb +33 -0
  194. data/lib/omnizip/algorithms/zstandard/fse/bitstream.rb +183 -0
  195. data/lib/omnizip/algorithms/zstandard/fse/encoder.rb +534 -0
  196. data/lib/omnizip/algorithms/zstandard/fse/interleaved.rb +92 -0
  197. data/lib/omnizip/algorithms/zstandard/fse/table.rb +188 -0
  198. data/lib/omnizip/algorithms/zstandard/fse/table_description.rb +203 -0
  199. data/lib/omnizip/algorithms/zstandard/fse.rb +53 -0
  200. data/lib/omnizip/algorithms/zstandard/huffman.rb +276 -0
  201. data/lib/omnizip/algorithms/zstandard/huffman_encoder.rb +327 -0
  202. data/lib/omnizip/algorithms/zstandard/ldm.rb +121 -0
  203. data/lib/omnizip/algorithms/zstandard/literals.rb +241 -0
  204. data/lib/omnizip/algorithms/zstandard/literals_encoder.rb +96 -0
  205. data/lib/omnizip/algorithms/zstandard/match_finder.rb +483 -0
  206. data/lib/omnizip/algorithms/zstandard/sequences.rb +350 -0
  207. data/lib/omnizip/algorithms/zstandard/sequences_encoder.rb +333 -0
  208. data/lib/omnizip/algorithms/zstandard/xxhash.rb +159 -0
  209. data/lib/omnizip/algorithms/zstandard.rb +210 -0
  210. data/lib/omnizip/algorithms.rb +35 -0
  211. data/lib/omnizip/archive/builder.rb +48 -0
  212. data/lib/omnizip/archive/reader_session.rb +67 -0
  213. data/lib/omnizip/archive.rb +86 -0
  214. data/lib/omnizip/archive_handler.rb +82 -0
  215. data/lib/omnizip/archive_handlers/cpio_handler.rb +47 -0
  216. data/lib/omnizip/archive_handlers/iso_handler.rb +54 -0
  217. data/lib/omnizip/archive_handlers/ole_handler.rb +37 -0
  218. data/lib/omnizip/archive_handlers/rar_handler.rb +96 -0
  219. data/lib/omnizip/archive_handlers/rpm_handler.rb +45 -0
  220. data/lib/omnizip/archive_handlers/seven_zip_handler.rb +89 -0
  221. data/lib/omnizip/archive_handlers/tar_handler.rb +75 -0
  222. data/lib/omnizip/archive_handlers/xar_handler.rb +49 -0
  223. data/lib/omnizip/archive_handlers/zip_handler.rb +115 -0
  224. data/lib/omnizip/archive_handlers.rb +18 -0
  225. data/lib/omnizip/backends.rb +160 -0
  226. data/lib/omnizip/buffer/memory_archive.rb +246 -0
  227. data/lib/omnizip/buffer/memory_extractor.rb +234 -0
  228. data/lib/omnizip/buffer/seven_zip_bridge.rb +171 -0
  229. data/lib/omnizip/buffer.rb +187 -0
  230. data/lib/omnizip/checksum_registry.rb +41 -0
  231. data/lib/omnizip/checksums/crc32.rb +101 -0
  232. data/lib/omnizip/checksums/crc64.rb +102 -0
  233. data/lib/omnizip/checksums/crc_base.rb +158 -0
  234. data/lib/omnizip/checksums/registration.rb +16 -0
  235. data/lib/omnizip/checksums/verifier.rb +130 -0
  236. data/lib/omnizip/checksums.rb +11 -0
  237. data/lib/omnizip/chunked/memory_manager.rb +194 -0
  238. data/lib/omnizip/chunked/reader.rb +78 -0
  239. data/lib/omnizip/chunked/writer.rb +123 -0
  240. data/lib/omnizip/chunked.rb +123 -0
  241. data/lib/omnizip/cli/output_formatter.rb +112 -0
  242. data/lib/omnizip/cli.rb +604 -0
  243. data/lib/omnizip/commands/.keep +0 -0
  244. data/lib/omnizip/commands/archive_create_command.rb +245 -0
  245. data/lib/omnizip/commands/archive_extract_command.rb +231 -0
  246. data/lib/omnizip/commands/archive_list_command.rb +204 -0
  247. data/lib/omnizip/commands/archive_repair_command.rb +129 -0
  248. data/lib/omnizip/commands/archive_verify_command.rb +115 -0
  249. data/lib/omnizip/commands/compress_command.rb +115 -0
  250. data/lib/omnizip/commands/decompress_command.rb +149 -0
  251. data/lib/omnizip/commands/list_command.rb +51 -0
  252. data/lib/omnizip/commands/metadata_command.rb +215 -0
  253. data/lib/omnizip/commands/parity_create_command.rb +114 -0
  254. data/lib/omnizip/commands/parity_repair_command.rb +120 -0
  255. data/lib/omnizip/commands/parity_verify_command.rb +122 -0
  256. data/lib/omnizip/commands/profile_list_command.rb +54 -0
  257. data/lib/omnizip/commands/profile_show_command.rb +44 -0
  258. data/lib/omnizip/commands.rb +21 -0
  259. data/lib/omnizip/convenience.rb +389 -0
  260. data/lib/omnizip/converter/conversion_registry.rb +50 -0
  261. data/lib/omnizip/converter/conversion_strategy.rb +108 -0
  262. data/lib/omnizip/converter/extract_repack_strategy.rb +51 -0
  263. data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +43 -0
  264. data/lib/omnizip/converter/zip_to_seven_zip_strategy.rb +54 -0
  265. data/lib/omnizip/converter.rb +124 -0
  266. data/lib/omnizip/crypto/aes256/cipher.rb +104 -0
  267. data/lib/omnizip/crypto/aes256/constants.rb +28 -0
  268. data/lib/omnizip/crypto/aes256/key_derivation.rb +100 -0
  269. data/lib/omnizip/crypto/aes256.rb +102 -0
  270. data/lib/omnizip/crypto.rb +11 -0
  271. data/lib/omnizip/entry.rb +44 -0
  272. data/lib/omnizip/error.rb +110 -0
  273. data/lib/omnizip/eta/exponential_smoothing_estimator.rb +95 -0
  274. data/lib/omnizip/eta/moving_average_estimator.rb +96 -0
  275. data/lib/omnizip/eta/rate_calculator.rb +104 -0
  276. data/lib/omnizip/eta/sample_history.rb +143 -0
  277. data/lib/omnizip/eta/time_estimator.rb +106 -0
  278. data/lib/omnizip/eta.rb +68 -0
  279. data/lib/omnizip/extraction/filter_chain.rb +179 -0
  280. data/lib/omnizip/extraction/glob_pattern.rb +140 -0
  281. data/lib/omnizip/extraction/pattern_matcher.rb +66 -0
  282. data/lib/omnizip/extraction/predicate_pattern.rb +52 -0
  283. data/lib/omnizip/extraction/regex_pattern.rb +50 -0
  284. data/lib/omnizip/extraction/selective_extractor.rb +247 -0
  285. data/lib/omnizip/extraction.rb +115 -0
  286. data/lib/omnizip/file_type/mime_classifier.rb +144 -0
  287. data/lib/omnizip/file_type.rb +117 -0
  288. data/lib/omnizip/filter.rb +141 -0
  289. data/lib/omnizip/filter_pipeline.rb +108 -0
  290. data/lib/omnizip/filter_registry.rb +113 -0
  291. data/lib/omnizip/filters/bcj.rb +277 -0
  292. data/lib/omnizip/filters/bcj2/constants.rb +53 -0
  293. data/lib/omnizip/filters/bcj2/decoder.rb +263 -0
  294. data/lib/omnizip/filters/bcj2/encoder.rb +58 -0
  295. data/lib/omnizip/filters/bcj2/stream_data.rb +91 -0
  296. data/lib/omnizip/filters/bcj2.rb +93 -0
  297. data/lib/omnizip/filters/bcj_arm.rb +174 -0
  298. data/lib/omnizip/filters/bcj_arm64.rb +242 -0
  299. data/lib/omnizip/filters/bcj_ia64.rb +194 -0
  300. data/lib/omnizip/filters/bcj_ppc.rb +188 -0
  301. data/lib/omnizip/filters/bcj_sparc.rb +174 -0
  302. data/lib/omnizip/filters/bcj_x86.rb +191 -0
  303. data/lib/omnizip/filters/delta.rb +195 -0
  304. data/lib/omnizip/filters/xz_delta.rb +258 -0
  305. data/lib/omnizip/filters.rb +20 -0
  306. data/lib/omnizip/format_detector.rb +166 -0
  307. data/lib/omnizip/formats/.keep +0 -0
  308. data/lib/omnizip/formats/bzip2_file.rb +159 -0
  309. data/lib/omnizip/formats/cpio/bounded_io.rb +66 -0
  310. data/lib/omnizip/formats/cpio/constants.rb +55 -0
  311. data/lib/omnizip/formats/cpio/entry.rb +389 -0
  312. data/lib/omnizip/formats/cpio/reader.rb +194 -0
  313. data/lib/omnizip/formats/cpio/writer.rb +232 -0
  314. data/lib/omnizip/formats/cpio.rb +132 -0
  315. data/lib/omnizip/formats/format_spec_loader.rb +228 -0
  316. data/lib/omnizip/formats/gzip.rb +236 -0
  317. data/lib/omnizip/formats/iso/directory_builder.rb +321 -0
  318. data/lib/omnizip/formats/iso/directory_record.rb +162 -0
  319. data/lib/omnizip/formats/iso/joliet.rb +204 -0
  320. data/lib/omnizip/formats/iso/path_table.rb +125 -0
  321. data/lib/omnizip/formats/iso/reader.rb +194 -0
  322. data/lib/omnizip/formats/iso/rock_ridge.rb +349 -0
  323. data/lib/omnizip/formats/iso/volume_builder.rb +319 -0
  324. data/lib/omnizip/formats/iso/volume_descriptor.rb +168 -0
  325. data/lib/omnizip/formats/iso/writer.rb +543 -0
  326. data/lib/omnizip/formats/iso.rb +133 -0
  327. data/lib/omnizip/formats/lzip.rb +161 -0
  328. data/lib/omnizip/formats/lzma_alone.rb +149 -0
  329. data/lib/omnizip/formats/msi/cab_extractor.rb +181 -0
  330. data/lib/omnizip/formats/msi/constants.rb +157 -0
  331. data/lib/omnizip/formats/msi/directory_resolver.rb +188 -0
  332. data/lib/omnizip/formats/msi/entry.rb +134 -0
  333. data/lib/omnizip/formats/msi/reader.rb +301 -0
  334. data/lib/omnizip/formats/msi/string_pool.rb +246 -0
  335. data/lib/omnizip/formats/msi/table_parser.rb +244 -0
  336. data/lib/omnizip/formats/msi.rb +73 -0
  337. data/lib/omnizip/formats/ole/allocation_table.rb +263 -0
  338. data/lib/omnizip/formats/ole/constants.rb +61 -0
  339. data/lib/omnizip/formats/ole/dirent.rb +439 -0
  340. data/lib/omnizip/formats/ole/header.rb +199 -0
  341. data/lib/omnizip/formats/ole/ranges_io.rb +347 -0
  342. data/lib/omnizip/formats/ole/storage.rb +482 -0
  343. data/lib/omnizip/formats/ole/types/variant.rb +338 -0
  344. data/lib/omnizip/formats/ole.rb +143 -0
  345. data/lib/omnizip/formats/rar/archive_repairer.rb +240 -0
  346. data/lib/omnizip/formats/rar/archive_verifier.rb +192 -0
  347. data/lib/omnizip/formats/rar/block_parser.rb +282 -0
  348. data/lib/omnizip/formats/rar/compression/bit_stream.rb +180 -0
  349. data/lib/omnizip/formats/rar/compression/dispatcher.rb +210 -0
  350. data/lib/omnizip/formats/rar/compression/lz77_huffman/decoder.rb +212 -0
  351. data/lib/omnizip/formats/rar/compression/lz77_huffman/encoder.rb +154 -0
  352. data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_builder.rb +217 -0
  353. data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_coder.rb +189 -0
  354. data/lib/omnizip/formats/rar/compression/lz77_huffman/match_finder.rb +135 -0
  355. data/lib/omnizip/formats/rar/compression/lz77_huffman/sliding_window.rb +165 -0
  356. data/lib/omnizip/formats/rar/compression/lz77_huffman.rb +25 -0
  357. data/lib/omnizip/formats/rar/compression/ppmd/context.rb +103 -0
  358. data/lib/omnizip/formats/rar/compression/ppmd/decoder.rb +238 -0
  359. data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +250 -0
  360. data/lib/omnizip/formats/rar/compression/ppmd.rb +16 -0
  361. data/lib/omnizip/formats/rar/compression.rb +17 -0
  362. data/lib/omnizip/formats/rar/compression_method_registry.rb +106 -0
  363. data/lib/omnizip/formats/rar/constants.rb +88 -0
  364. data/lib/omnizip/formats/rar/decompressor.rb +316 -0
  365. data/lib/omnizip/formats/rar/external_writer.rb +311 -0
  366. data/lib/omnizip/formats/rar/header.rb +209 -0
  367. data/lib/omnizip/formats/rar/license_validator.rb +111 -0
  368. data/lib/omnizip/formats/rar/models/rar_archive.rb +77 -0
  369. data/lib/omnizip/formats/rar/models/rar_entry.rb +65 -0
  370. data/lib/omnizip/formats/rar/models/rar_volume.rb +56 -0
  371. data/lib/omnizip/formats/rar/models.rb +14 -0
  372. data/lib/omnizip/formats/rar/parity_handler.rb +299 -0
  373. data/lib/omnizip/formats/rar/rar5/compression/lzma.rb +200 -0
  374. data/lib/omnizip/formats/rar/rar5/compression/lzss.rb +584 -0
  375. data/lib/omnizip/formats/rar/rar5/compression/store.rb +60 -0
  376. data/lib/omnizip/formats/rar/rar5/crc32.rb +39 -0
  377. data/lib/omnizip/formats/rar/rar5/encryption/aes256_cbc.rb +96 -0
  378. data/lib/omnizip/formats/rar/rar5/encryption/encryption_header.rb +114 -0
  379. data/lib/omnizip/formats/rar/rar5/encryption/encryption_manager.rb +162 -0
  380. data/lib/omnizip/formats/rar/rar5/encryption/key_derivation.rb +97 -0
  381. data/lib/omnizip/formats/rar/rar5/header.rb +208 -0
  382. data/lib/omnizip/formats/rar/rar5/models/encryption_options.rb +74 -0
  383. data/lib/omnizip/formats/rar/rar5/models/recovery_options.rb +63 -0
  384. data/lib/omnizip/formats/rar/rar5/models/solid_options.rb +63 -0
  385. data/lib/omnizip/formats/rar/rar5/models/volume_options.rb +74 -0
  386. data/lib/omnizip/formats/rar/rar5/multi_volume/ARCHITECTURE.md +290 -0
  387. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_manager.rb +260 -0
  388. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_splitter.rb +155 -0
  389. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_writer.rb +176 -0
  390. data/lib/omnizip/formats/rar/rar5/solid/solid_encoder.rb +108 -0
  391. data/lib/omnizip/formats/rar/rar5/solid/solid_manager.rb +139 -0
  392. data/lib/omnizip/formats/rar/rar5/solid/solid_stream.rb +121 -0
  393. data/lib/omnizip/formats/rar/rar5/vint.rb +53 -0
  394. data/lib/omnizip/formats/rar/rar5/writer.rb +540 -0
  395. data/lib/omnizip/formats/rar/rar_format_base.rb +239 -0
  396. data/lib/omnizip/formats/rar/reader.rb +377 -0
  397. data/lib/omnizip/formats/rar/recovery_record.rb +245 -0
  398. data/lib/omnizip/formats/rar/volume_manager.rb +166 -0
  399. data/lib/omnizip/formats/rar/writer.rb +437 -0
  400. data/lib/omnizip/formats/rar.rb +193 -0
  401. data/lib/omnizip/formats/rar3/compressor.rb +71 -0
  402. data/lib/omnizip/formats/rar3/decompressor.rb +64 -0
  403. data/lib/omnizip/formats/rar3/reader.rb +107 -0
  404. data/lib/omnizip/formats/rar3/writer.rb +220 -0
  405. data/lib/omnizip/formats/rar3.rb +15 -0
  406. data/lib/omnizip/formats/rar5/compressor.rb +71 -0
  407. data/lib/omnizip/formats/rar5/decompressor.rb +64 -0
  408. data/lib/omnizip/formats/rar5/reader.rb +130 -0
  409. data/lib/omnizip/formats/rar5/writer.rb +65 -0
  410. data/lib/omnizip/formats/rar5.rb +15 -0
  411. data/lib/omnizip/formats/rpm/constants.rb +58 -0
  412. data/lib/omnizip/formats/rpm/entry.rb +106 -0
  413. data/lib/omnizip/formats/rpm/header.rb +106 -0
  414. data/lib/omnizip/formats/rpm/lead.rb +132 -0
  415. data/lib/omnizip/formats/rpm/tag.rb +228 -0
  416. data/lib/omnizip/formats/rpm/writer.rb +652 -0
  417. data/lib/omnizip/formats/rpm.rb +458 -0
  418. data/lib/omnizip/formats/seven_zip/bcj2_stream_decompressor.rb +274 -0
  419. data/lib/omnizip/formats/seven_zip/coder_chain.rb +169 -0
  420. data/lib/omnizip/formats/seven_zip/constants.rb +126 -0
  421. data/lib/omnizip/formats/seven_zip/encoded_header.rb +110 -0
  422. data/lib/omnizip/formats/seven_zip/encrypted_header.rb +139 -0
  423. data/lib/omnizip/formats/seven_zip/file_collector.rb +143 -0
  424. data/lib/omnizip/formats/seven_zip/header.rb +102 -0
  425. data/lib/omnizip/formats/seven_zip/header_encryptor.rb +132 -0
  426. data/lib/omnizip/formats/seven_zip/header_writer.rb +425 -0
  427. data/lib/omnizip/formats/seven_zip/models/coder_info.rb +30 -0
  428. data/lib/omnizip/formats/seven_zip/models/file_entry.rb +65 -0
  429. data/lib/omnizip/formats/seven_zip/models/folder.rb +67 -0
  430. data/lib/omnizip/formats/seven_zip/models/stream_info.rb +42 -0
  431. data/lib/omnizip/formats/seven_zip/models.rb +20 -0
  432. data/lib/omnizip/formats/seven_zip/parser.rb +671 -0
  433. data/lib/omnizip/formats/seven_zip/reader.rb +626 -0
  434. data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +611 -0
  435. data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +308 -0
  436. data/lib/omnizip/formats/seven_zip/stream_compressor.rb +151 -0
  437. data/lib/omnizip/formats/seven_zip/stream_decompressor.rb +166 -0
  438. data/lib/omnizip/formats/seven_zip/writer.rb +801 -0
  439. data/lib/omnizip/formats/seven_zip.rb +179 -0
  440. data/lib/omnizip/formats/tar/constants.rb +73 -0
  441. data/lib/omnizip/formats/tar/entry.rb +98 -0
  442. data/lib/omnizip/formats/tar/header.rb +206 -0
  443. data/lib/omnizip/formats/tar/reader.rb +119 -0
  444. data/lib/omnizip/formats/tar/writer.rb +212 -0
  445. data/lib/omnizip/formats/tar.rb +74 -0
  446. data/lib/omnizip/formats/xar/constants.rb +119 -0
  447. data/lib/omnizip/formats/xar/entry.rb +268 -0
  448. data/lib/omnizip/formats/xar/header.rb +223 -0
  449. data/lib/omnizip/formats/xar/reader.rb +400 -0
  450. data/lib/omnizip/formats/xar/toc.rb +570 -0
  451. data/lib/omnizip/formats/xar/writer.rb +461 -0
  452. data/lib/omnizip/formats/xar.rb +145 -0
  453. data/lib/omnizip/formats/xz/reader.rb +121 -0
  454. data/lib/omnizip/formats/xz/writer.rb +416 -0
  455. data/lib/omnizip/formats/xz.rb +210 -0
  456. data/lib/omnizip/formats/xz_const.rb +49 -0
  457. data/lib/omnizip/formats/xz_impl/block_decoder.rb +761 -0
  458. data/lib/omnizip/formats/xz_impl/block_encoder.rb +304 -0
  459. data/lib/omnizip/formats/xz_impl/block_header.rb +210 -0
  460. data/lib/omnizip/formats/xz_impl/block_header_parser.rb +182 -0
  461. data/lib/omnizip/formats/xz_impl/index_decoder.rb +174 -0
  462. data/lib/omnizip/formats/xz_impl/index_encoder.rb +120 -0
  463. data/lib/omnizip/formats/xz_impl/stream_decoder.rb +470 -0
  464. data/lib/omnizip/formats/xz_impl/stream_encoder.rb +95 -0
  465. data/lib/omnizip/formats/xz_impl/stream_footer.rb +79 -0
  466. data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +115 -0
  467. data/lib/omnizip/formats/xz_impl/stream_header.rb +53 -0
  468. data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +106 -0
  469. data/lib/omnizip/formats/xz_impl/vli.rb +128 -0
  470. data/lib/omnizip/formats/xz_impl.rb +52 -0
  471. data/lib/omnizip/formats/zip/central_directory_header.rb +223 -0
  472. data/lib/omnizip/formats/zip/constants.rb +69 -0
  473. data/lib/omnizip/formats/zip/end_of_central_directory.rb +132 -0
  474. data/lib/omnizip/formats/zip/local_file_header.rb +136 -0
  475. data/lib/omnizip/formats/zip/reader.rb +376 -0
  476. data/lib/omnizip/formats/zip/unix_extra_field.rb +153 -0
  477. data/lib/omnizip/formats/zip/writer.rb +489 -0
  478. data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +102 -0
  479. data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +64 -0
  480. data/lib/omnizip/formats/zip/zip64_extra_field.rb +112 -0
  481. data/lib/omnizip/formats/zip.rb +50 -0
  482. data/lib/omnizip/formats.rb +29 -0
  483. data/lib/omnizip/implementations/base/lzma2_decoder_base.rb +75 -0
  484. data/lib/omnizip/implementations/base/lzma2_encoder_base.rb +128 -0
  485. data/lib/omnizip/implementations/base/lzma_decoder_base.rb +83 -0
  486. data/lib/omnizip/implementations/base/lzma_encoder_base.rb +108 -0
  487. data/lib/omnizip/implementations/base/state_machine_base.rb +182 -0
  488. data/lib/omnizip/implementations/base.rb +19 -0
  489. data/lib/omnizip/implementations/rust/archive.rb +93 -0
  490. data/lib/omnizip/implementations/rust/library.rb +221 -0
  491. data/lib/omnizip/implementations/rust.rb +26 -0
  492. data/lib/omnizip/implementations/seven_zip/lzma/decoder.rb +428 -0
  493. data/lib/omnizip/implementations/seven_zip/lzma/encoder.rb +456 -0
  494. data/lib/omnizip/implementations/seven_zip/lzma/match_finder.rb +285 -0
  495. data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +197 -0
  496. data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +194 -0
  497. data/lib/omnizip/implementations/seven_zip/lzma/state_machine.rb +139 -0
  498. data/lib/omnizip/implementations/seven_zip/lzma.rb +21 -0
  499. data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +56 -0
  500. data/lib/omnizip/implementations/seven_zip/lzma2.rb +12 -0
  501. data/lib/omnizip/implementations/seven_zip.rb +11 -0
  502. data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +547 -0
  503. data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +748 -0
  504. data/lib/omnizip/implementations/xz_utils/lzma2.rb +13 -0
  505. data/lib/omnizip/implementations/xz_utils.rb +10 -0
  506. data/lib/omnizip/implementations.rb +25 -0
  507. data/lib/omnizip/io/buffered_input.rb +147 -0
  508. data/lib/omnizip/io/buffered_output.rb +106 -0
  509. data/lib/omnizip/io/source.rb +143 -0
  510. data/lib/omnizip/io/stream_manager.rb +117 -0
  511. data/lib/omnizip/io.rb +15 -0
  512. data/lib/omnizip/link_handler/hard_link.rb +79 -0
  513. data/lib/omnizip/link_handler/symbolic_link.rb +74 -0
  514. data/lib/omnizip/link_handler.rb +129 -0
  515. data/lib/omnizip/metadata/archive_metadata.rb +114 -0
  516. data/lib/omnizip/metadata/entry_metadata.rb +153 -0
  517. data/lib/omnizip/metadata/metadata_editor.rb +169 -0
  518. data/lib/omnizip/metadata/metadata_registry.rb +71 -0
  519. data/lib/omnizip/metadata/metadata_validator.rb +101 -0
  520. data/lib/omnizip/metadata.rb +58 -0
  521. data/lib/omnizip/models/.keep +0 -0
  522. data/lib/omnizip/models/algorithm_metadata.rb +41 -0
  523. data/lib/omnizip/models/attribute_apply.rb +40 -0
  524. data/lib/omnizip/models/compression_options.rb +53 -0
  525. data/lib/omnizip/models/conversion_options.rb +79 -0
  526. data/lib/omnizip/models/conversion_result.rb +141 -0
  527. data/lib/omnizip/models/eta_result.rb +45 -0
  528. data/lib/omnizip/models/extraction_rule.rb +115 -0
  529. data/lib/omnizip/models/filter_chain.rb +142 -0
  530. data/lib/omnizip/models/filter_config.rb +148 -0
  531. data/lib/omnizip/models/match_result.rb +141 -0
  532. data/lib/omnizip/models/optimization_suggestion.rb +95 -0
  533. data/lib/omnizip/models/parallel_options.rb +116 -0
  534. data/lib/omnizip/models/performance_result.rb +85 -0
  535. data/lib/omnizip/models/profile_report.rb +116 -0
  536. data/lib/omnizip/models/progress_options.rb +39 -0
  537. data/lib/omnizip/models/split_options.rb +116 -0
  538. data/lib/omnizip/optimization_registry.rb +45 -0
  539. data/lib/omnizip/parallel/engine.rb +52 -0
  540. data/lib/omnizip/parallel/job_queue.rb +209 -0
  541. data/lib/omnizip/parallel/job_scheduler.rb +225 -0
  542. data/lib/omnizip/parallel/parallel_compressor.rb +326 -0
  543. data/lib/omnizip/parallel/parallel_extractor.rb +238 -0
  544. data/lib/omnizip/parallel/thread_pool.rb +60 -0
  545. data/lib/omnizip/parallel/worker_pool.rb +227 -0
  546. data/lib/omnizip/parallel.rb +156 -0
  547. data/lib/omnizip/parity/chunked_block_processor.rb +194 -0
  548. data/lib/omnizip/parity/galois16.rb +145 -0
  549. data/lib/omnizip/parity/models/creator_packet.rb +71 -0
  550. data/lib/omnizip/parity/models/file_description_packet.rb +131 -0
  551. data/lib/omnizip/parity/models/ifsc_packet.rb +111 -0
  552. data/lib/omnizip/parity/models/main_packet.rb +126 -0
  553. data/lib/omnizip/parity/models/packet.rb +156 -0
  554. data/lib/omnizip/parity/models/packet_registry.rb +102 -0
  555. data/lib/omnizip/parity/models/recovery_slice_packet.rb +76 -0
  556. data/lib/omnizip/parity/models.rb +27 -0
  557. data/lib/omnizip/parity/par2_creator.rb +496 -0
  558. data/lib/omnizip/parity/par2_repairer.rb +409 -0
  559. data/lib/omnizip/parity/par2_verifier.rb +373 -0
  560. data/lib/omnizip/parity/par2cmdline_algorithm.rb +108 -0
  561. data/lib/omnizip/parity/par2cmdline_coefficients.rb +78 -0
  562. data/lib/omnizip/parity/reed_solomon_decoder.rb +263 -0
  563. data/lib/omnizip/parity/reed_solomon_encoder.rb +108 -0
  564. data/lib/omnizip/parity/reed_solomon_matrix.rb +339 -0
  565. data/lib/omnizip/parity.rb +207 -0
  566. data/lib/omnizip/password/encryption_registry.rb +26 -0
  567. data/lib/omnizip/password/encryption_strategy.rb +96 -0
  568. data/lib/omnizip/password/password_validator.rb +129 -0
  569. data/lib/omnizip/password/winzip_aes_strategy.rb +190 -0
  570. data/lib/omnizip/password/zip_crypto_strategy.rb +135 -0
  571. data/lib/omnizip/password.rb +88 -0
  572. data/lib/omnizip/pipe/stream_compressor.rb +124 -0
  573. data/lib/omnizip/pipe/stream_decompressor.rb +173 -0
  574. data/lib/omnizip/pipe.rb +120 -0
  575. data/lib/omnizip/platform/ntfs_streams.rb +200 -0
  576. data/lib/omnizip/platform.rb +189 -0
  577. data/lib/omnizip/profile/archive_profile.rb +36 -0
  578. data/lib/omnizip/profile/balanced_profile.rb +31 -0
  579. data/lib/omnizip/profile/binary_profile.rb +33 -0
  580. data/lib/omnizip/profile/compression_profile.rb +157 -0
  581. data/lib/omnizip/profile/custom_profile.rb +155 -0
  582. data/lib/omnizip/profile/fast_profile.rb +31 -0
  583. data/lib/omnizip/profile/maximum_profile.rb +31 -0
  584. data/lib/omnizip/profile/profile_detector.rb +108 -0
  585. data/lib/omnizip/profile/profile_registry.rb +160 -0
  586. data/lib/omnizip/profile/text_profile.rb +33 -0
  587. data/lib/omnizip/profile.rb +261 -0
  588. data/lib/omnizip/profiler/memory_profiler.rb +66 -0
  589. data/lib/omnizip/profiler/method_profiler.rb +49 -0
  590. data/lib/omnizip/profiler/report_generator.rb +158 -0
  591. data/lib/omnizip/profiler.rb +192 -0
  592. data/lib/omnizip/progress/callback_reporter.rb +34 -0
  593. data/lib/omnizip/progress/console_reporter.rb +59 -0
  594. data/lib/omnizip/progress/log_reporter.rb +89 -0
  595. data/lib/omnizip/progress/operation_progress.rb +118 -0
  596. data/lib/omnizip/progress/progress_bar.rb +156 -0
  597. data/lib/omnizip/progress/progress_reporter.rb +40 -0
  598. data/lib/omnizip/progress/progress_tracker.rb +215 -0
  599. data/lib/omnizip/progress/silent_reporter.rb +22 -0
  600. data/lib/omnizip/progress.rb +131 -0
  601. data/lib/omnizip/registry.rb +111 -0
  602. data/lib/omnizip/rubyzip_compat.rb +61 -0
  603. data/lib/omnizip/temp/safe_extract.rb +124 -0
  604. data/lib/omnizip/temp/temp_file.rb +123 -0
  605. data/lib/omnizip/temp/temp_file_pool.rb +109 -0
  606. data/lib/omnizip/temp.rb +184 -0
  607. data/lib/omnizip/version.rb +5 -0
  608. data/lib/omnizip/zip/entry.rb +160 -0
  609. data/lib/omnizip/zip/file.rb +485 -0
  610. data/lib/omnizip/zip/input_stream.rb +270 -0
  611. data/lib/omnizip/zip/output_stream.rb +320 -0
  612. data/lib/omnizip/zip.rb +23 -0
  613. data/lib/omnizip.rb +157 -0
  614. data/lib/tasks/msi_test_files.rake +66 -0
  615. data/lib/tasks/platform_gems.rake +147 -0
  616. data/lib/tasks/rust_backend.rake +48 -0
  617. data/readme-docs/advanced-features.adoc +514 -0
  618. data/readme-docs/api-usage.adoc +445 -0
  619. data/readme-docs/architecture.adoc +450 -0
  620. data/readme-docs/archive-formats.adoc +479 -0
  621. data/readme-docs/cli-usage.adoc +222 -0
  622. data/readme-docs/compression-algorithms.adoc +446 -0
  623. data/readme-docs/compression-profiles.adoc +247 -0
  624. data/readme-docs/encryption-checksums.adoc +328 -0
  625. data/readme-docs/format-converter.adoc +316 -0
  626. data/readme-docs/installation.adoc +228 -0
  627. data/readme-docs/par2-archives.adoc +608 -0
  628. data/readme-docs/performance-profiler.adoc +389 -0
  629. data/readme-docs/preprocessing-filters.adoc +280 -0
  630. data/vendor/libomnizip_ffi.so +0 -0
  631. data/xz-file-format-1.2.1.txt +1174 -0
  632. metadata +768 -0
@@ -0,0 +1,761 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2025 Ribose Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a
6
+ # copy of this software and associated documentation files (the "Software"),
7
+ # to deal in the Software without restriction, including without limitation
8
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
9
+ # and/or sell copies of the Software, and to permit persons to whom the
10
+ # Software is furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
+ # DEALINGS IN THE SOFTWARE.
22
+
23
+ require "stringio"
24
+
25
+ module Omnizip
26
+ module Formats
27
+ module XzImpl
28
+ # XZ Block decoder
29
+ #
30
+ # Decodes a single XZ block which consists of:
31
+ # - Block Header
32
+ # - Compressed Data
33
+ # - Block Padding (to 4-byte boundary)
34
+ # - Check (CRC32/CRC64/SHA256)
35
+ #
36
+ # Reference: /tmp/xz-source/src/liblzma/common/block_decoder.c
37
+ class BlockDecoder
38
+ # Filter IDs
39
+ FILTER_LZMA2 = 0x21
40
+
41
+ # XZ spec: max valid prop is 40 (gives ~2GB dict)
42
+ # Cap at 40 to prevent memory exhaustion from malformed files
43
+ MAX_DICT_PROP = 40
44
+ MAX_DICT_SIZE = 64 * 1024 * 1024 # 64MB practical limit
45
+
46
+ # Accessor for new input after block (used by stream decoder for multi-block files)
47
+ attr_reader :new_input_after_block
48
+ # Accessor for block size information (used for index validation)
49
+ attr_reader :unpadded_size, :uncompressed_size
50
+
51
+ # Wrapper for counting bytes read from a stream
52
+ class CountingInputStream
53
+ attr_reader :bytes_read
54
+
55
+ def initialize(stream)
56
+ @stream = stream
57
+ @bytes_read = 0
58
+ end
59
+
60
+ def read(length = nil, outbuf = nil)
61
+ result = @stream.read(length, outbuf)
62
+ if result
63
+ bytes_read = result.bytesize
64
+ @bytes_read += bytes_read
65
+ end
66
+ result
67
+ end
68
+
69
+ def getbyte
70
+ byte = @stream.getbyte
71
+ @bytes_read += 1 if byte
72
+ byte
73
+ end
74
+
75
+ def eos?
76
+ @stream.eos?
77
+ end
78
+
79
+ def set_encoding(enc)
80
+ # allowed: caller-supplied IO; String streams lack set_encoding
81
+ @stream.set_encoding(enc) if @stream.respond_to?(:set_encoding)
82
+ end
83
+ end
84
+
85
+ # Initialize block decoder
86
+ #
87
+ # @param input [IO] Input stream positioned at block header
88
+ # @param check_type [Integer] Check type (0=None, 1=CRC32, 4=CRC64, 10=SHA256)
89
+ def initialize(input, check_type)
90
+ @input = input
91
+ @check_type = check_type
92
+ @new_input_after_block = nil # Track new input for stream decoder
93
+ @data_already_decompressed = false # Track if LZMA2 already decoded the data
94
+ @unpadded_size = nil # Track unpadded block size (for index validation)
95
+ @uncompressed_size = nil # Track uncompressed size (for index validation)
96
+ end
97
+
98
+ # Decode block
99
+ #
100
+ # @return [Array<String, Hash>] Decompressed data and block info:
101
+ # - data: String (decompressed data)
102
+ # - info: Hash with header info
103
+ # @raise [RuntimeError] If block is invalid or checksum mismatch
104
+ def decode
105
+ # Parse block header
106
+ header = BlockHeaderParser.parse(@input)
107
+
108
+ # Read compressed data
109
+ compressed_size = header[:compressed_size]
110
+ check_size = Checksums::Verifier.check_size(@check_type)
111
+
112
+ if ENV["XZ_BLOCK_DEBUG"]
113
+ warn "DEBUG: decode - compressed_size=#{compressed_size.inspect}, check_type=#{@check_type}"
114
+ # allowed: debug output under XZ_BLOCK_DEBUG
115
+ warn "DEBUG: @input.class=#{@input.class}, @input.respond_to?(:pos)=#{@input.respond_to?(:pos)}"
116
+ # allowed: debug output under XZ_BLOCK_DEBUG
117
+ pos = @input.respond_to?(:pos) ? @input.pos : "N/A"
118
+ warn "DEBUG: @input.pos=#{pos}"
119
+ end
120
+
121
+ if compressed_size.nil?
122
+ # Compressed size is not present in header - need to determine block boundary
123
+ # Read all remaining data
124
+ all_remaining = @input.read
125
+
126
+ # Decode LZMA2 and track how many bytes it consumes
127
+ uncompressed_data, consumed_bytes = decode_lzma2_with_consumption_tracking(
128
+ all_remaining: all_remaining,
129
+ filters: header[:filters],
130
+ )
131
+
132
+ # Mark that data is already decompressed (LZMA2 only for now)
133
+ @data_already_decompressed = true
134
+
135
+ # Calculate padding and check positions
136
+ # Block structure: [compressed data] [padding to 4-byte boundary] [check]
137
+ padding_needed = (4 - (consumed_bytes % 4)) % 4
138
+ check_start_pos = consumed_bytes + padding_needed
139
+
140
+ # XZ Utils: Validate padding bytes are all zeros
141
+ # Reference: /Users/mulgogi/src/external/xz/src/liblzma/common/block_decoder.c:131-139
142
+ if padding_needed.positive?
143
+ padding_bytes = all_remaining.byteslice(consumed_bytes,
144
+ padding_needed)
145
+ if padding_bytes.nil? || padding_bytes.bytesize < padding_needed
146
+ raise Omnizip::FormatError,
147
+ "Unexpected end of stream in block padding"
148
+ end
149
+ # Verify padding is all zeros
150
+ unless padding_bytes.bytes.all?(0)
151
+ raise Omnizip::FormatError,
152
+ "Block padding contains non-zero bytes"
153
+ end
154
+ end
155
+
156
+ if ENV["XZ_BLOCK_DEBUG"]
157
+ warn "DEBUG: consumed_bytes=#{consumed_bytes}, padding_needed=#{padding_needed}, check_start_pos=#{check_start_pos}"
158
+ warn "DEBUG: all_remaining.bytesize=#{all_remaining.bytesize}"
159
+ end
160
+
161
+ if check_start_pos + check_size > all_remaining.bytesize
162
+ raise Omnizip::FormatError,
163
+ "Invalid check position"
164
+ end
165
+
166
+ check_bytes = all_remaining.byteslice(check_start_pos, check_size)
167
+
168
+ # Create new input with remaining data (after this block)
169
+ total_block_size = check_start_pos + check_size
170
+ data_after_block = all_remaining[total_block_size..]
171
+
172
+ # Create new StringIO with remaining data
173
+ new_input = StringIO.new(data_after_block)
174
+ new_input.set_encoding(Encoding::BINARY)
175
+
176
+ # Store the new input for the stream decoder to use
177
+ @new_input_after_block = new_input
178
+ else
179
+ compressed_data = @input.read(compressed_size)
180
+ if compressed_data.nil? || compressed_data.bytesize < compressed_size
181
+ raise Omnizip::IOError,
182
+ "Unexpected end of stream in compressed data: expected #{compressed_size} bytes"
183
+ end
184
+
185
+ # Read block padding (align to 4-byte boundary)
186
+ # Block header is always 4-byte aligned, so we only need to pad the data
187
+ padding_needed = (4 - (compressed_size % 4)) % 4
188
+ if padding_needed.positive?
189
+ padding = @input.read(padding_needed)
190
+ if padding.nil? || padding.bytesize < padding_needed
191
+ raise Omnizip::IOError,
192
+ "Unexpected end of stream in block padding"
193
+ end
194
+ # Verify padding is all zeros
195
+ unless padding.bytes.all?(0)
196
+ raise Omnizip::FormatError,
197
+ "Block padding contains non-zero bytes"
198
+ end
199
+ end
200
+
201
+ # Read check
202
+ if check_size.positive?
203
+ check_bytes = @input.read(check_size)
204
+ if check_bytes.nil? || check_bytes.bytesize < check_size
205
+ raise Omnizip::IOError,
206
+ "Unexpected end of stream in block check"
207
+ end
208
+ else
209
+ check_bytes = ""
210
+ end
211
+
212
+ # When compressed_size is explicit, the input stream is now correctly
213
+ # positioned at the start of the next block, so no need to create new input
214
+ end
215
+
216
+ # Decode filter chain (for now, just LZMA2)
217
+ # Skip if data was already decompressed by decode_lzma2_with_consumption_tracking
218
+ if @data_already_decompressed
219
+ # LZMA2 was already decoded, but we may still have other filters to apply
220
+ # For multi-filter chains, apply remaining filters in reverse order
221
+ filters_to_process = header[:filters].dup
222
+ # Remove the LZMA2 filter that was already processed
223
+ filters_to_process.reject! { |f| f[:id] == FILTER_LZMA2 }
224
+
225
+ if filters_to_process.empty?
226
+ # No remaining filters
227
+ uncompressed_data = @decompressed_data
228
+ else
229
+ # Apply remaining filters in reverse order
230
+ data = @decompressed_data
231
+ filters_to_process.reverse_each do |filter|
232
+ data = decode_single_filter(data, filter)
233
+ end
234
+ uncompressed_data = data
235
+ end
236
+ else
237
+ uncompressed_data = decode_filters(compressed_data,
238
+ header[:filters])
239
+ end
240
+
241
+ # Verify uncompressed size matches header (if present)
242
+ if header[:uncompressed_size] && (uncompressed_data.bytesize != header[:uncompressed_size])
243
+ raise Omnizip::DecompressionError,
244
+ "Uncompressed size mismatch: header says #{header[:uncompressed_size]}, got #{uncompressed_data.bytesize}"
245
+ end
246
+
247
+ # DEBUG: Show output before checksum check
248
+ if ENV["DEBUG_CHECKSUM"]
249
+ puts "DEBUG: uncompressed_data.bytesize=#{uncompressed_data.bytesize}"
250
+ puts "DEBUG: first 100 bytes: #{uncompressed_data[0, 100].inspect}"
251
+ puts "DEBUG: last 50 bytes: #{uncompressed_data[-50..].inspect}"
252
+ end
253
+
254
+ # Verify check
255
+ unless Checksums::Verifier.verify(uncompressed_data, check_bytes,
256
+ @check_type)
257
+ raise Omnizip::ChecksumError,
258
+ "Block checksum mismatch for check type #{@check_type}"
259
+ end
260
+
261
+ # Track block sizes for index validation (per XZ Utils index_hash.c)
262
+ # Unpadded size = block header + compressed data + check (NO padding)
263
+ # This is used to validate against the index records
264
+ # Reference: xz-file-format-1.2.1.txt Section 3.3.2:
265
+ # "Unpadded Size is the size of the Block Header, Compressed Data,
266
+ # and Check fields. The Block Padding field is NOT included."
267
+ @uncompressed_size = uncompressed_data.bytesize
268
+
269
+ # Calculate unpadded block size (excludes padding per XZ spec)
270
+ # Block structure: [block header] [compressed data] [padding] [check]
271
+ # Reference: /Users/mulgogi/src/external/xz/src/liblzma/common/block_decoder.c
272
+ header_size = header[:header_size] || 0
273
+ if compressed_size.nil?
274
+ # When compressed_size wasn't specified, we tracked consumed_bytes
275
+ # unpadded_size = header_size + consumed_bytes + check_size (NO padding)
276
+ # Note: BlockHeaderParser already consumed the header from input
277
+ # For the size calculation, we need to include header size
278
+ actual_compressed_size = consumed_bytes
279
+ @unpadded_size = header_size + actual_compressed_size + check_size
280
+ else
281
+ # When compressed_size was specified
282
+ @unpadded_size = header_size + compressed_size + check_size
283
+ end
284
+
285
+ uncompressed_data
286
+ end
287
+
288
+ private
289
+
290
+ # Decode filter chain
291
+ #
292
+ # @param compressed_data [String] Compressed data
293
+ # @param filters [Array<Hash>] Filter definitions
294
+ # @return [String] Decompressed data
295
+ # @raise [RuntimeError] If filter chain is unsupported
296
+ def decode_filters(compressed_data, filters)
297
+ case filters.size
298
+ when 0
299
+ # No filters - passthrough
300
+ compressed_data
301
+ when 1
302
+ # Single filter - should be LZMA2
303
+ decode_single_filter(compressed_data, filters[0])
304
+ else
305
+ # Multiple filters - decode in reverse order
306
+ # For now, only support LZMA2
307
+ data = compressed_data
308
+ filters.reverse_each do |filter|
309
+ data = decode_single_filter(data, filter)
310
+ end
311
+ data
312
+ end
313
+ end
314
+
315
+ # Decode a single filter
316
+ #
317
+ # @param compressed_data [String] Compressed data
318
+ # @param filter [Hash] Filter definition with :id and :properties
319
+ # @return [String] Decompressed data
320
+ def decode_single_filter(compressed_data, filter)
321
+ case filter[:id]
322
+ when FILTER_LZMA2
323
+ decode_lzma2(compressed_data, filter[:properties])
324
+ when 0x03 # FILTER_DELTA
325
+ decode_delta(compressed_data, filter[:properties])
326
+ when 0x04 # x86 BCJ
327
+ decode_bcj(compressed_data, :x86, filter[:properties])
328
+ when 0x05 # PowerPC BCJ
329
+ decode_bcj(compressed_data, :powerpc, filter[:properties])
330
+ when 0x06 # IA-64 BCJ
331
+ decode_bcj(compressed_data, :ia64, filter[:properties])
332
+ when 0x07 # ARM BCJ
333
+ decode_bcj(compressed_data, :arm, filter[:properties])
334
+ when 0x08 # ARM Thumb BCJ
335
+ decode_bcj(compressed_data, :armthumb, filter[:properties])
336
+ when 0x09 # SPARC BCJ
337
+ decode_bcj(compressed_data, :sparc, filter[:properties])
338
+ when 0x0A # ARM64 BCJ
339
+ decode_bcj_arm64(compressed_data, filter[:properties])
340
+ else
341
+ raise Omnizip::FormatError,
342
+ "Unsupported filter ID: 0x#{filter[:id].to_s(16).upcase}"
343
+ end
344
+ end
345
+
346
+ # Decode Delta filter
347
+ #
348
+ # @param data [String] Input data
349
+ # @param properties [String, nil] Filter properties (first byte is distance - 1)
350
+ # @return [String] Delta-transformed data
351
+ def decode_delta(data, properties)
352
+ # XZ Utils: lzma_delta_props_decode sets opt->dist = props[0] + 1
353
+ # So if props[0] = 0, distance = 1; if props[0] = 255, distance = 256
354
+ distance = if properties&.bytesize&.positive?
355
+ (properties.getbyte(0) || 0) + 1
356
+ else
357
+ 1
358
+ end
359
+
360
+ Omnizip::Filters::Delta.new(distance).decode(data, 0)
361
+ end
362
+
363
+ # Decode BCJ filter
364
+ #
365
+ # @param data [String] Input data
366
+ # @param architecture [Symbol] Target architecture
367
+ # @param properties [String, nil] Filter properties
368
+ # @return [String] BCJ-transformed data
369
+ def decode_bcj(data, architecture, properties)
370
+ # Get start_offset from properties if present
371
+ # XZ filter properties for BCJ: first 4 bytes are start_offset (big-endian)
372
+ start_offset = 0
373
+ if properties&.bytesize&.>= 4
374
+ start_offset = (properties.getbyte(0) || 0) << 24
375
+ start_offset |= (properties.getbyte(1) || 0) << 16
376
+ start_offset |= (properties.getbyte(2) || 0) << 8
377
+ start_offset |= properties.getbyte(3) || 0
378
+ end
379
+
380
+ # Use the appropriate BCJ filter based on architecture
381
+ case architecture
382
+ when :x86
383
+ Omnizip::Filters::BCJ.new(architecture: :x86).decode(data,
384
+ start_offset)
385
+ when :powerpc
386
+ Omnizip::Filters::BCJ.new(architecture: :powerpc).decode(data,
387
+ start_offset)
388
+ when :ia64
389
+ Omnizip::Filters::BCJ.new(architecture: :ia64).decode(data,
390
+ start_offset)
391
+ when :arm
392
+ Omnizip::Filters::BCJ.new(architecture: :arm).decode(data,
393
+ start_offset)
394
+ when :armthumb
395
+ Omnizip::Filters::BCJ.new(architecture: :armthumb).decode(data,
396
+ start_offset)
397
+ when :sparc
398
+ Omnizip::Filters::BCJ.new(architecture: :sparc).decode(data,
399
+ start_offset)
400
+ when :arm64
401
+ Omnizip::Filters::BCJ.new(architecture: :arm64).decode(data,
402
+ start_offset)
403
+ else
404
+ raise Omnizip::FormatError,
405
+ "Unsupported BCJ architecture: #{architecture}"
406
+ end
407
+ end
408
+
409
+ # Decode ARM64 BCJ filter
410
+ #
411
+ # XZ Utils pattern (simple/arm64.c):
412
+ # - Converts BL instructions (bits 26-31 == 0x25) with +/-128 MiB range
413
+ # - Converts ADRP instructions (bits 25-29 == 0x10000) with +/-512 MiB range
414
+ # - Uses start_offset for position calculation
415
+ #
416
+ # @param data [String] Input data
417
+ # @param properties [String, nil] Filter properties (first 4 bytes are start_offset)
418
+ # @return [String] ARM64 BCJ-transformed data
419
+ def decode_bcj_arm64(data, properties)
420
+ # Get start_offset from properties if present
421
+ # XZ filter properties for BCJ: first 4 bytes are start_offset (little-endian per XZ spec)
422
+ # Reference: /Users/mulgogi/src/external/xz/src/liblzma/simple/simple_decoder.c:30
423
+ start_offset = 0
424
+ if properties&.bytesize&.>= 4
425
+ # Read as little-endian (LSB first)
426
+ start_offset = properties.getbyte(0) || 0
427
+ start_offset |= (properties.getbyte(1) || 0) << 8
428
+ start_offset |= (properties.getbyte(2) || 0) << 16
429
+ start_offset |= (properties.getbyte(3) || 0) << 24
430
+ end
431
+
432
+ # DEBUG: Show input data
433
+ if ENV["DEBUG_ARM64_BCJ"]
434
+ puts "DEBUG ARM64 BCJ: start_offset=0x#{start_offset.to_s(16).upcase}"
435
+ puts "DEBUG ARM64 BCJ: input (first 32 bytes):"
436
+ puts data[0, 32].unpack1("H*").scan(/../).each_slice(16).map { |row|
437
+ row.join(" ")
438
+ }.join("\n")
439
+ end
440
+
441
+ # XZ Utils ARM64 BCJ filter implementation
442
+ result = data.b
443
+ size = data.bytesize & ~3 # Round down to multiple of 4
444
+
445
+ (0...size).step(4) do |i|
446
+ pc = (start_offset + i) & 0xFFFFFFFF
447
+ instr = read_uint32_le(result, i)
448
+
449
+ # Check for BL instruction (bits 26-31 == 0x25)
450
+ if (instr >> 26) == 0x25
451
+ src = instr
452
+ instr = 0x94000000
453
+
454
+ # XZ Utils: pc >>= 2; if (!is_encoder) pc = 0U - pc;
455
+ # Reference: /Users/mulgogi/src/external/xz/src/liblzma/simple/arm64.c:56-60
456
+ pc_div_4 = pc >> 2
457
+ pc_for_calc = (0 - pc_div_4) & 0xFFFFFFFF
458
+
459
+ instr |= (src + pc_for_calc) & 0x03FFFFFF
460
+ write_uint32_le(result, i, instr)
461
+ # Check for ADRP instruction (bits 25-29 == 0x10000)
462
+ elsif (instr & 0x9F000000) == 0x90000000
463
+ # Extract src from ADRP instruction
464
+ src = ((instr >> 29) & 3) | ((instr >> 3) & 0x001FFFFC)
465
+
466
+ # Check if in +/-512 MiB range
467
+ # XZ Utils: if ((src + 0x00020000) & 0x001C0000) continue;
468
+ next if (src + 0x00020000).anybits?(0x001C0000)
469
+
470
+ instr &= 0x9000001F
471
+
472
+ # XZ Utils: pc >>= 12; if (!is_encoder) pc = 0U - pc;
473
+ # Reference: /Users/mulgogi/src/external/xz/src/liblzma/simple/arm64.c:95-96
474
+ pc_div_12 = pc >> 12
475
+ pc_for_calc = (0 - pc_div_12) & 0xFFFFFFFF
476
+
477
+ dest = (src + pc_for_calc) & 0xFFFFFFFF
478
+ instr |= (dest & 3) << 29
479
+ instr |= (dest & 0x0003FFFC) << 3
480
+ instr |= (0 - (dest & 0x00020000)) & 0x00E00000
481
+
482
+ write_uint32_le(result, i, instr)
483
+ end
484
+ end
485
+
486
+ # DEBUG: Show output data
487
+ if ENV["DEBUG_ARM64_BCJ"]
488
+ puts "DEBUG ARM64 BCJ: output (first 32 bytes):"
489
+ puts result[0,
490
+ 32].unpack1("H*").scan(/../).each_slice(16).map { |row|
491
+ row.join(" ")
492
+ }.join("\n")
493
+ end
494
+
495
+ result
496
+ end
497
+
498
+ # Read an unsigned 32-bit little-endian integer from data
499
+ #
500
+ # @param data [String] Binary data
501
+ # @param offset [Integer] Starting position
502
+ # @return [Integer] Unsigned 32-bit integer
503
+ def read_uint32_le(data, offset)
504
+ bytes = data.byteslice(offset, 4).bytes
505
+ bytes[0] |
506
+ (bytes[1] << 8) |
507
+ (bytes[2] << 16) |
508
+ (bytes[3] << 24)
509
+ end
510
+
511
+ # Write an unsigned 32-bit little-endian integer to data
512
+ #
513
+ # @param data [String] Binary data (modified in place)
514
+ # @param offset [Integer] Starting position
515
+ # @param value [Integer] 32-bit integer to write
516
+ # @return [void]
517
+ def write_uint32_le(data, offset, value)
518
+ value &= 0xFFFFFFFF
519
+ data.setbyte(offset, value & 0xFF)
520
+ data.setbyte(offset + 1, (value >> 8) & 0xFF)
521
+ data.setbyte(offset + 2, (value >> 16) & 0xFF)
522
+ data.setbyte(offset + 3, (value >> 24) & 0xFF)
523
+ end
524
+
525
+ # Decode LZMA2 data with byte consumption tracking
526
+ #
527
+ # This method is used when compressed_size is not specified in the block header.
528
+ # It uses a CountingInputStream to track how many bytes the LZMA2 decoder consumes.
529
+ #
530
+ # @param all_remaining [String] All remaining data after block header
531
+ # @param filters [Array<Hash>] Filter definitions
532
+ # @return [Array<String, Integer>] Decompressed data and bytes consumed
533
+ def decode_lzma2_with_consumption_tracking(all_remaining:, filters:)
534
+ # Debug: Show first 30 bytes of input data
535
+ if ENV["DEBUG_LZMA2_INPUT"]
536
+ puts "DEBUG LZMA2 INPUT: first 30 bytes:"
537
+ all_remaining.bytes[0, 30].each_with_index do |byte, i|
538
+ printf " [%2d] 0x%02x (%3d)", i, byte, byte
539
+ puts "" if ((i + 1) % 4).zero?
540
+ end
541
+ puts ""
542
+ end
543
+
544
+ input_buffer = CountingInputStream.new(StringIO.new(all_remaining))
545
+ input_buffer.set_encoding(Encoding::BINARY)
546
+
547
+ # Get dict_size from LZMA2 filter properties
548
+ # IMPORTANT: For multi-filter chains, find the LZMA2 filter (not just filters[0])
549
+ # The filter chain is in encoding order, so we need to find the LZMA2 filter
550
+ lzma2_filter = filters.find { |f| f[:id] == FILTER_LZMA2 }
551
+ if lzma2_filter.nil?
552
+ raise Omnizip::FormatError,
553
+ "Unsupported filter chain: LZMA2 filter not found (not supported)"
554
+ end
555
+
556
+ properties = lzma2_filter[:properties]
557
+ # XZ spec: max valid prop is 40 (gives ~2GB dict)
558
+ # Cap at 40 to prevent memory exhaustion from malformed files
559
+ dict_size = if properties&.bytesize&.positive?
560
+ prop = [properties.getbyte(0), MAX_DICT_PROP].min
561
+ if prop.even?
562
+ 1 << ((prop / 2) + 12)
563
+ else
564
+ 3 * (1 << (((prop - 1) / 2) + 11))
565
+ end
566
+ else
567
+ 8 * 1024 * 1024 # 8MB default
568
+ end
569
+ dict_size = [dict_size, MAX_DICT_SIZE].min
570
+
571
+ # Create LZMA2 decoder with raw_mode for XZ format
572
+ decoder = Omnizip::Implementations::XZUtils::LZMA2::Decoder.new(input_buffer,
573
+ raw_mode: true,
574
+ dict_size: dict_size)
575
+
576
+ # Decode stream
577
+ uncompressed_data = decoder.decode_stream
578
+
579
+ # Save decompressed data for filter chain processing
580
+ @decompressed_data = uncompressed_data
581
+
582
+ # Return both data and bytes consumed
583
+ [uncompressed_data, input_buffer.bytes_read]
584
+ end
585
+
586
+ # Decode LZMA2 data
587
+ #
588
+ # @param compressed_data [String] LZMA2 compressed data
589
+ # @param properties [String, nil] LZMA2 properties byte
590
+ # @return [String] Decompressed data
591
+ def decode_lzma2(compressed_data, properties)
592
+ input_buffer = StringIO.new(compressed_data)
593
+ input_buffer.set_encoding(Encoding::BINARY)
594
+
595
+ # For XZ format, LZMA2 data starts with control bytes, not a property byte
596
+ # The filter properties byte contains the dictionary size encoding
597
+ # We need to extract dict_size from properties if available, otherwise use a default
598
+
599
+ # Parse properties byte to get dict_size
600
+ # Properties byte format: (pb * 5 + lp) * 9 + lc for LZMA1
601
+ # For LZMA2, it encodes dictionary size directly
602
+ # Format: if d < 40: size = 2^((d/2) + 12) for even d, or 3 * 2^((d-1)/2 + 11) for odd d
603
+
604
+ # For now, use a reasonable default since the XZ spec doesn't require
605
+ # the dict_size to be specified in the filter properties for LZMA2
606
+ # The block header filter properties byte (0x08 in our test file) encodes dict_size
607
+ # Using the formula from XZ spec for LZMA2 dict_size encoding:
608
+ # prop 0x08 = 8 means: 2^((8/2) + 12) = 2^16 = 65536 bytes (if even)
609
+ # Wait, let me use the standard formula:
610
+ # If prop is even: dict_size = 2^((prop/2) + 12)
611
+ # If prop is odd: dict_size = 3 * 2^((prop-1)/2 + 11)
612
+ dict_size = if properties&.bytesize&.positive?
613
+ prop = [properties.getbyte(0), MAX_DICT_PROP].min
614
+ if prop.even?
615
+ 1 << ((prop / 2) + 12)
616
+ else
617
+ 3 * (1 << (((prop - 1) / 2) + 11))
618
+ end
619
+ else
620
+ 8 * 1024 * 1024 # 8MB default
621
+ end
622
+ dict_size = [dict_size, MAX_DICT_SIZE].min
623
+
624
+ # Create LZMA2 decoder with raw_mode for XZ format
625
+ decoder = Omnizip::Implementations::XZUtils::LZMA2::Decoder.new(input_buffer,
626
+ raw_mode: true,
627
+ dict_size: dict_size)
628
+
629
+ # Decode stream
630
+ decoder.decode_stream
631
+ end
632
+
633
+ # Find the end of LZMA2 compressed data by parsing chunks
634
+ #
635
+ # LZMA2 chunk format:
636
+ # - Control byte (1 byte)
637
+ # - 0x00: End of stream marker (STOP)
638
+ # - 0x01-0x02: Uncompressed chunk
639
+ # - Size (2 bytes, big-endian) + 1
640
+ # - Uncompressed data
641
+ # - 0x03-0x7F: Compressed chunk (LZMA)
642
+ # - Properties (1 byte)
643
+ # - Compressed LZMA data
644
+ # - 0x80-0xFF: Compressed chunk (LZMA)
645
+ # - Uncompressed size (2 bytes, big-endian, high 5 bits in control)
646
+ # - Compressed size (2 bytes, big-endian) + 1
647
+ # - Properties (1 byte, if control >= 0xC0)
648
+ # - Compressed LZMA data
649
+ #
650
+ # @param data [String] LZMA2 data to parse
651
+ # @return [Integer] Position where compressed data ends (before check bytes)
652
+ def find_lzma2_compressed_data_end(data)
653
+ pos = 0
654
+
655
+ while pos < data.bytesize
656
+ control = data.getbyte(pos)
657
+ pos += 1
658
+
659
+ case control
660
+ when 0x00
661
+ # End of stream marker - LZMA2 data ends here
662
+ # Return pos (which includes the end marker, as we've already read it)
663
+ return pos
664
+ when 0x01, 0x02
665
+ # Uncompressed chunk
666
+ # Size encoding: 2 bytes (big-endian) + 1
667
+ size_bytes = data.getbyte(pos) || 0
668
+ pos += 1
669
+ size_bytes = (size_bytes << 8) | (data.getbyte(pos) || 0)
670
+ pos += 1
671
+ uncompressed_size = size_bytes + 1
672
+
673
+ # Skip uncompressed data
674
+ pos += uncompressed_size
675
+ when 0x03..0x7F
676
+ # Compressed chunk (LZMA without explicit uncompressed size)
677
+ # Skip properties byte
678
+ pos += 1
679
+
680
+ # For compressed data, we need to find where it ends
681
+ # This is complex because the range decoder consumes variable bytes
682
+ # For now, we'll look ahead for patterns that indicate chunk boundaries
683
+
684
+ # Look for next chunk start (0x00, 0x01, 0x02, or 0x03-0x7F)
685
+ # But we need to be careful not to mistake data for chunk markers
686
+ #
687
+ # Heuristic: scan forward looking for potential chunk starts
688
+ # A valid chunk start would be followed by valid data structure
689
+ found_next_chunk = false
690
+ scan_pos = pos
691
+
692
+ while scan_pos < data.bytesize && !found_next_chunk
693
+ next_byte = data.getbyte(scan_pos)
694
+
695
+ # Check if this could be a chunk start
696
+ case next_byte
697
+ when 0x00
698
+ # End marker - this is the end of the block
699
+ return scan_pos
700
+ when 0x01, 0x02
701
+ # Uncompressed chunk - verify it has valid size byte
702
+ next_next_byte = data.getbyte(scan_pos + 1)
703
+ if next_next_byte
704
+ size_hi = (next_byte >> 5)
705
+ size_lo = next_next_byte
706
+ uncompressed_size = (size_hi << 8) | size_lo
707
+
708
+ # Check if this size makes sense (not too large)
709
+ if uncompressed_size <= 1024 && scan_pos + 1 + uncompressed_size <= data.bytesize
710
+ # Valid uncompressed chunk found
711
+ return scan_pos
712
+ end
713
+ end
714
+ scan_pos += 1
715
+ when 0x03..0x7F
716
+ # Another compressed chunk - verify it has properties byte
717
+ if scan_pos + 1 < data.bytesize
718
+ # Could be valid - assume this is the next chunk
719
+ return scan_pos
720
+ end
721
+
722
+ scan_pos += 1
723
+ else
724
+ scan_pos += 1
725
+ end
726
+ end
727
+
728
+ # If we couldn't find a clear boundary, use current position
729
+ return pos
730
+ when 0x80..0xFF
731
+ # Compressed chunk (LZMA with explicit uncompressed size)
732
+ # Uncompressed size (2 bytes, big-endian)
733
+ pos += 2
734
+
735
+ # Compressed size (2 bytes, big-endian) + 1
736
+ compressed_size_hi = data.getbyte(pos) || 0
737
+ pos += 1
738
+ compressed_size_lo = data.getbyte(pos) || 0
739
+ pos += 1
740
+ compressed_size = (compressed_size_hi << 8) | compressed_size_lo
741
+ compressed_size += 1
742
+
743
+ # Properties byte (if control >= 0xC0)
744
+ pos += 1 if control >= 0xC0
745
+
746
+ # Skip compressed LZMA data
747
+ pos += compressed_size
748
+ else
749
+ # Invalid control byte
750
+ raise Omnizip::FormatError,
751
+ "Invalid LZMA2 control byte: 0x#{control.to_s(16).upcase}"
752
+ end
753
+ end
754
+
755
+ # If we reach here, we've consumed all data
756
+ pos
757
+ end
758
+ end
759
+ end
760
+ end
761
+ end