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,470 @@
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 Stream decoder
29
+ #
30
+ # Decodes a complete XZ stream which consists of:
31
+ # - Stream Header (12 bytes)
32
+ # - Blocks (one or more)
33
+ # - Index
34
+ # - Stream Footer (12 bytes)
35
+ #
36
+ # Reference: /tmp/xz-source/src/liblzma/common/stream_decoder.c
37
+ class StreamDecoder
38
+ # Decode XZ stream from input
39
+ #
40
+ # @param input [IO] Input stream (file, StringIO, etc.)
41
+ # @return [String] Decompressed data
42
+ # @raise [RuntimeError] If stream is invalid
43
+ def self.decode(input)
44
+ header = StreamHeaderParser.parse(input)
45
+ check_type = header[:check_type]
46
+
47
+ # Store original input and file size for backward_size validation (if available)
48
+ original_input = input
49
+ # allowed: caller-supplied IO; only sized streams get validated
50
+ original_file_size = input.size if input.respond_to?(:size)
51
+
52
+ output, block_count, final_input, block_sizes = decode_blocks(input,
53
+ check_type)
54
+ index = verify_index(final_input, block_count, block_sizes)
55
+
56
+ # Validate backward_size points to valid index position (XZ spec requirement)
57
+ # XZ spec: "The value of Backward Size is the size of the Index field...stored in
58
+ # multiples of four bytes...If the stored value does not match the real size of
59
+ # the Index field, the decoder MUST indicate an error."
60
+ # Reference: /Users/mulgogi/src/external/xz/src/liblzma/common/stream_decoder.c
61
+ # allowed: caller-supplied IO; footer check needs seeking
62
+ if original_input.respond_to?(:seek) && original_file_size&.positive?
63
+ # Use original input and file size for validation
64
+ validate_backward_size_from_footer(original_input,
65
+ original_file_size, index[:index_size])
66
+ end
67
+
68
+ # Read the stream footer to advance input position past it
69
+ read_stream_footer(final_input, check_type, index[:index_size])
70
+
71
+ # Now check for trailing data after the stream footer
72
+ verify_no_trailing_data(final_input)
73
+
74
+ output.join.force_encoding(Encoding::BINARY)
75
+ end
76
+
77
+ # Decode all blocks from stream until index marker
78
+ #
79
+ # @param input [IO] Input stream
80
+ # @param check_type [Symbol] Checksum type
81
+ # @return [Array, Integer, IO, Array] Output data array, block count, final input stream, and array of block size info
82
+ def self.decode_blocks(input, check_type)
83
+ output = []
84
+ block_count = 0
85
+ block_sizes = [] # Track unpadded and uncompressed sizes for index validation
86
+
87
+ loop do
88
+ peek_byte = input.getbyte
89
+ raise FormatError, "Unexpected end of stream" if peek_byte.nil?
90
+
91
+ if peek_byte == XzConst::INDEX_INDICATOR
92
+ restore_byte_for_index(input, peek_byte)
93
+ break
94
+ end
95
+
96
+ data, decoder = decode_block(input, peek_byte, check_type)
97
+ output << data
98
+ block_count += 1
99
+
100
+ # Track block sizes for index validation (per XZ Utils index_hash.c)
101
+ if decoder.unpadded_size && decoder.uncompressed_size
102
+ block_sizes << {
103
+ unpadded_size: decoder.unpadded_size,
104
+ uncompressed_size: decoder.uncompressed_size,
105
+ }
106
+ end
107
+
108
+ # If block decoder created a new input (for multi-block files without explicit sizes),
109
+ # use it for the next iteration
110
+ input = decoder.new_input_after_block if decoder.new_input_after_block
111
+ end
112
+
113
+ [output, block_count, input, block_sizes]
114
+ end
115
+
116
+ # Restore byte to stream for index parser
117
+ #
118
+ # @param input [IO] Input stream
119
+ # @param peek_byte [Integer] Byte to restore
120
+ def self.restore_byte_for_index(input, peek_byte)
121
+ restore_byte(input, peek_byte)
122
+ end
123
+
124
+ # Decode single block from stream
125
+ #
126
+ # @param input [IO] Input stream
127
+ # @param peek_byte [Integer] Peeked byte
128
+ # @param check_type [Symbol] Checksum type
129
+ # @return [Array, Hash, BlockDecoder] Decompressed data, block info, and decoder instance
130
+ def self.decode_block(input, peek_byte, check_type)
131
+ restore_byte(input, peek_byte)
132
+ decoder = BlockDecoder.new(input, check_type)
133
+ data = decoder.decode
134
+ [data, decoder]
135
+ end
136
+
137
+ # Restore a byte to the input stream
138
+ #
139
+ # @param input [IO] Input stream
140
+ # @param byte [Integer] Byte to restore
141
+ # @raise [RuntimeError] If IO doesn't support ungetbyte
142
+ def self.restore_byte(input, byte)
143
+ # allowed: caller-supplied IO; FormatError follows when unsupported
144
+ return input.ungetbyte(byte) if input.respond_to?(:ungetbyte)
145
+
146
+ raise FormatError,
147
+ "IO object doesn't support ungetbyte - cannot parse stream"
148
+ end
149
+
150
+ # Parse and verify index matches decoded blocks
151
+ #
152
+ # @param input [IO] Input stream
153
+ # @param block_count [Integer] Number of blocks decoded
154
+ # @param block_sizes [Array<Hash>] Array of {unpadded_size, uncompressed_size} for each block
155
+ # @return [Hash] Index data including index_size for backward_size validation
156
+ # @raise [FormatError] If index doesn't match decoded blocks
157
+ def self.verify_index(input, block_count, block_sizes)
158
+ index = IndexDecoder.parse(input)
159
+
160
+ # Validate count matches
161
+ if index[:count] != block_count
162
+ raise FormatError,
163
+ "Index count mismatch: index says #{index[:count]}, decoded #{block_count}"
164
+ end
165
+
166
+ # Validate block sizes match index records (per XZ Utils index_hash.c:244-290)
167
+ # This catches corrupted index files where the sizes don't match the actual blocks
168
+ if block_sizes.any? && index[:records].any?
169
+ # Helper function to calculate VLI ceil4 (round up to multiple of 4)
170
+ # Reference: /Users/mulgogi/src/external/xz/src/liblzma/common/index.h:48
171
+ vli_ceil4 = lambda { |vli|
172
+ (vli + 3) & ~3
173
+ }
174
+
175
+ # Calculate sums from actual blocks
176
+ # Note: XZ Utils uses vli_ceil4 on unpadded_size when summing
177
+ blocks_unpadded_sum = block_sizes.sum do |b|
178
+ vli_ceil4.call(b[:unpadded_size])
179
+ end
180
+ blocks_uncompressed_sum = block_sizes.sum do |b|
181
+ b[:uncompressed_size]
182
+ end
183
+
184
+ # Calculate sums from index records
185
+ # Note: Index records already contain the unpadded_size, need to ceil4 them too
186
+ index_unpadded_sum = index[:records].sum do |r|
187
+ vli_ceil4.call(r[:unpadded_size])
188
+ end
189
+ index_uncompressed_sum = index[:records].sum do |r|
190
+ r[:uncompressed_size]
191
+ end
192
+
193
+ # Validate sums match
194
+ if blocks_unpadded_sum != index_unpadded_sum
195
+ raise FormatError,
196
+ "Index unpadded size mismatch: blocks sum to #{blocks_unpadded_sum}, " \
197
+ "index says #{index_unpadded_sum}"
198
+ end
199
+
200
+ if blocks_uncompressed_sum != index_uncompressed_sum
201
+ raise FormatError,
202
+ "Index uncompressed size mismatch: blocks sum to #{blocks_uncompressed_sum}, " \
203
+ "index says #{index_uncompressed_sum}"
204
+ end
205
+
206
+ # Validate individual record sizes match (in correct order)
207
+ # Compare the raw unpadded_size values (not ceiled)
208
+ block_sizes.each_with_index do |block, i|
209
+ record = index[:records][i]
210
+ if block[:unpadded_size] != record[:unpadded_size]
211
+ raise FormatError,
212
+ "Index record #{i} unpadded size mismatch: block has #{block[:unpadded_size]}, " \
213
+ "index says #{record[:unpadded_size]}"
214
+ end
215
+
216
+ if block[:uncompressed_size] != record[:uncompressed_size]
217
+ raise FormatError,
218
+ "Index record #{i} uncompressed size mismatch: block has #{block[:uncompressed_size]}, " \
219
+ "index says #{record[:uncompressed_size]}"
220
+ end
221
+ end
222
+ end
223
+
224
+ index
225
+ end
226
+
227
+ # Parse and verify footer if input is seekable
228
+ #
229
+ # @param input [IO] Input stream
230
+ # @param check_type [Symbol] Expected checksum type
231
+ # @param index_size [Integer, nil] Actual index size for backward_size validation
232
+ def self.verify_footer_if_seekable(input, check_type, index_size = nil)
233
+ # allowed: caller-supplied IO; skipped on non-seekable streams
234
+ return unless input.respond_to?(:seek) && input.respond_to?(:size) && input.size
235
+
236
+ original_pos = input.pos
237
+ input.seek(-12, ::IO::SEEK_END)
238
+ footer = StreamFooterParser.parse(input)
239
+ input.pos = original_pos
240
+
241
+ # Verify check type matches
242
+ return if footer[:check_type] != check_type
243
+
244
+ # Validate backward_size against actual index size (XZ spec requirement)
245
+ # XZ spec: "If the stored value does not match the real size of the Index field,
246
+ # the decoder MUST indicate an error."
247
+ if index_size
248
+ # Convert stored_backward_size to real size: (stored + 1) * 4
249
+ real_backward_size = (footer[:backward_size] + 1) * 4
250
+ if real_backward_size != index_size
251
+ raise FormatError, "Backward size mismatch: footer indicates #{real_backward_size} bytes, " \
252
+ "but index is #{index_size} bytes"
253
+ end
254
+ end
255
+ end
256
+
257
+ # Read and verify the stream footer from the current position
258
+ #
259
+ # @param input [IO] Input stream positioned at the start of the stream footer
260
+ # @param check_type [Symbol] Expected checksum type
261
+ # @param index_size [Integer, nil] Actual index size for backward_size validation
262
+ # @raise [FormatError] If footer is invalid or doesn't match expected values
263
+ def self.read_stream_footer(input, check_type, index_size = nil)
264
+ footer = StreamFooterParser.parse(input)
265
+
266
+ # Verify check type matches
267
+ if footer[:check_type] != check_type
268
+ raise FormatError,
269
+ "Stream footer check type mismatch: expected #{check_type}, got #{footer[:check_type]}"
270
+ end
271
+
272
+ # Validate backward_size against actual index size (XZ spec requirement)
273
+ if index_size
274
+ # Convert stored_backward_size to real size: (stored + 1) * 4
275
+ real_backward_size = (footer[:backward_size] + 1) * 4
276
+ if real_backward_size != index_size
277
+ raise FormatError, "Backward size mismatch: footer indicates #{real_backward_size} bytes, " \
278
+ "but index is #{index_size} bytes"
279
+ end
280
+ end
281
+
282
+ footer
283
+ end
284
+
285
+ # Verify there's no invalid trailing data after the stream footer
286
+ #
287
+ # According to XZ spec, after a stream there can be:
288
+ # 1. Stream padding (null bytes to 4-byte boundary)
289
+ # 2. Another stream (concatenated streams)
290
+ #
291
+ # For bad-0cat-header_magic.xz style files with invalid extra data, we must reject them.
292
+ # XZ Utils rejects these with LZMA_FORMAT_ERROR when the extra data is not valid.
293
+ #
294
+ # @param input [IO] Input stream
295
+ # @raise [FormatError] If there's invalid trailing data
296
+ def self.verify_no_trailing_data(input)
297
+ # allowed: caller-supplied IO; needs byte-level positioning
298
+ return unless input.respond_to?(:pos) && input.respond_to?(:getbyte)
299
+
300
+ # Skip stream padding (null bytes)
301
+ # Stream padding must be a multiple of 4 bytes (per XZ spec)
302
+ padding_bytes = 0
303
+ loop do
304
+ byte = input.getbyte
305
+ break if byte.nil?
306
+
307
+ if byte.zero?
308
+ padding_bytes += 1
309
+ else
310
+ # Non-zero byte found - this should be a new stream or it's invalid
311
+ # Restore the byte and check if it's a valid stream header
312
+ # allowed: caller-supplied IO capability probe
313
+ input.ungetbyte(byte) if input.respond_to?(:ungetbyte)
314
+
315
+ # Stream padding must be a multiple of 4 bytes
316
+ if padding_bytes % 4 != 0
317
+ raise FormatError,
318
+ "Invalid stream padding: not a multiple of 4 bytes"
319
+ end
320
+
321
+ # Check if this looks like a valid XZ stream header
322
+ verify_or_reject_trailing_stream(input)
323
+ break
324
+ end
325
+ end
326
+
327
+ # If we reached EOF (no more data after padding), verify padding is multiple of 4
328
+ # XZ spec: "Stream Padding MUST contain only null bytes...the size of Stream
329
+ # Padding MUST be a multiple of four bytes."
330
+ if padding_bytes.positive? && padding_bytes % 4 != 0
331
+ raise FormatError,
332
+ "Invalid stream padding at EOF: #{padding_bytes} bytes (not a multiple of 4)"
333
+ end
334
+ end
335
+
336
+ # Verify that trailing data (if any) is a valid XZ stream
337
+ #
338
+ # @param input [IO] Input stream positioned at potential next stream
339
+ # @raise [FormatError] If the trailing data is not a valid XZ stream
340
+ def self.verify_or_reject_trailing_stream(input)
341
+ # Try to peek at the stream header magic
342
+ header_magic = [0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00] # XZ magic bytes
343
+
344
+ # Read the next 6 bytes to check for stream header
345
+ potential_header = []
346
+ 6.times do
347
+ byte = input.getbyte
348
+ break if byte.nil?
349
+
350
+ potential_header << byte
351
+ end
352
+
353
+ # Restore the bytes we read
354
+ # allowed: caller-supplied IO capability probe
355
+ if input.respond_to?(:ungetbyte)
356
+ potential_header.reverse_each do |b|
357
+ input.ungetbyte(b)
358
+ end
359
+ end
360
+
361
+ # If we couldn't read 6 bytes, it's EOF - that's fine
362
+ return if potential_header.size < 6
363
+
364
+ # Check if it matches XZ stream header magic
365
+ potential_header.each_with_index do |byte, i|
366
+ if byte != header_magic[i]
367
+ # Invalid trailing data - not a valid XZ stream
368
+ raise FormatError,
369
+ "Trailing data: invalid stream header (byte #{i}: 0x#{byte.to_s(16)} != 0x#{header_magic[i].to_s(16)})"
370
+ end
371
+ end
372
+
373
+ # At this point, we have a valid concatenated stream header
374
+ # We don't decode additional streams yet, but we don't reject them either
375
+ # The XZ spec allows concatenated streams, so having valid stream data after
376
+ # the first stream is OK - we just stop after decoding the first stream
377
+ end
378
+
379
+ # Validate that backward_size in footer points to valid index position
380
+ #
381
+ # This is required by the XZ spec: the backward_size must match the actual
382
+ # index size, and the index must start with the index indicator (0x00).
383
+ # Reference: /Users/mulgogi/src/external/xz/src/liblzma/common/stream_decoder.c
384
+ #
385
+ # @param input [IO] Input stream (must be seekable)
386
+ # @param file_size [Integer] Total file size in bytes
387
+ # @param index_size [Integer] Actual index size in bytes
388
+ # @raise [FormatError] If backward_size points to invalid position
389
+ def self.validate_backward_size_from_footer(input, file_size,
390
+ _index_size)
391
+ # allowed: caller-supplied IO; skipped on non-seekable streams
392
+ return unless input.respond_to?(:seek)
393
+ return if file_size.nil? || file_size.zero?
394
+
395
+ # Save current position
396
+ original_pos = input.pos
397
+
398
+ # Seek to stream footer (last 12 bytes)
399
+ input.seek(-12, ::IO::SEEK_END)
400
+
401
+ # Verify we're at the footer by checking magic bytes
402
+ footer_start = input.pos
403
+ input.seek(10, ::IO::SEEK_CUR)
404
+ magic_bytes = input.read(2)
405
+ if magic_bytes.nil? || magic_bytes.bytesize < 2 || magic_bytes != [
406
+ 0x59, 0x5A
407
+ ]
408
+ input.seek(original_pos, ::IO::SEEK_SET)
409
+ return # Not a valid footer, skip validation
410
+ end
411
+
412
+ # Seek back to footer start and then to backward_size field
413
+ input.seek(footer_start, ::IO::SEEK_SET)
414
+ input.seek(4, ::IO::SEEK_CUR)
415
+ backward_size_bytes = input.read(4)
416
+ if backward_size_bytes.nil? || backward_size_bytes.bytesize < 4
417
+ input.seek(original_pos, ::IO::SEEK_SET)
418
+ return
419
+ end
420
+
421
+ backward_size = backward_size_bytes.unpack1("V")
422
+
423
+ # Calculate real backward size: (stored + 1) * 4
424
+ real_backward_size = (backward_size + 1) * 4
425
+
426
+ # Calculate where index should start
427
+ # Stream structure: [blocks] [index + indicator] [padding] [footer 12 bytes]
428
+ # Index (including indicator) starts at: file_size - footer_size - real_backward_size
429
+ expected_index_start = file_size - 12 - real_backward_size
430
+
431
+ # Validate index start position is valid
432
+ if expected_index_start.negative?
433
+ input.seek(original_pos, ::IO::SEEK_SET)
434
+ raise FormatError,
435
+ "Invalid backward size: #{backward_size} (#{real_backward_size} bytes) " \
436
+ "would place index at negative position #{expected_index_start}"
437
+ end
438
+
439
+ if expected_index_start >= file_size
440
+ input.seek(original_pos, ::IO::SEEK_SET)
441
+ raise FormatError,
442
+ "Invalid backward size: #{backward_size} (#{real_backward_size} bytes) " \
443
+ "would place index past end of file (position #{expected_index_start}, file size #{file_size})"
444
+ end
445
+
446
+ # Check that the byte at the expected index start is the index indicator (0x00)
447
+ input.seek(expected_index_start, ::IO::SEEK_SET)
448
+ index_indicator = input.getbyte
449
+
450
+ if index_indicator.nil?
451
+ input.seek(original_pos, ::IO::SEEK_SET)
452
+ raise FormatError,
453
+ "Invalid backward size: expected index indicator (0x00) at position #{expected_index_start}, " \
454
+ "but reached end of file"
455
+ end
456
+
457
+ if index_indicator != XzConst::INDEX_INDICATOR
458
+ input.seek(original_pos, ::IO::SEEK_SET)
459
+ raise FormatError,
460
+ "Invalid backward size: expected index indicator (0x00) at position #{expected_index_start}, " \
461
+ "but found 0x#{index_indicator.to_s(16).upcase}"
462
+ end
463
+
464
+ # Restore original position
465
+ input.seek(original_pos, ::IO::SEEK_SET)
466
+ end
467
+ end
468
+ end
469
+ end
470
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+
5
+ module Omnizip
6
+ module Formats
7
+ module XzImpl
8
+ # XZ Stream encoder
9
+ # Orchestrates the complete XZ stream creation
10
+ # Based on XZ Utils stream_encoder.c
11
+ class StreamEncoder
12
+ include Omnizip::Formats::XzConst
13
+
14
+ def initialize(check_type: CHECK_CRC64, dict_size: 64 * 1024 * 1024)
15
+ @check_type = check_type
16
+ @dict_size = dict_size
17
+ @index = IndexEncoder.new
18
+ end
19
+
20
+ # Encode data into XZ format
21
+ # @param input [String, IO] Input data to compress
22
+ # @return [String] XZ-formatted compressed data
23
+ def encode(input)
24
+ output = StringIO.new
25
+ output.set_encoding(Encoding::BINARY)
26
+
27
+ # Read input data
28
+ input_data = Omnizip::IO::Source.for(input).read
29
+ input_data = input_data.dup.force_encoding(Encoding::BINARY)
30
+
31
+ # 1. Write Stream Header
32
+ header = StreamHeader.new(check_type: @check_type)
33
+ output.write(header.encode)
34
+
35
+ # 2. Encode and write Block(s)
36
+ encode_blocks(input_data, output)
37
+
38
+ # 3. Write Index
39
+ index_data = @index.encode
40
+ output.write(index_data)
41
+
42
+ # 4. Write Stream Footer
43
+ footer = StreamFooter.new(
44
+ check_type: @check_type,
45
+ backward_size: @index.size,
46
+ )
47
+ output.write(footer.encode)
48
+
49
+ output.string
50
+ end
51
+
52
+ private
53
+
54
+ def encode_blocks(data, output)
55
+ # XZ Utils behavior: If input is empty, don't create any blocks
56
+ # The stream will consist of just: Stream Header + Index + Stream Footer
57
+ return if data.empty? || data.nil?
58
+
59
+ # One block for the whole input: single-block streams are
60
+ # valid xz at any size, match the omnizip-rs reference
61
+ # (xz_container Phase-A, single block), and keep the index
62
+ # trivial. Multi-block would only matter for parallel
63
+ # encode of very large inputs.
64
+ # Include block sizes for XZ Utils compatibility
65
+ # This ensures that XZ Utils can properly decode the files
66
+ block_encoder = BlockEncoder.new(
67
+ check_type: @check_type,
68
+ dict_size: @dict_size,
69
+ include_block_sizes: true, # Include size fields for compatibility
70
+ )
71
+
72
+ block = block_encoder.encode_block(data)
73
+
74
+ # Write block header
75
+ output.write(block[:header])
76
+
77
+ # Write compressed data
78
+ output.write(block[:data])
79
+
80
+ # Write padding
81
+ output.write(block[:padding])
82
+
83
+ # Write check value
84
+ output.write(block[:check])
85
+
86
+ # Add to index
87
+ @index.add_record(
88
+ block_encoder.unpadded_size,
89
+ block_encoder.uncompressed_size,
90
+ )
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+
5
+ module Omnizip
6
+ module Formats
7
+ module XzImpl
8
+ # XZ Stream Footer encoder
9
+ # Based on XZ Utils stream_flags_encoder.c
10
+ class StreamFooter
11
+ include Omnizip::Formats::XzConst
12
+
13
+ attr_reader :check_type, :backward_size
14
+
15
+ def initialize(backward_size:, check_type: CHECK_CRC64)
16
+ @check_type = check_type
17
+ @backward_size = backward_size
18
+ end
19
+
20
+ # Encode stream footer (12 bytes total)
21
+ # Format:
22
+ # - CRC32 (4 bytes): CRC32 of backward size + stream flags
23
+ # - Backward Size (4 bytes): Size of Index in 4-byte multiples
24
+ # - Stream Flags (2 bytes): version + check type
25
+ # - Footer Magic (2 bytes): 59 5A
26
+ def encode
27
+ output = String.new(encoding: Encoding::BINARY)
28
+
29
+ # Validate backward size
30
+ unless valid_backward_size?
31
+ raise ArgumentError, "Invalid backward size: #{@backward_size}"
32
+ end
33
+
34
+ # Encode backward size (stored as (bytes / 4) - 1)
35
+ backward_encoded = (@backward_size / 4) - 1
36
+ backward_bytes = [backward_encoded].pack("V") # Little-endian uint32
37
+
38
+ # Encode stream flags
39
+ flags = encode_stream_flags
40
+
41
+ # Calculate CRC32 of backward size + flags
42
+ crc_data = backward_bytes + flags
43
+ crc = Zlib.crc32(crc_data)
44
+
45
+ # Write CRC32
46
+ output << [crc].pack("V")
47
+
48
+ # Write backward size
49
+ output << backward_bytes
50
+
51
+ # Write stream flags
52
+ output << flags
53
+
54
+ # Write footer magic
55
+ output << FOOTER_MAGIC.pack("C*")
56
+
57
+ output
58
+ end
59
+
60
+ private
61
+
62
+ def encode_stream_flags
63
+ # Stream Flags format:
64
+ # Byte 0: Reserved (must be 0x00)
65
+ # Byte 1: Check type
66
+ flags = String.new(encoding: Encoding::BINARY)
67
+ flags << "\x00" # Reserved byte
68
+ flags << [@check_type].pack("C")
69
+ flags
70
+ end
71
+
72
+ def valid_backward_size?
73
+ @backward_size.between?(BACKWARD_SIZE_MIN, BACKWARD_SIZE_MAX) &&
74
+ (@backward_size % 4).zero?
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end