omnizip 0.4.7-aarch64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,368 @@
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 Algorithms
27
+ class LZMA < Algorithm
28
+ #
29
+ # Decoder for .lz (lzip) format
30
+ #
31
+ # This is the lzip format, a DIFFERENT container format from both
32
+ # XZ and .lzma (LZMA_Alone). Lzip was created as an alternative to
33
+ # the legacy .lzma format with better integrity checking.
34
+ #
35
+ # File format:
36
+ # - Magic bytes: "LZIP" (0x4C 0x5A 0x49 0x50)
37
+ # - Version (1 byte): 0 or 1
38
+ # - Dictionary size (1 byte): encoded format
39
+ # - LZMA1 compressed stream (with fixed LC=3, LP=0, PB=2)
40
+ # - Footer:
41
+ # - Version 0 (12 bytes): CRC32 (4) + Uncompressed size (8)
42
+ # - Version 1 (20 bytes): CRC32 (4) + Uncompressed size (8) + Member size (8)
43
+ #
44
+ # Reference: /Users/mulgogi/src/external/xz/src/liblzma/common/lzip_decoder.c
45
+ #
46
+ # This decoder uses the same LZMA1 decoding engine as XZ format,
47
+ # but with the lzip container format and CRC32 integrity checking.
48
+ #
49
+ # @example Decode .lz file
50
+ # data = File.binread("file.lz")
51
+ # decoder = Omnizip::Algorithms::LZMA::LzipDecoder.new(StringIO.new(data))
52
+ # result = decoder.decode_stream
53
+ #
54
+ class LzipDecoder
55
+ attr_reader :version, :dict_size, :member_size
56
+
57
+ # Lzip magic bytes: "LZIP" in ASCII
58
+ # Reference: lzip_decoder.c:106
59
+ MAGIC = [0x4C, 0x5A, 0x49, 0x50].freeze
60
+
61
+ # Fixed LC/LP/PB values for lzip format
62
+ # Reference: lzip_decoder.c:23-26
63
+ LZIP_LC = 3
64
+ LZIP_LP = 0
65
+ LZIP_PB = 2
66
+
67
+ # Footer sizes
68
+ # Reference: lzip_decoder.c:19-21
69
+ LZIP_V0_FOOTER_SIZE = 12
70
+ LZIP_V1_FOOTER_SIZE = 20
71
+ LZIP_FOOTER_SIZE_MAX = LZIP_V1_FOOTER_SIZE
72
+
73
+ # Minimum and maximum dictionary sizes (in bytes)
74
+ # Reference: lzip_decoder.c:197-198
75
+ MIN_DICT_SIZE = 4096 # 4 KiB
76
+ MAX_DICT_SIZE = (512 << 20) # 512 MiB
77
+
78
+ # Initialize the decoder with .lz format input
79
+ #
80
+ # @param input [IO] Input stream of .lz compressed data
81
+ # @param options [Hash] Decoding options
82
+ # @option options [Boolean] :ignore_check If true, skip CRC32 verification (default: false)
83
+ # @option options [Boolean] :concatenated If true, decode concatenated .lz members (default: false)
84
+ # @raise [Omnizip::DecompressionError] If header is invalid or unsupported
85
+ def initialize(input, options = {})
86
+ @input = input
87
+ @ignore_check = options.fetch(:ignore_check, false)
88
+ # Concatenated mode is enabled by default (lzip natively supports multiple members)
89
+ @concatenated = options.fetch(:concatenated, true)
90
+
91
+ # Parse .lz header
92
+ parse_header
93
+
94
+ # Track member size (including header and footer)
95
+ # We start with the 6 bytes we've already read (magic + version + dict_size)
96
+ @member_size = 6
97
+
98
+ # For concatenated mode, track if this is the first member
99
+ @first_member = true
100
+
101
+ # Initialize CRC32 calculator
102
+ @crc32 = 0
103
+ @uncompressed_size = 0
104
+ end
105
+
106
+ # Decode the .lz stream
107
+ #
108
+ # @param output [IO, nil] Optional output stream
109
+ # @return [String, Integer] Decompressed data or bytes written
110
+ def decode_stream(output = nil)
111
+ # For concatenated mode, accumulate all decoded data
112
+ all_decoded_data = String.new(encoding: Encoding::BINARY)
113
+ bytes_written = 0
114
+ result = nil # Initialize result variable
115
+
116
+ loop do
117
+ # Track the starting position of compressed data
118
+ start_pos = @input.pos
119
+
120
+ # Initialize the XZ Utils LZMA decoder with fixed lzip parameters
121
+ decoder = XzUtilsDecoder.new(@input,
122
+ lzma2_mode: true,
123
+ lc: LZIP_LC,
124
+ lp: LZIP_LP,
125
+ pb: LZIP_PB,
126
+ dict_size: @dict_size,
127
+ uncompressed_size: 0xFFFFFFFFFFFFFFFF) # Unknown size, allow EOPM
128
+
129
+ # Decode the LZMA stream (allow EOPM for .lz format)
130
+ # Get decoded data as string (no output stream)
131
+ decoded_data = decoder.decode_stream(nil, check_rc_finished: false)
132
+
133
+ # If caller provided output stream, write to it
134
+ if output
135
+ output.write(decoded_data)
136
+ bytes_written += decoded_data.bytesize
137
+ result = bytes_written
138
+ else
139
+ all_decoded_data << decoded_data
140
+ result = all_decoded_data
141
+ end
142
+
143
+ # Calculate member size (header + compressed data + footer)
144
+ # We calculate it here (compressed data + header), then add footer size below
145
+ @member_size = @input.pos - start_pos + 6 # +6 for header bytes
146
+
147
+ # Calculate and verify CRC32
148
+ if @ignore_check
149
+ # Skip footer
150
+ footer_size = @version.zero? ? LZIP_V0_FOOTER_SIZE : LZIP_V1_FOOTER_SIZE
151
+ @input.read(footer_size)
152
+ @member_size += footer_size
153
+ else
154
+ data_to_crc = decoded_data || +""
155
+ calculated_crc = Omnizip::Checksums::Crc32.calculate(data_to_crc)
156
+ @uncompressed_size = data_to_crc.bytesize
157
+
158
+ # Read and verify footer (also updates @member_size to include footer)
159
+ verify_footer(calculated_crc)
160
+ end
161
+
162
+ # Check for concatenated members
163
+ break unless @concatenated
164
+
165
+ # Peek ahead to check if there's another LZIP member
166
+ break unless has_next_member?
167
+
168
+ # Parse header for next member
169
+ parse_header
170
+ end
171
+
172
+ # Return decoded data or bytes written
173
+ result
174
+ end
175
+
176
+ private
177
+
178
+ # Check if there's another concatenated LZIP member
179
+ # Peeks ahead without consuming the magic bytes
180
+ #
181
+ # @return [Boolean] true if another member is present
182
+ def has_next_member?
183
+ # Peek at next 4 bytes to check for magic
184
+ magic_bytes = @input.read(4)
185
+ return false if magic_bytes.nil? || magic_bytes.bytesize < 4
186
+
187
+ # Check if it's LZIP magic
188
+ is_lzip = magic_bytes.getbyte(0) == MAGIC[0] &&
189
+ magic_bytes.getbyte(1) == MAGIC[1] &&
190
+ magic_bytes.getbyte(2) == MAGIC[2] &&
191
+ magic_bytes.getbyte(3) == MAGIC[3]
192
+
193
+ # Put the bytes back by seeking back
194
+ @input.seek(-4, ::IO::SEEK_CUR) if is_lzip
195
+
196
+ is_lzip
197
+ end
198
+
199
+ # Parse .lz format header
200
+ #
201
+ # Format (from lzip_decoder.c):
202
+ # - Magic bytes: "LZIP" (4 bytes)
203
+ # - Version (1 byte): 0 or 1
204
+ # - Dictionary size (1 byte): encoded format
205
+ #
206
+ # Reference: /Users/mulgogi/src/external/xz/src/liblzma/common/lzip_decoder.c
207
+ #
208
+ # @return [void]
209
+ # @raise [Omnizip::DecompressionError] If header is invalid
210
+ def parse_header
211
+ # Step 1: Verify magic bytes (SEQ_ID_STRING)
212
+ # Reference: lzip_decoder.c:104-153
213
+ magic_bytes = @input.read(4)
214
+ if magic_bytes.nil? || magic_bytes.bytesize < 4
215
+ raise Omnizip::DecompressionError,
216
+ "Incomplete .lz header: missing magic bytes"
217
+ end
218
+
219
+ 4.times do |i|
220
+ if magic_bytes.getbyte(i) != MAGIC[i]
221
+ raise Omnizip::DecompressionError, "Invalid .lz header: magic bytes don't match LZIP (expected #{MAGIC.map do |b|
222
+ "0x#{b.to_s(16).upcase}"
223
+ end.join(' ')}, got #{magic_bytes.bytes.map do |b|
224
+ "0x#{b.to_s(16).upcase}"
225
+ end.join(' ')})"
226
+ end
227
+ end
228
+
229
+ # Step 2: Read version byte (SEQ_VERSION)
230
+ # Reference: lzip_decoder.c:156-174
231
+ version_byte = @input.getbyte
232
+ if version_byte.nil?
233
+ raise Omnizip::DecompressionError,
234
+ "Incomplete .lz header: missing version byte"
235
+ end
236
+
237
+ @version = version_byte
238
+
239
+ # We support version 0 and unextended version 1
240
+ # Reference: lzip_decoder.c:163-164
241
+ if @version > 1
242
+ raise Omnizip::UnsupportedFormatError,
243
+ "Unsupported .lz version: #{@version} (only 0 and 1 are supported)"
244
+ end
245
+
246
+ # Step 3: Parse dictionary size (SEQ_DICT_SIZE)
247
+ # Reference: lzip_decoder.c:177-222
248
+ dict_size_byte = @input.getbyte
249
+ if dict_size_byte.nil?
250
+ raise Omnizip::DecompressionError,
251
+ "Incomplete .lz header: missing dictionary size byte"
252
+ end
253
+
254
+ # Decode dictionary size from the encoded byte
255
+ # The five lowest bits are for the base-2 logarithm of the dictionary size
256
+ # and the highest three bits are the fractional part (0/16 to 7/16)
257
+ # Reference: lzip_decoder.c:183-204
258
+ b2log = dict_size_byte & 0x1F
259
+ fracnum = dict_size_byte >> 5
260
+
261
+ # Validate range: [4 KiB, 512 MiB]
262
+ # Reference: lzip_decoder.c:198-199
263
+ if b2log < 12 || b2log > 29 || (b2log == 12 && fracnum.positive?)
264
+ raise Omnizip::DecompressionError,
265
+ "Invalid .lz header: dictionary size byte 0x#{dict_size_byte.to_s(16).upcase} is out of valid range"
266
+ end
267
+
268
+ # Calculate: 2^[b2log] - [fracnum] * 2^([b2log] - 4)
269
+ # Reference: lzip_decoder.c:201-204
270
+ @dict_size = (1 << b2log) - (fracnum << (b2log - 4))
271
+
272
+ # Sanity checks
273
+ if @dict_size < MIN_DICT_SIZE
274
+ raise Omnizip::DecompressionError,
275
+ "Dictionary size calculation error: too small"
276
+ end
277
+ if @dict_size > MAX_DICT_SIZE
278
+ raise Omnizip::DecompressionError,
279
+ "Dictionary size calculation error: too large"
280
+ end
281
+ end
282
+
283
+ # Verify .lz format footer
284
+ #
285
+ # Format (from lzip_decoder.c):
286
+ # - CRC32 of uncompressed data (4 bytes, little-endian)
287
+ # - Uncompressed size (8 bytes, little-endian)
288
+ # - Member size (8 bytes, little-endian) - only for version 1
289
+ #
290
+ # Reference: /Users/mulgogi/src/external/xz/src/liblzma/common/lzip_decoder.c:277-309
291
+ #
292
+ # @param calculated_crc [Integer] Calculated CRC32 of uncompressed data
293
+ # @raise [Omnizip::DecompressionError] If footer is invalid or checksum mismatch
294
+ def verify_footer(calculated_crc)
295
+ footer_size = @version.zero? ? LZIP_V0_FOOTER_SIZE : LZIP_V1_FOOTER_SIZE
296
+ footer = @input.read(footer_size)
297
+ if footer.nil? || footer.bytesize < footer_size
298
+ raise Omnizip::DecompressionError,
299
+ "Incomplete .lz footer: expected #{footer_size} bytes, got #{footer&.bytesize || 0}"
300
+ end
301
+
302
+ # Update member_size to include the footer
303
+ @member_size += footer_size
304
+
305
+ # Parse CRC32 (little-endian)
306
+ stored_crc = footer.getbyte(0) | (footer.getbyte(1) << 8) |
307
+ (footer.getbyte(2) << 16) | (footer.getbyte(3) << 24)
308
+
309
+ # Verify CRC32
310
+ if calculated_crc != stored_crc
311
+ raise Omnizip::ChecksumError,
312
+ "CRC32 mismatch: calculated 0x#{calculated_crc.to_s(16).upcase}, stored 0x#{stored_crc.to_s(16).upcase}"
313
+ end
314
+
315
+ # Parse and verify uncompressed size (little-endian)
316
+ stored_uncompressed_size = footer.getbyte(4) | (footer.getbyte(5) << 8) |
317
+ (footer.getbyte(6) << 16) | (footer.getbyte(7) << 24) |
318
+ (footer.getbyte(8) << 32) | (footer.getbyte(9) << 40) |
319
+ (footer.getbyte(10) << 48) | (footer.getbyte(11) << 56)
320
+
321
+ if @uncompressed_size != stored_uncompressed_size
322
+ raise Omnizip::ChecksumError,
323
+ "Uncompressed size mismatch: decoded #{@uncompressed_size}, stored #{stored_uncompressed_size}"
324
+ end
325
+
326
+ # For version 1, verify member size
327
+ if @version.positive?
328
+ stored_member_size = footer.getbyte(12) | (footer.getbyte(13) << 8) |
329
+ (footer.getbyte(14) << 16) | (footer.getbyte(15) << 24) |
330
+ (footer.getbyte(16) << 32) | (footer.getbyte(17) << 40) |
331
+ (footer.getbyte(18) << 48) | (footer.getbyte(19) << 56)
332
+
333
+ if @member_size != stored_member_size
334
+ raise Omnizip::ChecksumError,
335
+ "Member size mismatch: decoded #{@member_size}, stored #{stored_member_size}"
336
+ end
337
+ end
338
+ end
339
+
340
+ # Wrapper input stream that tracks bytes read
341
+ class TrackingInputStream
342
+ attr_reader :bytes_read
343
+
344
+ def initialize(input, start_offset = 0)
345
+ @input = input
346
+ @bytes_read = start_offset
347
+ end
348
+
349
+ def read(size = nil)
350
+ data = @input.read(size)
351
+ @bytes_read += data.bytesize if data
352
+ data
353
+ end
354
+
355
+ def getbyte
356
+ byte = @input.getbyte
357
+ @bytes_read += 1 if byte
358
+ byte
359
+ end
360
+
361
+ def eof?
362
+ @input.eof?
363
+ end
364
+ end
365
+ end
366
+ end
367
+ end
368
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+
5
+ # Copyright (C) 2025 Ribose Inc.
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a
8
+ # copy of this software and associated documentation files (the "Software"),
9
+ # to deal in the Software without restriction, including without limitation
10
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
11
+ # and/or sell copies of the Software, and to permit persons to whom the
12
+ # Software is furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in
15
+ # all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23
+ # DEALINGS IN THE SOFTWARE.
24
+
25
+ module Omnizip
26
+ module Algorithms
27
+ class LZMA
28
+ # Raw LZMA1 stream encoder (one continuous range-coded stream
29
+ # with the end-of-stream marker), reusing the XZ Utils symbol
30
+ # coders. This is the payload for the legacy .lzma container
31
+ # and the lzip member body.
32
+ class Lzma1Encoder < Implementations::XZUtils::LZMA2::Encoder
33
+ UINT32_MAX = 0xFFFFFFFF
34
+ REPS = 4
35
+
36
+ # Encode `input` as a complete standalone LZMA1 stream,
37
+ # terminated by the end-of-stream marker (dist = 0xFFFFFFFF,
38
+ # length 2) so decoders that do not know the size can stop.
39
+ #
40
+ # @param input [String]
41
+ # @param emit_eopm [Boolean] omit when the container carries
42
+ # the uncompressed size (the legacy .lzma form)
43
+ # @return [String] binary stream
44
+ # rubocop:disable-next Metrics/MethodLength
45
+ def encode(input, emit_eopm: true)
46
+ @match_finder.reset
47
+ output_buffer = StringIO.new
48
+ output_buffer.set_encoding(Encoding::BINARY)
49
+ encoder = XzRangeEncoder.new(output_buffer)
50
+ @match_finder.feed(input)
51
+ @match_finder.skip(@match_finder.buffer.bytesize)
52
+
53
+ start_pos = 0
54
+ @current_start_pos = start_pos
55
+ pos = 0
56
+ while pos < input.bytesize
57
+ encode_queued_symbols(encoder, output_buffer)
58
+
59
+ match_pos = start_pos + pos
60
+ distance, length = @optimal.find_optimal(
61
+ match_pos, @match_finder, @state, @state.reps, @models
62
+ )
63
+
64
+ if distance == UINT32_MAX || length == 1
65
+ encode_literal(input.getbyte(pos), encoder, match_pos)
66
+ pos += 1
67
+ elsif distance < REPS
68
+ encode_repeated_match(distance, length, encoder, match_pos,
69
+ match_pos)
70
+ pos += length
71
+ else
72
+ encode_match(distance - REPS, length, encoder, match_pos,
73
+ match_pos, input)
74
+ pos += length
75
+ end
76
+ end
77
+
78
+ encode_eopm(encoder, output_buffer, input.bytesize) if emit_eopm
79
+ encode_queued_symbols(encoder, output_buffer)
80
+ encoder.queue_flush
81
+ encode_queued_symbols(encoder, output_buffer)
82
+ output_buffer.string
83
+ end
84
+
85
+ private
86
+
87
+ # End-of-stream marker: a normal match header with
88
+ # dist0 = 0xFFFFFFFF and length 2. The distance slot is 63,
89
+ # whose 30 footer bits the parent's distance coder emits as
90
+ # 26 direct bits + the 4 align bits.
91
+ def encode_eopm(encoder, output_buffer, total_pos)
92
+ pos_state = total_pos & ((1 << @pb) - 1)
93
+ encoder.queue_bit(@models.is_match[@state.value][pos_state], 1)
94
+ encoder.queue_bit(@models.is_rep[@state.value], 0)
95
+ @state.update_match!(UINT32_MAX)
96
+ encode_match_length(2, pos_state, encoder)
97
+ # Slot 63's 30 footer bits plus the length/header symbols
98
+ # exceed the 53-slot symbol queue in one go.
99
+ encode_queued_symbols(encoder, output_buffer)
100
+ encode_distance(UINT32_MAX + 1, 2, encoder)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,194 @@
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 Algorithms
27
+ class LZMA < Algorithm
28
+ #
29
+ # Decoder for .lzma (LZMA_Alone) format
30
+ #
31
+ # This is the legacy LZMA_Alone format used by LZMA Utils 4.32.x.
32
+ # It is DIFFERENT from the XZ format's LZMA2 compression!
33
+ #
34
+ # File format:
35
+ # - Properties (1 byte): encodes lc, lp, pb values
36
+ # - Dictionary size (4 bytes, little-endian)
37
+ # - Uncompressed size (8 bytes, little-endian, UINT64_MAX = unknown)
38
+ # - LZMA1 compressed stream (no footer, no CRC32)
39
+ #
40
+ # Reference: /Users/mulgogi/src/external/xz/src/liblzma/common/alone_decoder.c
41
+ #
42
+ # This decoder uses the same LZMA1 decoding engine as XZ format,
43
+ # but with the legacy .lzma container format.
44
+ #
45
+ # @example Decode .lzma file
46
+ # data = File.binread("file.lzma")
47
+ # decoder = Omnizip::Algorithms::LZMA::LzmaAloneDecoder.new(StringIO.new(data))
48
+ # result = decoder.decode_stream
49
+ #
50
+ class LzmaAloneDecoder
51
+ # Maximum valid uncompressed size (256 GiB)
52
+ # From alone_decoder.c:118
53
+ MAX_UNCOMPRESSED_SIZE = (1 << 38)
54
+
55
+ # Property byte validation limits
56
+ # From lzma_decoder.c:1218
57
+ MAX_PROPERTY_BYTE = (((4 * 5) + 4) * 9) + 8 # = 233
58
+
59
+ # Header fields exposed to container callers.
60
+ attr_reader :lc, :lp, :pb, :dict_size, :uncompressed_size
61
+
62
+ # Initialize the decoder with .lzma format input
63
+ #
64
+ # @param input [IO] Input stream of .lzma compressed data
65
+ # @param options [Hash] Decoding options
66
+ # @option options [Boolean] :picky If true, reject files unlikely to be .lzma (default: false)
67
+ # @raise [RuntimeError] If header is invalid or unsupported
68
+ def initialize(input, options = {})
69
+ @input = input
70
+ @picky = options.fetch(:picky, false)
71
+
72
+ # Parse .lzma header
73
+ parse_header
74
+
75
+ # Create a wrapper stream that starts after the header
76
+ # The XzUtilsDecoder will read from this stream
77
+ @lzma_stream = @input
78
+
79
+ # Initialize the XZ Utils LZMA decoder with parsed parameters
80
+ # validate_size=true because .lzma format has explicit uncompressed size
81
+ # allow_eopm=true because .lzma format allows EOPM even with known size
82
+ # Reference: alone_decoder.c:127 (LZMA_LZMA1EXT_ALLOW_EOPM)
83
+ @decoder = XzUtilsDecoder.new(@lzma_stream,
84
+ lzma2_mode: true,
85
+ validate_size: true,
86
+ allow_eopm: true,
87
+ lc: @lc,
88
+ lp: @lp,
89
+ pb: @pb,
90
+ dict_size: @dict_size,
91
+ uncompressed_size: @uncompressed_size)
92
+ end
93
+
94
+ # Decode the .lzma stream
95
+ #
96
+ # @param output [IO, nil] Optional output stream
97
+ # @return [String, Integer] Decompressed data or bytes written
98
+ def decode_stream(output = nil)
99
+ # .lzma format allows EOPM even when uncompressed size is known
100
+ # Reference: alone_decoder.c:127 (LZMA_LZMA1EXT_ALLOW_EOPM)
101
+ @decoder.decode_stream(output, check_rc_finished: false)
102
+ end
103
+
104
+ private
105
+
106
+ # Parse .lzma format header
107
+ #
108
+ # Format (from alone_decoder.c):
109
+ # - Properties (1 byte): lc/lp/pb encoded
110
+ # - Dictionary size (4 bytes, little-endian)
111
+ # - Uncompressed size (8 bytes, little-endian, UINT64_MAX = unknown)
112
+ #
113
+ # Reference: /Users/mulgogi/src/external/xz/src/liblzma/common/alone_decoder.c
114
+ #
115
+ # @return [void]
116
+ # @raise [RuntimeError] If header is invalid
117
+ def parse_header
118
+ # Step 1: Parse properties byte (SEQ_PROPERTIES)
119
+ # Reference: alone_decoder.c:64-68
120
+ props = @input.getbyte
121
+ raise Omnizip::DecompressionError, "Invalid .lzma header: missing properties byte" if props.nil?
122
+
123
+ # Use XZ Utils property byte parsing
124
+ # Reference: /Users/mulgogi/src/external/xz/src/liblzma/lzma/lzma_decoder.c:1216-1228
125
+ if props > MAX_PROPERTY_BYTE
126
+ raise Omnizip::DecompressionError, "Invalid .lzma header: properties byte #{props} exceeds maximum #{MAX_PROPERTY_BYTE}"
127
+ end
128
+
129
+ # Parse lc, lp, pb from properties byte
130
+ # Formula: pb = props / (9 * 5); lp = (props % 45) / 9; lc = (props % 45) % 9
131
+ @pb = props / (9 * 5)
132
+ remainder = props - (@pb * 9 * 5)
133
+ @lp = remainder / 9
134
+ @lc = remainder - (@lp * 9)
135
+
136
+ # Validate lc + lp <= 4 (LZMA_LCLP_MAX)
137
+ # Reference: lzma_decoder.c:1227
138
+ if @lc + @lp > 4
139
+ raise Omnizip::DecompressionError, "Invalid .lzma header: lc (#{@lc}) + lp (#{@lp}) exceeds maximum 4"
140
+ end
141
+
142
+ # Step 2: Parse dictionary size (SEQ_DICTIONARY_SIZE)
143
+ # Reference: alone_decoder.c:71-96
144
+ @dict_size = 0
145
+ 4.times do |i|
146
+ byte = @input.getbyte
147
+ raise Omnizip::DecompressionError, "Incomplete .lzma header: missing dictionary size byte" if byte.nil?
148
+
149
+ @dict_size |= (byte << (i * 8))
150
+ end
151
+
152
+ # Picky mode validation: only accept dictionary sizes that are
153
+ # 2^n or 2^n + 2^(n-1). This reduces false positives.
154
+ # Reference: alone_decoder.c:76-93
155
+ if @picky && @dict_size != 0xFFFFFFFF
156
+ # Check if dict_size is valid: 2^n or 2^n + 2^(n-1)
157
+ d = @dict_size - 1
158
+ d |= d >> 2
159
+ d |= d >> 3
160
+ d |= d >> 4
161
+ d |= d >> 8
162
+ d |= d >> 16
163
+ d += 1
164
+
165
+ if d != @dict_size
166
+ raise Omnizip::DecompressionError, "Invalid .lzma header: dictionary size #{@dict_size} is not 2^n or 2^n + 2^(n-1)"
167
+ end
168
+ end
169
+
170
+ # Step 3: Parse uncompressed size (SEQ_UNCOMPRESSED_SIZE)
171
+ # Reference: alone_decoder.c:102-120
172
+ @uncompressed_size = 0
173
+ 8.times do |i|
174
+ byte = @input.getbyte
175
+ raise Omnizip::DecompressionError, "Incomplete .lzma header: missing uncompressed size byte" if byte.nil?
176
+
177
+ @uncompressed_size |= (byte << (i * 8))
178
+ end
179
+
180
+ # Picky mode validation: if uncompressed size is known (not UINT64_MAX),
181
+ # it must be less than 256 GiB
182
+ # Reference: alone_decoder.c:116-120
183
+ if @picky && @uncompressed_size != 0xFFFFFFFFFFFFFFFF &&
184
+ @uncompressed_size >= MAX_UNCOMPRESSED_SIZE
185
+ raise Omnizip::DecompressionError, "Invalid .lzma header: uncompressed size #{@uncompressed_size} exceeds maximum #{MAX_UNCOMPRESSED_SIZE}"
186
+ end
187
+
188
+ # Note: XZ Utils uses UINT64_MAX (0xFFFFFFFFFFFFFFFF) for unknown size
189
+ # Our decoder treats this as "allow end-of-payload marker"
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end