omnizip 0.4.7-arm-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (632) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +32 -0
  4. data/.rubocop_todo.yml +293 -0
  5. data/CHANGELOG.md +1647 -0
  6. data/CONTEXT.md +68 -0
  7. data/COPYING +502 -0
  8. data/Gemfile +19 -0
  9. data/LICENSE +12 -0
  10. data/README.adoc +1327 -0
  11. data/Rakefile +15 -0
  12. data/TODO.refactor/00-overview.md +67 -0
  13. data/TODO.refactor/01-registry-base-class.md +81 -0
  14. data/TODO.refactor/02-error-hierarchy.md +57 -0
  15. data/TODO.refactor/03-autoload-entry-point.md +69 -0
  16. data/TODO.refactor/04-filter-base-consolidation.md +49 -0
  17. data/TODO.refactor/05-send-private-methods.md +64 -0
  18. data/TODO.refactor/06-instance-variable-access.md +69 -0
  19. data/TODO.refactor/07-respond-to-replacement.md +237 -0
  20. data/TODO.refactor/08-spec-doubles.md +27 -0
  21. data/TODO.refactor/09-cli-shared-module.md +35 -0
  22. data/TODO.refactor/10-format-detector-ocp.md +34 -0
  23. data/TODO.refactor/11-convenience-decoupling.md +38 -0
  24. data/TODO.refactor/12-thread-safety.md +8 -0
  25. data/TODO.refactor/13-lutaml-model-migration.md +158 -0
  26. data/TODO.refactor/14-add-missing-specs.md +25 -0
  27. data/benchmark/README.md +260 -0
  28. data/benchmark/benchmark_suite.rb +125 -0
  29. data/benchmark/compression_bench.rb +181 -0
  30. data/benchmark/filter_bench.rb +180 -0
  31. data/benchmark/models/benchmark_result.rb +59 -0
  32. data/benchmark/models/comparison_result.rb +69 -0
  33. data/benchmark/profile_suite.rb +167 -0
  34. data/benchmark/reporter.rb +150 -0
  35. data/benchmark/run_benchmarks.rb +66 -0
  36. data/benchmark/test_data.rb +137 -0
  37. data/config/formats/rar3_spec.yml +92 -0
  38. data/config/formats/rar5_spec.yml +102 -0
  39. data/docs/.github/workflows/docs.yml +142 -0
  40. data/docs/.gitignore +21 -0
  41. data/docs/.lychee.toml +67 -0
  42. data/docs/Gemfile +13 -0
  43. data/docs/RAR_WRITE_SUPPORT.md +26 -0
  44. data/docs/README.md +101 -0
  45. data/docs/_config.yml +112 -0
  46. data/docs/assets/logo.svg +1 -0
  47. data/docs/assets/omnizip-logo.pdf +1540 -11
  48. data/docs/comparison/feature-matrix.adoc +694 -0
  49. data/docs/comparison/index.adoc +113 -0
  50. data/docs/comparison/vs-7zip.adoc +309 -0
  51. data/docs/comparison/vs-peazip.adoc +77 -0
  52. data/docs/comparison/vs-rubyzip.adoc +342 -0
  53. data/docs/comparison/vs-winrar.adoc +100 -0
  54. data/docs/compatibility.adoc +579 -0
  55. data/docs/concepts/index.adoc +129 -0
  56. data/docs/developer/architecture.adoc +256 -0
  57. data/docs/developer/contributing.adoc +158 -0
  58. data/docs/developer/index.adoc +25 -0
  59. data/docs/developer/testing.adoc +212 -0
  60. data/docs/getting-started/basic-usage.adoc +263 -0
  61. data/docs/getting-started/index.adoc +42 -0
  62. data/docs/getting-started/installation.adoc +138 -0
  63. data/docs/getting-started/quick-start.adoc +183 -0
  64. data/docs/getting-started/your-first-archive.adoc +219 -0
  65. data/docs/guides/advanced-features/encryption.adoc +277 -0
  66. data/docs/guides/advanced-features/index.adoc +59 -0
  67. data/docs/guides/advanced-features/parallel-processing.adoc +144 -0
  68. data/docs/guides/advanced-features/progress-tracking.adoc +124 -0
  69. data/docs/guides/advanced-features/streaming.adoc +142 -0
  70. data/docs/guides/archive-formats/gzip-format.adoc +105 -0
  71. data/docs/guides/archive-formats/index.adoc +160 -0
  72. data/docs/guides/archive-formats/ole-format.adoc +151 -0
  73. data/docs/guides/archive-formats/rar-format.adoc +104 -0
  74. data/docs/guides/archive-formats/rar5.adoc +521 -0
  75. data/docs/guides/archive-formats/rpm-format.adoc +108 -0
  76. data/docs/guides/archive-formats/seven-zip-format.adoc +35 -0
  77. data/docs/guides/archive-formats/tar-format.adoc +106 -0
  78. data/docs/guides/archive-formats/xz-format.adoc +118 -0
  79. data/docs/guides/archive-formats/zip-format.adoc +35 -0
  80. data/docs/guides/compression-algorithms/bzip2.adoc +114 -0
  81. data/docs/guides/compression-algorithms/deflate.adoc +313 -0
  82. data/docs/guides/compression-algorithms/index.adoc +190 -0
  83. data/docs/guides/compression-algorithms/lzma.adoc +394 -0
  84. data/docs/guides/compression-algorithms/lzma2.adoc +244 -0
  85. data/docs/guides/compression-algorithms/ppmd.adoc +313 -0
  86. data/docs/guides/compression-algorithms/zstandard.adoc +296 -0
  87. data/docs/guides/creating-archives.adoc +348 -0
  88. data/docs/guides/extracting-archives.adoc +53 -0
  89. data/docs/guides/format-conversion.adoc +64 -0
  90. data/docs/guides/index.adoc +49 -0
  91. data/docs/guides/migration-rubyzip.adoc +217 -0
  92. data/docs/guides/parity-archives.adoc +605 -0
  93. data/docs/guides/performance-tuning.adoc +88 -0
  94. data/docs/index.adoc +228 -0
  95. data/docs/lychee.toml +67 -0
  96. data/docs/reference/api/overview.adoc +187 -0
  97. data/docs/reference/cli/compress-command.adoc +114 -0
  98. data/docs/reference/cli/overview.adoc +140 -0
  99. data/docs/reference/index.adoc +26 -0
  100. data/docs/resources/faq.adoc +185 -0
  101. data/docs/resources/quick-reference.adoc +222 -0
  102. data/docs/rust-backend.md +0 -0
  103. data/docs/troubleshooting/index.adoc +207 -0
  104. data/docs/xar_format.md +216 -0
  105. data/examples/api_comparison.rb +205 -0
  106. data/examples/deflate64_example.rb +96 -0
  107. data/examples/par2_demo.rb +121 -0
  108. data/examples/quick_start_native.rb +150 -0
  109. data/examples/quick_start_rubyzip.rb +115 -0
  110. data/examples/rubyzip_compatibility_demo.rb +194 -0
  111. data/exe/omnizip +27 -0
  112. data/lib/omnizip/algorithm.rb +176 -0
  113. data/lib/omnizip/algorithm_registry.rb +65 -0
  114. data/lib/omnizip/algorithms/.keep +0 -0
  115. data/lib/omnizip/algorithms/bzip2/bwt.rb +236 -0
  116. data/lib/omnizip/algorithms/bzip2/bz2.rb +687 -0
  117. data/lib/omnizip/algorithms/bzip2/rle.rb +146 -0
  118. data/lib/omnizip/algorithms/bzip2.rb +108 -0
  119. data/lib/omnizip/algorithms/deflate/constants.rb +28 -0
  120. data/lib/omnizip/algorithms/deflate/decoder.rb +37 -0
  121. data/lib/omnizip/algorithms/deflate/encoder.rb +45 -0
  122. data/lib/omnizip/algorithms/deflate.rb +135 -0
  123. data/lib/omnizip/algorithms/deflate64/constants.rb +45 -0
  124. data/lib/omnizip/algorithms/deflate64/decoder.rb +151 -0
  125. data/lib/omnizip/algorithms/deflate64/encoder.rb +95 -0
  126. data/lib/omnizip/algorithms/deflate64/huffman_coder.rb +352 -0
  127. data/lib/omnizip/algorithms/deflate64/lz77_encoder.rb +140 -0
  128. data/lib/omnizip/algorithms/deflate64.rb +118 -0
  129. data/lib/omnizip/algorithms/lzma/bit_model.rb +120 -0
  130. data/lib/omnizip/algorithms/lzma/constants.rb +112 -0
  131. data/lib/omnizip/algorithms/lzma/decoder.rb +146 -0
  132. data/lib/omnizip/algorithms/lzma/dictionary.rb +70 -0
  133. data/lib/omnizip/algorithms/lzma/distance_coder.rb +326 -0
  134. data/lib/omnizip/algorithms/lzma/encoder.rb +139 -0
  135. data/lib/omnizip/algorithms/lzma/length_coder.rb +172 -0
  136. data/lib/omnizip/algorithms/lzma/literal_decoder.rb +204 -0
  137. data/lib/omnizip/algorithms/lzma/literal_encoder.rb +208 -0
  138. data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +368 -0
  139. data/lib/omnizip/algorithms/lzma/lzma1_encoder.rb +105 -0
  140. data/lib/omnizip/algorithms/lzma/lzma_alone_decoder.rb +194 -0
  141. data/lib/omnizip/algorithms/lzma/lzma_state.rb +128 -0
  142. data/lib/omnizip/algorithms/lzma/match.rb +32 -0
  143. data/lib/omnizip/algorithms/lzma/match_finder.rb +233 -0
  144. data/lib/omnizip/algorithms/lzma/match_finder_config.rb +142 -0
  145. data/lib/omnizip/algorithms/lzma/match_finder_factory.rb +84 -0
  146. data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +159 -0
  147. data/lib/omnizip/algorithms/lzma/probability_models.rb +72 -0
  148. data/lib/omnizip/algorithms/lzma/range_coder.rb +82 -0
  149. data/lib/omnizip/algorithms/lzma/range_decoder.rb +274 -0
  150. data/lib/omnizip/algorithms/lzma/range_encoder.rb +202 -0
  151. data/lib/omnizip/algorithms/lzma/state.rb +127 -0
  152. data/lib/omnizip/algorithms/lzma/xz_buffered_range_encoder.rb +323 -0
  153. data/lib/omnizip/algorithms/lzma/xz_encoder.rb +420 -0
  154. data/lib/omnizip/algorithms/lzma/xz_encoder_fast.rb +640 -0
  155. data/lib/omnizip/algorithms/lzma/xz_match_finder_adapter.rb +224 -0
  156. data/lib/omnizip/algorithms/lzma/xz_price_calculator.rb +167 -0
  157. data/lib/omnizip/algorithms/lzma/xz_probability_models.rb +258 -0
  158. data/lib/omnizip/algorithms/lzma/xz_range_encoder.rb +223 -0
  159. data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +314 -0
  160. data/lib/omnizip/algorithms/lzma/xz_state.rb +108 -0
  161. data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +1311 -0
  162. data/lib/omnizip/algorithms/lzma.rb +266 -0
  163. data/lib/omnizip/algorithms/lzma2/chunk_manager.rb +180 -0
  164. data/lib/omnizip/algorithms/lzma2/constants.rb +38 -0
  165. data/lib/omnizip/algorithms/lzma2/encoder.rb +145 -0
  166. data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +165 -0
  167. data/lib/omnizip/algorithms/lzma2/properties.rb +179 -0
  168. data/lib/omnizip/algorithms/lzma2/simple_lzma2_encoder.rb +122 -0
  169. data/lib/omnizip/algorithms/lzma2/xz_encoder_adapter.rb +85 -0
  170. data/lib/omnizip/algorithms/lzma2.rb +133 -0
  171. data/lib/omnizip/algorithms/ppmd7/constants.rb +74 -0
  172. data/lib/omnizip/algorithms/ppmd7/context.rb +151 -0
  173. data/lib/omnizip/algorithms/ppmd7/decoder.rb +127 -0
  174. data/lib/omnizip/algorithms/ppmd7/encoder.rb +153 -0
  175. data/lib/omnizip/algorithms/ppmd7/model.rb +245 -0
  176. data/lib/omnizip/algorithms/ppmd7/symbol_state.rb +57 -0
  177. data/lib/omnizip/algorithms/ppmd7.rb +146 -0
  178. data/lib/omnizip/algorithms/ppmd8/constants.rb +61 -0
  179. data/lib/omnizip/algorithms/ppmd8/context.rb +32 -0
  180. data/lib/omnizip/algorithms/ppmd8/decoder.rb +104 -0
  181. data/lib/omnizip/algorithms/ppmd8/encoder.rb +135 -0
  182. data/lib/omnizip/algorithms/ppmd8/model.rb +246 -0
  183. data/lib/omnizip/algorithms/ppmd8/restoration_method.rb +78 -0
  184. data/lib/omnizip/algorithms/ppmd8.rb +122 -0
  185. data/lib/omnizip/algorithms/ppmd_base.rb +142 -0
  186. data/lib/omnizip/algorithms/registration.rb +28 -0
  187. data/lib/omnizip/algorithms/zstandard/constants.rb +158 -0
  188. data/lib/omnizip/algorithms/zstandard/decoder.rb +249 -0
  189. data/lib/omnizip/algorithms/zstandard/dictionary.rb +73 -0
  190. data/lib/omnizip/algorithms/zstandard/encoder.rb +294 -0
  191. data/lib/omnizip/algorithms/zstandard/frame/block.rb +143 -0
  192. data/lib/omnizip/algorithms/zstandard/frame/header.rb +233 -0
  193. data/lib/omnizip/algorithms/zstandard/frame.rb +33 -0
  194. data/lib/omnizip/algorithms/zstandard/fse/bitstream.rb +183 -0
  195. data/lib/omnizip/algorithms/zstandard/fse/encoder.rb +534 -0
  196. data/lib/omnizip/algorithms/zstandard/fse/interleaved.rb +92 -0
  197. data/lib/omnizip/algorithms/zstandard/fse/table.rb +188 -0
  198. data/lib/omnizip/algorithms/zstandard/fse/table_description.rb +203 -0
  199. data/lib/omnizip/algorithms/zstandard/fse.rb +53 -0
  200. data/lib/omnizip/algorithms/zstandard/huffman.rb +276 -0
  201. data/lib/omnizip/algorithms/zstandard/huffman_encoder.rb +327 -0
  202. data/lib/omnizip/algorithms/zstandard/ldm.rb +121 -0
  203. data/lib/omnizip/algorithms/zstandard/literals.rb +241 -0
  204. data/lib/omnizip/algorithms/zstandard/literals_encoder.rb +96 -0
  205. data/lib/omnizip/algorithms/zstandard/match_finder.rb +483 -0
  206. data/lib/omnizip/algorithms/zstandard/sequences.rb +350 -0
  207. data/lib/omnizip/algorithms/zstandard/sequences_encoder.rb +333 -0
  208. data/lib/omnizip/algorithms/zstandard/xxhash.rb +159 -0
  209. data/lib/omnizip/algorithms/zstandard.rb +210 -0
  210. data/lib/omnizip/algorithms.rb +35 -0
  211. data/lib/omnizip/archive/builder.rb +48 -0
  212. data/lib/omnizip/archive/reader_session.rb +67 -0
  213. data/lib/omnizip/archive.rb +86 -0
  214. data/lib/omnizip/archive_handler.rb +82 -0
  215. data/lib/omnizip/archive_handlers/cpio_handler.rb +47 -0
  216. data/lib/omnizip/archive_handlers/iso_handler.rb +54 -0
  217. data/lib/omnizip/archive_handlers/ole_handler.rb +37 -0
  218. data/lib/omnizip/archive_handlers/rar_handler.rb +96 -0
  219. data/lib/omnizip/archive_handlers/rpm_handler.rb +45 -0
  220. data/lib/omnizip/archive_handlers/seven_zip_handler.rb +89 -0
  221. data/lib/omnizip/archive_handlers/tar_handler.rb +75 -0
  222. data/lib/omnizip/archive_handlers/xar_handler.rb +49 -0
  223. data/lib/omnizip/archive_handlers/zip_handler.rb +115 -0
  224. data/lib/omnizip/archive_handlers.rb +18 -0
  225. data/lib/omnizip/backends.rb +160 -0
  226. data/lib/omnizip/buffer/memory_archive.rb +246 -0
  227. data/lib/omnizip/buffer/memory_extractor.rb +234 -0
  228. data/lib/omnizip/buffer/seven_zip_bridge.rb +171 -0
  229. data/lib/omnizip/buffer.rb +187 -0
  230. data/lib/omnizip/checksum_registry.rb +41 -0
  231. data/lib/omnizip/checksums/crc32.rb +101 -0
  232. data/lib/omnizip/checksums/crc64.rb +102 -0
  233. data/lib/omnizip/checksums/crc_base.rb +158 -0
  234. data/lib/omnizip/checksums/registration.rb +16 -0
  235. data/lib/omnizip/checksums/verifier.rb +130 -0
  236. data/lib/omnizip/checksums.rb +11 -0
  237. data/lib/omnizip/chunked/memory_manager.rb +194 -0
  238. data/lib/omnizip/chunked/reader.rb +78 -0
  239. data/lib/omnizip/chunked/writer.rb +123 -0
  240. data/lib/omnizip/chunked.rb +123 -0
  241. data/lib/omnizip/cli/output_formatter.rb +112 -0
  242. data/lib/omnizip/cli.rb +604 -0
  243. data/lib/omnizip/commands/.keep +0 -0
  244. data/lib/omnizip/commands/archive_create_command.rb +245 -0
  245. data/lib/omnizip/commands/archive_extract_command.rb +231 -0
  246. data/lib/omnizip/commands/archive_list_command.rb +204 -0
  247. data/lib/omnizip/commands/archive_repair_command.rb +129 -0
  248. data/lib/omnizip/commands/archive_verify_command.rb +115 -0
  249. data/lib/omnizip/commands/compress_command.rb +115 -0
  250. data/lib/omnizip/commands/decompress_command.rb +149 -0
  251. data/lib/omnizip/commands/list_command.rb +51 -0
  252. data/lib/omnizip/commands/metadata_command.rb +215 -0
  253. data/lib/omnizip/commands/parity_create_command.rb +114 -0
  254. data/lib/omnizip/commands/parity_repair_command.rb +120 -0
  255. data/lib/omnizip/commands/parity_verify_command.rb +122 -0
  256. data/lib/omnizip/commands/profile_list_command.rb +54 -0
  257. data/lib/omnizip/commands/profile_show_command.rb +44 -0
  258. data/lib/omnizip/commands.rb +21 -0
  259. data/lib/omnizip/convenience.rb +389 -0
  260. data/lib/omnizip/converter/conversion_registry.rb +50 -0
  261. data/lib/omnizip/converter/conversion_strategy.rb +108 -0
  262. data/lib/omnizip/converter/extract_repack_strategy.rb +51 -0
  263. data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +43 -0
  264. data/lib/omnizip/converter/zip_to_seven_zip_strategy.rb +54 -0
  265. data/lib/omnizip/converter.rb +124 -0
  266. data/lib/omnizip/crypto/aes256/cipher.rb +104 -0
  267. data/lib/omnizip/crypto/aes256/constants.rb +28 -0
  268. data/lib/omnizip/crypto/aes256/key_derivation.rb +100 -0
  269. data/lib/omnizip/crypto/aes256.rb +102 -0
  270. data/lib/omnizip/crypto.rb +11 -0
  271. data/lib/omnizip/entry.rb +44 -0
  272. data/lib/omnizip/error.rb +110 -0
  273. data/lib/omnizip/eta/exponential_smoothing_estimator.rb +95 -0
  274. data/lib/omnizip/eta/moving_average_estimator.rb +96 -0
  275. data/lib/omnizip/eta/rate_calculator.rb +104 -0
  276. data/lib/omnizip/eta/sample_history.rb +143 -0
  277. data/lib/omnizip/eta/time_estimator.rb +106 -0
  278. data/lib/omnizip/eta.rb +68 -0
  279. data/lib/omnizip/extraction/filter_chain.rb +179 -0
  280. data/lib/omnizip/extraction/glob_pattern.rb +140 -0
  281. data/lib/omnizip/extraction/pattern_matcher.rb +66 -0
  282. data/lib/omnizip/extraction/predicate_pattern.rb +52 -0
  283. data/lib/omnizip/extraction/regex_pattern.rb +50 -0
  284. data/lib/omnizip/extraction/selective_extractor.rb +247 -0
  285. data/lib/omnizip/extraction.rb +115 -0
  286. data/lib/omnizip/file_type/mime_classifier.rb +144 -0
  287. data/lib/omnizip/file_type.rb +117 -0
  288. data/lib/omnizip/filter.rb +141 -0
  289. data/lib/omnizip/filter_pipeline.rb +108 -0
  290. data/lib/omnizip/filter_registry.rb +113 -0
  291. data/lib/omnizip/filters/bcj.rb +277 -0
  292. data/lib/omnizip/filters/bcj2/constants.rb +53 -0
  293. data/lib/omnizip/filters/bcj2/decoder.rb +263 -0
  294. data/lib/omnizip/filters/bcj2/encoder.rb +58 -0
  295. data/lib/omnizip/filters/bcj2/stream_data.rb +91 -0
  296. data/lib/omnizip/filters/bcj2.rb +93 -0
  297. data/lib/omnizip/filters/bcj_arm.rb +174 -0
  298. data/lib/omnizip/filters/bcj_arm64.rb +242 -0
  299. data/lib/omnizip/filters/bcj_ia64.rb +194 -0
  300. data/lib/omnizip/filters/bcj_ppc.rb +188 -0
  301. data/lib/omnizip/filters/bcj_sparc.rb +174 -0
  302. data/lib/omnizip/filters/bcj_x86.rb +191 -0
  303. data/lib/omnizip/filters/delta.rb +195 -0
  304. data/lib/omnizip/filters/xz_delta.rb +258 -0
  305. data/lib/omnizip/filters.rb +20 -0
  306. data/lib/omnizip/format_detector.rb +166 -0
  307. data/lib/omnizip/formats/.keep +0 -0
  308. data/lib/omnizip/formats/bzip2_file.rb +159 -0
  309. data/lib/omnizip/formats/cpio/bounded_io.rb +66 -0
  310. data/lib/omnizip/formats/cpio/constants.rb +55 -0
  311. data/lib/omnizip/formats/cpio/entry.rb +389 -0
  312. data/lib/omnizip/formats/cpio/reader.rb +194 -0
  313. data/lib/omnizip/formats/cpio/writer.rb +232 -0
  314. data/lib/omnizip/formats/cpio.rb +132 -0
  315. data/lib/omnizip/formats/format_spec_loader.rb +228 -0
  316. data/lib/omnizip/formats/gzip.rb +236 -0
  317. data/lib/omnizip/formats/iso/directory_builder.rb +321 -0
  318. data/lib/omnizip/formats/iso/directory_record.rb +162 -0
  319. data/lib/omnizip/formats/iso/joliet.rb +204 -0
  320. data/lib/omnizip/formats/iso/path_table.rb +125 -0
  321. data/lib/omnizip/formats/iso/reader.rb +194 -0
  322. data/lib/omnizip/formats/iso/rock_ridge.rb +349 -0
  323. data/lib/omnizip/formats/iso/volume_builder.rb +319 -0
  324. data/lib/omnizip/formats/iso/volume_descriptor.rb +168 -0
  325. data/lib/omnizip/formats/iso/writer.rb +543 -0
  326. data/lib/omnizip/formats/iso.rb +133 -0
  327. data/lib/omnizip/formats/lzip.rb +161 -0
  328. data/lib/omnizip/formats/lzma_alone.rb +149 -0
  329. data/lib/omnizip/formats/msi/cab_extractor.rb +181 -0
  330. data/lib/omnizip/formats/msi/constants.rb +157 -0
  331. data/lib/omnizip/formats/msi/directory_resolver.rb +188 -0
  332. data/lib/omnizip/formats/msi/entry.rb +134 -0
  333. data/lib/omnizip/formats/msi/reader.rb +301 -0
  334. data/lib/omnizip/formats/msi/string_pool.rb +246 -0
  335. data/lib/omnizip/formats/msi/table_parser.rb +244 -0
  336. data/lib/omnizip/formats/msi.rb +73 -0
  337. data/lib/omnizip/formats/ole/allocation_table.rb +263 -0
  338. data/lib/omnizip/formats/ole/constants.rb +61 -0
  339. data/lib/omnizip/formats/ole/dirent.rb +439 -0
  340. data/lib/omnizip/formats/ole/header.rb +199 -0
  341. data/lib/omnizip/formats/ole/ranges_io.rb +347 -0
  342. data/lib/omnizip/formats/ole/storage.rb +482 -0
  343. data/lib/omnizip/formats/ole/types/variant.rb +338 -0
  344. data/lib/omnizip/formats/ole.rb +143 -0
  345. data/lib/omnizip/formats/rar/archive_repairer.rb +240 -0
  346. data/lib/omnizip/formats/rar/archive_verifier.rb +192 -0
  347. data/lib/omnizip/formats/rar/block_parser.rb +282 -0
  348. data/lib/omnizip/formats/rar/compression/bit_stream.rb +180 -0
  349. data/lib/omnizip/formats/rar/compression/dispatcher.rb +210 -0
  350. data/lib/omnizip/formats/rar/compression/lz77_huffman/decoder.rb +212 -0
  351. data/lib/omnizip/formats/rar/compression/lz77_huffman/encoder.rb +154 -0
  352. data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_builder.rb +217 -0
  353. data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_coder.rb +189 -0
  354. data/lib/omnizip/formats/rar/compression/lz77_huffman/match_finder.rb +135 -0
  355. data/lib/omnizip/formats/rar/compression/lz77_huffman/sliding_window.rb +165 -0
  356. data/lib/omnizip/formats/rar/compression/lz77_huffman.rb +25 -0
  357. data/lib/omnizip/formats/rar/compression/ppmd/context.rb +103 -0
  358. data/lib/omnizip/formats/rar/compression/ppmd/decoder.rb +238 -0
  359. data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +250 -0
  360. data/lib/omnizip/formats/rar/compression/ppmd.rb +16 -0
  361. data/lib/omnizip/formats/rar/compression.rb +17 -0
  362. data/lib/omnizip/formats/rar/compression_method_registry.rb +106 -0
  363. data/lib/omnizip/formats/rar/constants.rb +88 -0
  364. data/lib/omnizip/formats/rar/decompressor.rb +316 -0
  365. data/lib/omnizip/formats/rar/external_writer.rb +311 -0
  366. data/lib/omnizip/formats/rar/header.rb +209 -0
  367. data/lib/omnizip/formats/rar/license_validator.rb +111 -0
  368. data/lib/omnizip/formats/rar/models/rar_archive.rb +77 -0
  369. data/lib/omnizip/formats/rar/models/rar_entry.rb +65 -0
  370. data/lib/omnizip/formats/rar/models/rar_volume.rb +56 -0
  371. data/lib/omnizip/formats/rar/models.rb +14 -0
  372. data/lib/omnizip/formats/rar/parity_handler.rb +299 -0
  373. data/lib/omnizip/formats/rar/rar5/compression/lzma.rb +200 -0
  374. data/lib/omnizip/formats/rar/rar5/compression/lzss.rb +584 -0
  375. data/lib/omnizip/formats/rar/rar5/compression/store.rb +60 -0
  376. data/lib/omnizip/formats/rar/rar5/crc32.rb +39 -0
  377. data/lib/omnizip/formats/rar/rar5/encryption/aes256_cbc.rb +96 -0
  378. data/lib/omnizip/formats/rar/rar5/encryption/encryption_header.rb +114 -0
  379. data/lib/omnizip/formats/rar/rar5/encryption/encryption_manager.rb +162 -0
  380. data/lib/omnizip/formats/rar/rar5/encryption/key_derivation.rb +97 -0
  381. data/lib/omnizip/formats/rar/rar5/header.rb +208 -0
  382. data/lib/omnizip/formats/rar/rar5/models/encryption_options.rb +74 -0
  383. data/lib/omnizip/formats/rar/rar5/models/recovery_options.rb +63 -0
  384. data/lib/omnizip/formats/rar/rar5/models/solid_options.rb +63 -0
  385. data/lib/omnizip/formats/rar/rar5/models/volume_options.rb +74 -0
  386. data/lib/omnizip/formats/rar/rar5/multi_volume/ARCHITECTURE.md +290 -0
  387. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_manager.rb +260 -0
  388. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_splitter.rb +155 -0
  389. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_writer.rb +176 -0
  390. data/lib/omnizip/formats/rar/rar5/solid/solid_encoder.rb +108 -0
  391. data/lib/omnizip/formats/rar/rar5/solid/solid_manager.rb +139 -0
  392. data/lib/omnizip/formats/rar/rar5/solid/solid_stream.rb +121 -0
  393. data/lib/omnizip/formats/rar/rar5/vint.rb +53 -0
  394. data/lib/omnizip/formats/rar/rar5/writer.rb +540 -0
  395. data/lib/omnizip/formats/rar/rar_format_base.rb +239 -0
  396. data/lib/omnizip/formats/rar/reader.rb +377 -0
  397. data/lib/omnizip/formats/rar/recovery_record.rb +245 -0
  398. data/lib/omnizip/formats/rar/volume_manager.rb +166 -0
  399. data/lib/omnizip/formats/rar/writer.rb +437 -0
  400. data/lib/omnizip/formats/rar.rb +193 -0
  401. data/lib/omnizip/formats/rar3/compressor.rb +71 -0
  402. data/lib/omnizip/formats/rar3/decompressor.rb +64 -0
  403. data/lib/omnizip/formats/rar3/reader.rb +107 -0
  404. data/lib/omnizip/formats/rar3/writer.rb +220 -0
  405. data/lib/omnizip/formats/rar3.rb +15 -0
  406. data/lib/omnizip/formats/rar5/compressor.rb +71 -0
  407. data/lib/omnizip/formats/rar5/decompressor.rb +64 -0
  408. data/lib/omnizip/formats/rar5/reader.rb +130 -0
  409. data/lib/omnizip/formats/rar5/writer.rb +65 -0
  410. data/lib/omnizip/formats/rar5.rb +15 -0
  411. data/lib/omnizip/formats/rpm/constants.rb +58 -0
  412. data/lib/omnizip/formats/rpm/entry.rb +106 -0
  413. data/lib/omnizip/formats/rpm/header.rb +106 -0
  414. data/lib/omnizip/formats/rpm/lead.rb +132 -0
  415. data/lib/omnizip/formats/rpm/tag.rb +228 -0
  416. data/lib/omnizip/formats/rpm/writer.rb +652 -0
  417. data/lib/omnizip/formats/rpm.rb +458 -0
  418. data/lib/omnizip/formats/seven_zip/bcj2_stream_decompressor.rb +274 -0
  419. data/lib/omnizip/formats/seven_zip/coder_chain.rb +169 -0
  420. data/lib/omnizip/formats/seven_zip/constants.rb +126 -0
  421. data/lib/omnizip/formats/seven_zip/encoded_header.rb +110 -0
  422. data/lib/omnizip/formats/seven_zip/encrypted_header.rb +139 -0
  423. data/lib/omnizip/formats/seven_zip/file_collector.rb +143 -0
  424. data/lib/omnizip/formats/seven_zip/header.rb +102 -0
  425. data/lib/omnizip/formats/seven_zip/header_encryptor.rb +132 -0
  426. data/lib/omnizip/formats/seven_zip/header_writer.rb +425 -0
  427. data/lib/omnizip/formats/seven_zip/models/coder_info.rb +30 -0
  428. data/lib/omnizip/formats/seven_zip/models/file_entry.rb +65 -0
  429. data/lib/omnizip/formats/seven_zip/models/folder.rb +67 -0
  430. data/lib/omnizip/formats/seven_zip/models/stream_info.rb +42 -0
  431. data/lib/omnizip/formats/seven_zip/models.rb +20 -0
  432. data/lib/omnizip/formats/seven_zip/parser.rb +671 -0
  433. data/lib/omnizip/formats/seven_zip/reader.rb +626 -0
  434. data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +611 -0
  435. data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +308 -0
  436. data/lib/omnizip/formats/seven_zip/stream_compressor.rb +151 -0
  437. data/lib/omnizip/formats/seven_zip/stream_decompressor.rb +166 -0
  438. data/lib/omnizip/formats/seven_zip/writer.rb +801 -0
  439. data/lib/omnizip/formats/seven_zip.rb +179 -0
  440. data/lib/omnizip/formats/tar/constants.rb +73 -0
  441. data/lib/omnizip/formats/tar/entry.rb +98 -0
  442. data/lib/omnizip/formats/tar/header.rb +206 -0
  443. data/lib/omnizip/formats/tar/reader.rb +119 -0
  444. data/lib/omnizip/formats/tar/writer.rb +212 -0
  445. data/lib/omnizip/formats/tar.rb +74 -0
  446. data/lib/omnizip/formats/xar/constants.rb +119 -0
  447. data/lib/omnizip/formats/xar/entry.rb +268 -0
  448. data/lib/omnizip/formats/xar/header.rb +223 -0
  449. data/lib/omnizip/formats/xar/reader.rb +400 -0
  450. data/lib/omnizip/formats/xar/toc.rb +570 -0
  451. data/lib/omnizip/formats/xar/writer.rb +461 -0
  452. data/lib/omnizip/formats/xar.rb +145 -0
  453. data/lib/omnizip/formats/xz/reader.rb +121 -0
  454. data/lib/omnizip/formats/xz/writer.rb +416 -0
  455. data/lib/omnizip/formats/xz.rb +210 -0
  456. data/lib/omnizip/formats/xz_const.rb +49 -0
  457. data/lib/omnizip/formats/xz_impl/block_decoder.rb +761 -0
  458. data/lib/omnizip/formats/xz_impl/block_encoder.rb +304 -0
  459. data/lib/omnizip/formats/xz_impl/block_header.rb +210 -0
  460. data/lib/omnizip/formats/xz_impl/block_header_parser.rb +182 -0
  461. data/lib/omnizip/formats/xz_impl/index_decoder.rb +174 -0
  462. data/lib/omnizip/formats/xz_impl/index_encoder.rb +120 -0
  463. data/lib/omnizip/formats/xz_impl/stream_decoder.rb +470 -0
  464. data/lib/omnizip/formats/xz_impl/stream_encoder.rb +95 -0
  465. data/lib/omnizip/formats/xz_impl/stream_footer.rb +79 -0
  466. data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +115 -0
  467. data/lib/omnizip/formats/xz_impl/stream_header.rb +53 -0
  468. data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +106 -0
  469. data/lib/omnizip/formats/xz_impl/vli.rb +128 -0
  470. data/lib/omnizip/formats/xz_impl.rb +52 -0
  471. data/lib/omnizip/formats/zip/central_directory_header.rb +223 -0
  472. data/lib/omnizip/formats/zip/constants.rb +69 -0
  473. data/lib/omnizip/formats/zip/end_of_central_directory.rb +132 -0
  474. data/lib/omnizip/formats/zip/local_file_header.rb +136 -0
  475. data/lib/omnizip/formats/zip/reader.rb +376 -0
  476. data/lib/omnizip/formats/zip/unix_extra_field.rb +153 -0
  477. data/lib/omnizip/formats/zip/writer.rb +489 -0
  478. data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +102 -0
  479. data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +64 -0
  480. data/lib/omnizip/formats/zip/zip64_extra_field.rb +112 -0
  481. data/lib/omnizip/formats/zip.rb +50 -0
  482. data/lib/omnizip/formats.rb +29 -0
  483. data/lib/omnizip/implementations/base/lzma2_decoder_base.rb +75 -0
  484. data/lib/omnizip/implementations/base/lzma2_encoder_base.rb +128 -0
  485. data/lib/omnizip/implementations/base/lzma_decoder_base.rb +83 -0
  486. data/lib/omnizip/implementations/base/lzma_encoder_base.rb +108 -0
  487. data/lib/omnizip/implementations/base/state_machine_base.rb +182 -0
  488. data/lib/omnizip/implementations/base.rb +19 -0
  489. data/lib/omnizip/implementations/rust/archive.rb +93 -0
  490. data/lib/omnizip/implementations/rust/library.rb +221 -0
  491. data/lib/omnizip/implementations/rust.rb +26 -0
  492. data/lib/omnizip/implementations/seven_zip/lzma/decoder.rb +428 -0
  493. data/lib/omnizip/implementations/seven_zip/lzma/encoder.rb +456 -0
  494. data/lib/omnizip/implementations/seven_zip/lzma/match_finder.rb +285 -0
  495. data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +197 -0
  496. data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +194 -0
  497. data/lib/omnizip/implementations/seven_zip/lzma/state_machine.rb +139 -0
  498. data/lib/omnizip/implementations/seven_zip/lzma.rb +21 -0
  499. data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +56 -0
  500. data/lib/omnizip/implementations/seven_zip/lzma2.rb +12 -0
  501. data/lib/omnizip/implementations/seven_zip.rb +11 -0
  502. data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +547 -0
  503. data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +748 -0
  504. data/lib/omnizip/implementations/xz_utils/lzma2.rb +13 -0
  505. data/lib/omnizip/implementations/xz_utils.rb +10 -0
  506. data/lib/omnizip/implementations.rb +25 -0
  507. data/lib/omnizip/io/buffered_input.rb +147 -0
  508. data/lib/omnizip/io/buffered_output.rb +106 -0
  509. data/lib/omnizip/io/source.rb +143 -0
  510. data/lib/omnizip/io/stream_manager.rb +117 -0
  511. data/lib/omnizip/io.rb +15 -0
  512. data/lib/omnizip/link_handler/hard_link.rb +79 -0
  513. data/lib/omnizip/link_handler/symbolic_link.rb +74 -0
  514. data/lib/omnizip/link_handler.rb +129 -0
  515. data/lib/omnizip/metadata/archive_metadata.rb +114 -0
  516. data/lib/omnizip/metadata/entry_metadata.rb +153 -0
  517. data/lib/omnizip/metadata/metadata_editor.rb +169 -0
  518. data/lib/omnizip/metadata/metadata_registry.rb +71 -0
  519. data/lib/omnizip/metadata/metadata_validator.rb +101 -0
  520. data/lib/omnizip/metadata.rb +58 -0
  521. data/lib/omnizip/models/.keep +0 -0
  522. data/lib/omnizip/models/algorithm_metadata.rb +41 -0
  523. data/lib/omnizip/models/attribute_apply.rb +40 -0
  524. data/lib/omnizip/models/compression_options.rb +53 -0
  525. data/lib/omnizip/models/conversion_options.rb +79 -0
  526. data/lib/omnizip/models/conversion_result.rb +141 -0
  527. data/lib/omnizip/models/eta_result.rb +45 -0
  528. data/lib/omnizip/models/extraction_rule.rb +115 -0
  529. data/lib/omnizip/models/filter_chain.rb +142 -0
  530. data/lib/omnizip/models/filter_config.rb +148 -0
  531. data/lib/omnizip/models/match_result.rb +141 -0
  532. data/lib/omnizip/models/optimization_suggestion.rb +95 -0
  533. data/lib/omnizip/models/parallel_options.rb +116 -0
  534. data/lib/omnizip/models/performance_result.rb +85 -0
  535. data/lib/omnizip/models/profile_report.rb +116 -0
  536. data/lib/omnizip/models/progress_options.rb +39 -0
  537. data/lib/omnizip/models/split_options.rb +116 -0
  538. data/lib/omnizip/optimization_registry.rb +45 -0
  539. data/lib/omnizip/parallel/engine.rb +52 -0
  540. data/lib/omnizip/parallel/job_queue.rb +209 -0
  541. data/lib/omnizip/parallel/job_scheduler.rb +225 -0
  542. data/lib/omnizip/parallel/parallel_compressor.rb +326 -0
  543. data/lib/omnizip/parallel/parallel_extractor.rb +238 -0
  544. data/lib/omnizip/parallel/thread_pool.rb +60 -0
  545. data/lib/omnizip/parallel/worker_pool.rb +227 -0
  546. data/lib/omnizip/parallel.rb +156 -0
  547. data/lib/omnizip/parity/chunked_block_processor.rb +194 -0
  548. data/lib/omnizip/parity/galois16.rb +145 -0
  549. data/lib/omnizip/parity/models/creator_packet.rb +71 -0
  550. data/lib/omnizip/parity/models/file_description_packet.rb +131 -0
  551. data/lib/omnizip/parity/models/ifsc_packet.rb +111 -0
  552. data/lib/omnizip/parity/models/main_packet.rb +126 -0
  553. data/lib/omnizip/parity/models/packet.rb +156 -0
  554. data/lib/omnizip/parity/models/packet_registry.rb +102 -0
  555. data/lib/omnizip/parity/models/recovery_slice_packet.rb +76 -0
  556. data/lib/omnizip/parity/models.rb +27 -0
  557. data/lib/omnizip/parity/par2_creator.rb +496 -0
  558. data/lib/omnizip/parity/par2_repairer.rb +409 -0
  559. data/lib/omnizip/parity/par2_verifier.rb +373 -0
  560. data/lib/omnizip/parity/par2cmdline_algorithm.rb +108 -0
  561. data/lib/omnizip/parity/par2cmdline_coefficients.rb +78 -0
  562. data/lib/omnizip/parity/reed_solomon_decoder.rb +263 -0
  563. data/lib/omnizip/parity/reed_solomon_encoder.rb +108 -0
  564. data/lib/omnizip/parity/reed_solomon_matrix.rb +339 -0
  565. data/lib/omnizip/parity.rb +207 -0
  566. data/lib/omnizip/password/encryption_registry.rb +26 -0
  567. data/lib/omnizip/password/encryption_strategy.rb +96 -0
  568. data/lib/omnizip/password/password_validator.rb +129 -0
  569. data/lib/omnizip/password/winzip_aes_strategy.rb +190 -0
  570. data/lib/omnizip/password/zip_crypto_strategy.rb +135 -0
  571. data/lib/omnizip/password.rb +88 -0
  572. data/lib/omnizip/pipe/stream_compressor.rb +124 -0
  573. data/lib/omnizip/pipe/stream_decompressor.rb +173 -0
  574. data/lib/omnizip/pipe.rb +120 -0
  575. data/lib/omnizip/platform/ntfs_streams.rb +200 -0
  576. data/lib/omnizip/platform.rb +189 -0
  577. data/lib/omnizip/profile/archive_profile.rb +36 -0
  578. data/lib/omnizip/profile/balanced_profile.rb +31 -0
  579. data/lib/omnizip/profile/binary_profile.rb +33 -0
  580. data/lib/omnizip/profile/compression_profile.rb +157 -0
  581. data/lib/omnizip/profile/custom_profile.rb +155 -0
  582. data/lib/omnizip/profile/fast_profile.rb +31 -0
  583. data/lib/omnizip/profile/maximum_profile.rb +31 -0
  584. data/lib/omnizip/profile/profile_detector.rb +108 -0
  585. data/lib/omnizip/profile/profile_registry.rb +160 -0
  586. data/lib/omnizip/profile/text_profile.rb +33 -0
  587. data/lib/omnizip/profile.rb +261 -0
  588. data/lib/omnizip/profiler/memory_profiler.rb +66 -0
  589. data/lib/omnizip/profiler/method_profiler.rb +49 -0
  590. data/lib/omnizip/profiler/report_generator.rb +158 -0
  591. data/lib/omnizip/profiler.rb +192 -0
  592. data/lib/omnizip/progress/callback_reporter.rb +34 -0
  593. data/lib/omnizip/progress/console_reporter.rb +59 -0
  594. data/lib/omnizip/progress/log_reporter.rb +89 -0
  595. data/lib/omnizip/progress/operation_progress.rb +118 -0
  596. data/lib/omnizip/progress/progress_bar.rb +156 -0
  597. data/lib/omnizip/progress/progress_reporter.rb +40 -0
  598. data/lib/omnizip/progress/progress_tracker.rb +215 -0
  599. data/lib/omnizip/progress/silent_reporter.rb +22 -0
  600. data/lib/omnizip/progress.rb +131 -0
  601. data/lib/omnizip/registry.rb +111 -0
  602. data/lib/omnizip/rubyzip_compat.rb +61 -0
  603. data/lib/omnizip/temp/safe_extract.rb +124 -0
  604. data/lib/omnizip/temp/temp_file.rb +123 -0
  605. data/lib/omnizip/temp/temp_file_pool.rb +109 -0
  606. data/lib/omnizip/temp.rb +184 -0
  607. data/lib/omnizip/version.rb +5 -0
  608. data/lib/omnizip/zip/entry.rb +160 -0
  609. data/lib/omnizip/zip/file.rb +485 -0
  610. data/lib/omnizip/zip/input_stream.rb +270 -0
  611. data/lib/omnizip/zip/output_stream.rb +320 -0
  612. data/lib/omnizip/zip.rb +23 -0
  613. data/lib/omnizip.rb +157 -0
  614. data/lib/tasks/msi_test_files.rake +66 -0
  615. data/lib/tasks/platform_gems.rake +147 -0
  616. data/lib/tasks/rust_backend.rake +48 -0
  617. data/readme-docs/advanced-features.adoc +514 -0
  618. data/readme-docs/api-usage.adoc +445 -0
  619. data/readme-docs/architecture.adoc +450 -0
  620. data/readme-docs/archive-formats.adoc +479 -0
  621. data/readme-docs/cli-usage.adoc +222 -0
  622. data/readme-docs/compression-algorithms.adoc +446 -0
  623. data/readme-docs/compression-profiles.adoc +247 -0
  624. data/readme-docs/encryption-checksums.adoc +328 -0
  625. data/readme-docs/format-converter.adoc +316 -0
  626. data/readme-docs/installation.adoc +228 -0
  627. data/readme-docs/par2-archives.adoc +608 -0
  628. data/readme-docs/performance-profiler.adoc +389 -0
  629. data/readme-docs/preprocessing-filters.adoc +280 -0
  630. data/vendor/libomnizip_ffi.so +0 -0
  631. data/xz-file-format-1.2.1.txt +1174 -0
  632. metadata +768 -0
@@ -0,0 +1,801 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+
5
+ module Omnizip
6
+ module Formats
7
+ module SevenZip
8
+ # .7z archive writer - 7-Zip compatible implementation
9
+ #
10
+ # Creates archives that are fully compatible with official 7-Zip (7zz command).
11
+ #
12
+ # Archive structure:
13
+ # - Start Header (32 bytes)
14
+ # - LZMA2 compressed file data
15
+ # - UNCOMPRESSED Next Header metadata (properties: kHeader, kPackInfo, etc.)
16
+ # - Metadata footer (filename, attributes, timestamps)
17
+ class Writer
18
+ include Omnizip::Formats::SevenZip::Constants
19
+
20
+ # Constants for array literals used in loops (RuboCop Performance/CollectionLiteralInLoop)
21
+ COPY_MAIN_BYTE = [0x01].pack("C").freeze
22
+ COPY_METHOD_ID = [0x00].pack("C").freeze
23
+ NULL_TERMINATOR = [0x00, 0x00].pack("CC").freeze
24
+ FILE_ATTRIBUTE_ARCHIVE = [0x20].pack("V").freeze
25
+
26
+ attr_reader :output_path, :options, :entries
27
+
28
+ def initialize(output_path, options = {})
29
+ @output_path = output_path
30
+ @options = {
31
+ algorithm: :lzma2,
32
+ level: 5,
33
+ dict_size: 8 * 1024 * 1024, # 8MB default dictionary
34
+ solid: true, # Solid mode for LZMA2 compression
35
+ filters: [],
36
+ encrypt_headers: false,
37
+ }.merge(options)
38
+ @collector = FileCollector.new
39
+ @entries = []
40
+
41
+ # Preprocessing filters are decode-side only: the write
42
+ # path does not apply them, so say so instead of silently
43
+ # ignoring the option
44
+ filters = @options[:filters]
45
+ return unless filters && !filters.empty?
46
+
47
+ warn "omnizip: the 7z writer does not apply filters " \
48
+ "(#{Array(filters).join(', ')}); writing unfiltered data. " \
49
+ "Filters apply when reading archives that contain them."
50
+ end
51
+
52
+ def add_file(file_path, archive_path = nil)
53
+ @collector.add_path(file_path, archive_path: archive_path,
54
+ recursive: false)
55
+ end
56
+
57
+ def add_directory(dir_path, recursive = true)
58
+ @collector.add_path(dir_path, recursive: recursive)
59
+ end
60
+
61
+ def add_files(pattern)
62
+ @collector.add_glob(pattern)
63
+ end
64
+
65
+ def add_data(archive_path, data, options = {})
66
+ data_str = data.is_a?(String) ? data : data.read
67
+
68
+ entry = Models::FileEntry.new
69
+ entry.name = archive_path
70
+ entry.source_path = nil
71
+ entry.size = data_str.bytesize
72
+ entry.mtime = Time.now
73
+ # Zero-length data has no stream: readers identify it as an
74
+ # empty file through kEmptyStream + kEmptyFile.
75
+ entry.has_stream = data_str.bytesize.positive?
76
+ entry.inline_data = data_str
77
+ entry.compression_options = options
78
+ @entries << entry
79
+ end
80
+
81
+ # Add an explicit directory entry with no data or stream
82
+ #
83
+ # @param archive_path [String] Directory path inside the
84
+ # archive (trailing slash optional)
85
+ # @return [self]
86
+ def add_directory_entry(archive_path)
87
+ entry = Models::FileEntry.new
88
+ # Trailing slash is preserved: explicit directory entries
89
+ # carry it ("testdir/"), matching the collector's tree walk
90
+ entry.name = archive_path
91
+ entry.source_path = nil
92
+ entry.size = 0
93
+ entry.is_dir = true
94
+ entry.has_stream = false
95
+ entry.mtime = Time.now
96
+ @entries << entry
97
+ self
98
+ end
99
+
100
+ def write
101
+ # Collect any files from the collector (if add_file/add_directory was used)
102
+ collected_entries = @collector.collect_files
103
+ # Merge with entries added via add_data
104
+ @entries.concat(collected_entries)
105
+
106
+ # Check if split archive requested
107
+ if @options[:volume_size]
108
+ write_split_archive
109
+ else
110
+ File.open(@output_path, "wb") do |io|
111
+ write_archive(io)
112
+ end
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ # Write split archive (delegates to SplitArchiveWriter)
119
+ def write_split_archive
120
+ split_options = Omnizip::Models::SplitOptions.new
121
+ split_options.volume_size = @options[:volume_size]
122
+
123
+ writer = SplitArchiveWriter.new(@output_path, @options, split_options)
124
+
125
+ # Add all collected files
126
+ @entries.each do |entry|
127
+ if entry.directory?
128
+ # Directories are already in entries
129
+ next
130
+ elsif entry.source_path
131
+ writer.add_file(entry.source_path, entry.name)
132
+ end
133
+ end
134
+
135
+ writer.write
136
+ @entries = writer.entries
137
+ end
138
+
139
+ def write_archive(io)
140
+ # Reserve space for start header
141
+ io.write("\0" * START_HEADER_SIZE)
142
+
143
+ # Step 1: Collect file data
144
+ file_data = collect_file_data
145
+
146
+ # Step 2: Build compressed data based on mode
147
+ if @options[:solid]
148
+ # Solid mode: compress all data together with LZMA2
149
+ packed_data, packed_sizes = build_solid_packed_data(file_data)
150
+ else
151
+ # Non-solid mode: each file stored separately (COPY method - no compression)
152
+ packed_data, packed_sizes = build_non_solid_packed_data(file_data)
153
+ end
154
+
155
+ # Step 3: Build Next Header properties
156
+ # This includes kHeader, MAIN_STREAMS_INFO, FILES_INFO, etc.
157
+ next_header_data = build_next_header_properties(file_data,
158
+ packed_sizes)
159
+
160
+ # Step 4: Write the complete data section
161
+ # Note: CRC is stored in StartHeader, NOT appended to Next Header
162
+ io.write(packed_data) # Packed file data
163
+ io.write(next_header_data) # Next Header (CRC is in StartHeader)
164
+
165
+ # Step 5: Write Start Header
166
+ # Next Header starts after the packed data
167
+ # The offset is RELATIVE to the END of the StartHeader (byte 32)
168
+ next_header_offset = packed_data.bytesize
169
+
170
+ # Next Header size is the size of the Next Header data WITHOUT the CRC32
171
+ # (CRC32 is appended after the header data, not included in size)
172
+ next_header_size = next_header_data.bytesize
173
+
174
+ write_start_header(io, next_header_offset, next_header_size,
175
+ next_header_data)
176
+ end
177
+
178
+ # Build packed data for solid mode (LZMA2 compression)
179
+ def build_solid_packed_data(file_data)
180
+ lzma2_chunk, compressed_size = build_lzma2_compressed_chunk(file_data[:data])
181
+ [lzma2_chunk, [compressed_size]]
182
+ end
183
+
184
+ # Build packed data for non-solid mode: every file is its own
185
+ # LZMA2 stream. Empty files keep a zero-size pack stream.
186
+ def build_non_solid_packed_data(file_data)
187
+ packed_data = String.new(encoding: Encoding::BINARY)
188
+ packed_sizes = []
189
+
190
+ file_data[:streams].each do |stream|
191
+ if stream[:size].positive?
192
+ compressed = compress_with_lzma2(stream[:data])
193
+ packed_data << compressed
194
+ packed_sizes << compressed.bytesize
195
+ else
196
+ packed_sizes << 0
197
+ end
198
+ end
199
+
200
+ [packed_data, packed_sizes]
201
+ end
202
+
203
+ def collect_file_data
204
+ files_with_data = @entries.select(&:has_stream?)
205
+
206
+ if @options[:solid]
207
+ # Solid mode: combine all files into one stream
208
+ combined = String.new(encoding: "BINARY")
209
+ total_size = 0
210
+
211
+ files_with_data.each do |entry|
212
+ data = entry.inline_data || File.binread(entry.source_path)
213
+ combined << data
214
+ total_size += data.bytesize
215
+
216
+ crc = Omnizip::Checksums::Crc32.new
217
+ crc.update(data)
218
+ entry.crc = crc.finalize
219
+ entry.size = data.bytesize
220
+ end
221
+
222
+ { data: combined, total_size: total_size,
223
+ streams: [{ data: combined, size: total_size }] }
224
+ else
225
+ # Non-solid mode: each file gets its own stream
226
+ streams = []
227
+ total_size = 0
228
+
229
+ files_with_data.each do |entry|
230
+ data = entry.inline_data || File.binread(entry.source_path)
231
+
232
+ crc = Omnizip::Checksums::Crc32.new
233
+ crc.update(data)
234
+ entry.crc = crc.finalize
235
+ entry.size = data.bytesize
236
+
237
+ streams << { data: data, size: data.bytesize }
238
+ total_size += data.bytesize
239
+ end
240
+
241
+ # Combine all data for writing
242
+ combined = streams.map { |s| s[:data] }.join
243
+ { data: combined, total_size: total_size, streams: streams }
244
+ end
245
+ end
246
+
247
+ def build_lzma2_compressed_chunk(data)
248
+ return ["", 0] if data.nil? || data.empty?
249
+
250
+ # Use actual LZMA2 compression via 7-Zip SDK encoder
251
+ compressed = compress_with_lzma2(data)
252
+ [compressed, compressed.bytesize]
253
+ end
254
+
255
+ def build_next_header_properties(file_data, packed_sizes)
256
+ metadata = String.new(encoding: "BINARY")
257
+ unpack_size = file_data[:total_size]
258
+ num_files = @entries.size
259
+ solid = @options[:solid]
260
+
261
+ # kHeader property (0x01)
262
+ metadata << [PropertyId::HEADER].pack("C")
263
+
264
+ # kMainStreamsInfo property (0x04) - WRAPPER for stream info
265
+ metadata << [PropertyId::MAIN_STREAMS_INFO].pack("C")
266
+
267
+ if solid
268
+ # Solid mode: one pack stream, one folder
269
+ # packed_sizes is a single-element array with compressed size
270
+ compressed_size = packed_sizes.first
271
+ build_solid_streams_info(metadata, unpack_size, compressed_size,
272
+ num_files)
273
+ else
274
+ # Non-solid mode: one pack stream per file, one folder per file
275
+ build_non_solid_streams_info(metadata, file_data[:streams],
276
+ packed_sizes)
277
+ end
278
+
279
+ # kEnd for MainStreamsInfo
280
+ metadata << [PropertyId::K_END].pack("C")
281
+
282
+ # FILES_INFO section
283
+ build_files_info(metadata)
284
+
285
+ # kEnd for Header (closes the entire Next Header)
286
+ metadata << [PropertyId::K_END].pack("C")
287
+
288
+ # Encrypt headers if requested
289
+ if @options[:encrypt_headers]
290
+ metadata = encrypt_header(metadata)
291
+ end
292
+
293
+ metadata
294
+ end
295
+
296
+ # Encrypt header data
297
+ #
298
+ # @param header_data [String] Unencrypted header
299
+ # @return [String] Encrypted header with metadata
300
+ def encrypt_header(header_data)
301
+ unless @options[:password]
302
+ raise Omnizip::PasswordError, "Password required for header encryption"
303
+ end
304
+
305
+ encryptor = HeaderEncryptor.new(@options[:password])
306
+ result = encryptor.encrypt(header_data)
307
+
308
+ # Create encrypted header structure
309
+ encrypted_header = EncryptedHeader.new(
310
+ encrypted_data: result[:data],
311
+ salt: result[:salt],
312
+ iv: result[:iv],
313
+ original_size: result[:size],
314
+ )
315
+
316
+ encrypted_header.to_binary
317
+ end
318
+
319
+ def build_solid_streams_info(metadata, unpack_size, compressed_size,
320
+ _num_files)
321
+ # kPackInfo property (0x06)
322
+ metadata << [PropertyId::PACK_INFO].pack("C")
323
+ metadata << write_number(0) # Pack position
324
+ metadata << write_number(1) # Number of pack streams
325
+
326
+ # kSize property
327
+ metadata << [PropertyId::SIZE].pack("C")
328
+ metadata << write_number(compressed_size)
329
+
330
+ # kEnd for PackInfo
331
+ metadata << [PropertyId::K_END].pack("C")
332
+
333
+ # kUnpackInfo property (0x07)
334
+ metadata << [PropertyId::UNPACK_INFO].pack("C")
335
+
336
+ # kFolder property (0x0B)
337
+ metadata << [PropertyId::FOLDER].pack("C")
338
+ metadata << write_number(1) # Number of folders
339
+
340
+ # External flag (0 = inline, folders follow)
341
+ metadata << [0].pack("C")
342
+
343
+ # Folder definitions
344
+ build_folder_coder(metadata)
345
+
346
+ # kCodersUnpackSize - comes AFTER all folder definitions
347
+ metadata << [PropertyId::CODERS_UNPACK_SIZE].pack("C")
348
+ metadata << write_number(unpack_size)
349
+
350
+ # kEnd for UnpackInfo
351
+ metadata << [PropertyId::K_END].pack("C")
352
+
353
+ # kSubStreamsInfo describes exactly the entries WITH a
354
+ # stream: directory entries (empty-stream) are excluded,
355
+ # or readers reject the header.
356
+ metadata << [PropertyId::SUBSTREAMS_INFO].pack("C")
357
+ stream_files = @entries.select(&:has_stream?)
358
+ # Write the explicit form whenever any entry lacks a
359
+ # stream (directory entries): without kNumUnpackStreams,
360
+ # readers assume one substream per file and reject the
361
+ # digest count.
362
+ if stream_files.size > 1 || stream_files.size != @entries.size
363
+
364
+ # NUM_UNPACK_STREAM - number of substreams in this folder
365
+ metadata << [PropertyId::NUM_UNPACK_STREAM].pack("C")
366
+ metadata << write_number(stream_files.size)
367
+
368
+ # SIZE - size of each substream except the last one
369
+ # Per 7-Zip spec: only write numSubstreams-1 sizes, last
370
+ # is calculated from folder's unpack size minus the sum
371
+ metadata << [PropertyId::SIZE].pack("C")
372
+ stream_files[0..-2].each do |entry|
373
+ metadata << write_number(entry.size)
374
+ end
375
+
376
+ # CRCs
377
+ metadata << [PropertyId::CRC].pack("C")
378
+ metadata << [1].pack("C") # All defined
379
+ stream_files.each do |entry|
380
+ # Use 0 for entries without CRC (empty files)
381
+ crc = entry.crc || 0
382
+ metadata << [crc].pack("V")
383
+ end
384
+
385
+ else
386
+ # Single substream: CRC goes in SubStreamsInfo
387
+ metadata << [PropertyId::CRC].pack("C")
388
+ metadata << [1].pack("C") # All defined
389
+ # Use 0 for entries without CRC (empty files)
390
+ crc = stream_files.first&.crc || 0
391
+ metadata << [crc].pack("V")
392
+ end
393
+ metadata << [PropertyId::K_END].pack("C")
394
+ end
395
+
396
+ def build_non_solid_streams_info(metadata, streams, packed_sizes)
397
+ num_streams = streams.size
398
+
399
+ # kPackInfo property (0x06)
400
+ metadata << [PropertyId::PACK_INFO].pack("C")
401
+ metadata << write_number(0) # Pack position
402
+ metadata << write_number(num_streams) # Number of pack streams
403
+
404
+ # kSize property - sizes of each pack stream (compressed)
405
+ metadata << [PropertyId::SIZE].pack("C")
406
+ packed_sizes.each do |packed|
407
+ metadata << write_number(packed)
408
+ end
409
+
410
+ # kEnd for PackInfo
411
+ metadata << [PropertyId::K_END].pack("C")
412
+
413
+ # kUnpackInfo property (0x07)
414
+ metadata << [PropertyId::UNPACK_INFO].pack("C")
415
+
416
+ # kFolder property (0x0B)
417
+ metadata << [PropertyId::FOLDER].pack("C")
418
+ metadata << write_number(num_streams) # Number of folders
419
+
420
+ # External flag for all folders
421
+ metadata << [0].pack("C")
422
+
423
+ # Folder definitions (coders only, no sizes yet):
424
+ # one LZMA2 coder per non-empty stream, COPY for empties
425
+ streams.each do |stream|
426
+ if stream[:size].positive?
427
+ build_folder_coder(metadata)
428
+ else
429
+ metadata << write_number(1)
430
+ metadata << COPY_MAIN_BYTE
431
+ metadata << COPY_METHOD_ID
432
+ end
433
+ end
434
+
435
+ # kCodersUnpackSize - comes after ALL folder definitions
436
+ # (original sizes; kSize above carries the packed sizes)
437
+ metadata << [PropertyId::CODERS_UNPACK_SIZE].pack("C")
438
+ streams.each do |stream|
439
+ metadata << write_number(stream[:size])
440
+ end
441
+
442
+ # kEnd for UnpackInfo
443
+ metadata << [PropertyId::K_END].pack("C")
444
+
445
+ # kSubStreamsInfo - CRC for each substream. Each folder
446
+ # holds exactly one substream here, so digests cover only
447
+ # the entries WITH a stream (directory entries have none).
448
+ metadata << [PropertyId::SUBSTREAMS_INFO].pack("C")
449
+ metadata << [PropertyId::CRC].pack("C")
450
+ metadata << [1].pack("C") # All defined
451
+ @entries.select(&:has_stream?).each do |entry|
452
+ crc = entry.crc || 0
453
+ metadata << [crc].pack("V")
454
+ end
455
+ metadata << [PropertyId::K_END].pack("C")
456
+ end
457
+
458
+ def build_folder_coder(metadata)
459
+ # Number of coders
460
+ metadata << write_number(1)
461
+
462
+ # Coder info for LZMA2 (method 0x21)
463
+ # MainByte format:
464
+ # bits 0-3 = num bytes for CodecID (1 for 0x21)
465
+ # bit 4 = IsComplexCoder (0)
466
+ # bits 5-7 = num property bytes (we set to 0 and write size separately)
467
+ # Per 7-Zip SDK, bits 5-7 indicate if there ARE properties (non-zero = has props)
468
+ # MainByte = 0x21 means: 1 byte for CodecID + has properties
469
+ metadata << [0x21].pack("C") # MainByte: 1 byte for CodecID + has properties
470
+ metadata << [0x21].pack("C") # Method ID: LZMA2 = 0x21
471
+
472
+ # LZMA2 property byte encodes dictionary size
473
+ dict_size = @options[:dict_size] || (8 * 1024 * 1024)
474
+ prop_byte = encode_lzma2_dict_size(dict_size)
475
+
476
+ # 7-Zip format: write property SIZE (as VLI), then property bytes
477
+ metadata << write_number(1) # PropsSize = 1 byte
478
+ metadata << [prop_byte].pack("C") # Property byte
479
+ end
480
+
481
+ def build_files_info(metadata)
482
+ # kFilesInfo property (0x05)
483
+ metadata << [PropertyId::FILES_INFO].pack("C")
484
+
485
+ # Number of files
486
+ metadata << write_number(@entries.size)
487
+
488
+ # Build EMPTY_STREAM property (0x0E) — written first, like
489
+ # 7-Zip itself: readers derive the directory flag from it;
490
+ # without it, directory entries are extracted as (invalid)
491
+ # files. Format: [EMPTY_STREAM] [size] [bit vector]
492
+ empty_bits = @entries.map { |e| !e.has_stream? }
493
+ if empty_bits.any?
494
+ # kEmptyStream carries the raw bit bytes only — NO
495
+ # all-defined marker byte (verified against 7-Zip's own
496
+ # -mhc=off output; a marker byte here shifts the whole
497
+ # header for strict readers).
498
+ empty_data = write_bit_bytes(empty_bits)
499
+ metadata << [PropertyId::EMPTY_STREAM].pack("C")
500
+ metadata << write_number(empty_data.bytesize)
501
+ metadata << empty_data
502
+
503
+ # kEmptyFile distinguishes zero-byte files from
504
+ # directories among the empty-stream entries.
505
+ empty_file_bits = @entries.map do |e|
506
+ !e.has_stream? && !e.is_dir
507
+ end
508
+ if empty_file_bits.any?
509
+ ef_data = write_bit_bytes(empty_file_bits)
510
+ metadata << [PropertyId::EMPTY_FILE].pack("C")
511
+ metadata << write_number(ef_data.bytesize)
512
+ metadata << ef_data
513
+ end
514
+ end
515
+
516
+ # Build NAME property (0x11)
517
+ # Format: [NAME] [size] [external] [UTF-16LE names with null terminators]
518
+ name_data = String.new(encoding: "BINARY")
519
+
520
+ @entries.each do |entry|
521
+ # Encode name as UTF-16LE and force to BINARY
522
+ name_utf16le = entry.name.encode("UTF-16LE").b
523
+ # Add null terminator (2 bytes)
524
+ name_data << name_utf16le
525
+ name_data << NULL_TERMINATOR
526
+ end
527
+
528
+ metadata << [PropertyId::NAME].pack("C")
529
+ metadata << write_number(name_data.bytesize + 1) # +1 for External byte
530
+ metadata << [0].pack("C") # External = 0 (inline)
531
+ metadata << name_data
532
+
533
+ # Build MTIME property (0x14) - MUST come before WIN_ATTRIB per 7-Zip spec
534
+ # Format: [MTIME] [size] [defined bits] [external] [FILETIME values]
535
+ time_data = String.new(encoding: "BINARY")
536
+
537
+ @entries.each do |entry|
538
+ unix_time = entry.mtime.to_i
539
+ windows_time = (unix_time + 11_644_473_600) * 10_000_000
540
+ time_data << [windows_time].pack("Q<")
541
+ end
542
+
543
+ metadata << [PropertyId::MTIME].pack("C")
544
+ metadata << write_number(time_data.bytesize + 2) # +2 for all_defined and external bytes
545
+ metadata << [1].pack("C") # All defined
546
+ metadata << [0].pack("C") # External = 0 (inline)
547
+ metadata << time_data
548
+
549
+ # Build WIN_ATTRIB property (0x15) - comes after MTIME per 7-Zip spec
550
+ # Format: [WIN_ATTRIB] [size] [defined bits] [external] [attributes]
551
+ attr_data = String.new(encoding: "BINARY")
552
+ @entries.each do |entry|
553
+ attr_data << [entry.attributes || FILE_ATTRIBUTE_ARCHIVE.unpack1("V")].pack("V")
554
+ end
555
+
556
+ metadata << [PropertyId::WIN_ATTRIB].pack("C")
557
+ metadata << write_number(attr_data.bytesize + 2) # +2 for all_defined and external bytes
558
+ metadata << [1].pack("C") # All defined
559
+ metadata << [0].pack("C") # External = 0 (inline)
560
+ metadata << attr_data
561
+
562
+ # kEnd for FilesInfo
563
+ metadata << [PropertyId::K_END].pack("C")
564
+ end
565
+
566
+ def compress_with_lzma2(data)
567
+ # Use 7-Zip SDK LZMA2 encoder for 7-Zip format
568
+ #
569
+ # The encoder dictionary is sized to the data but never
570
+ # exceeds the size announced in the coder properties
571
+ # (options default 8MB): the decoder allocates its window
572
+ # from the announced size, so a larger encoder dictionary
573
+ # could emit matches reading outside the decoder window.
574
+ announced = @options[:dict_size] || (8 * 1024 * 1024)
575
+ dict_size = [4096, data.bytesize].max
576
+ dict_size = announced if announced < dict_size
577
+
578
+ encoder = Omnizip::Implementations::SevenZip::LZMA2::Encoder.new(
579
+ dict_size: dict_size,
580
+ lc: 3,
581
+ lp: 0,
582
+ pb: 2,
583
+ standalone: false, # No property byte (raw mode)
584
+ )
585
+
586
+ encoder.encode(data)
587
+ end
588
+
589
+ # Encode dictionary size to LZMA2 property byte
590
+ #
591
+ # LZMA2 property byte encoding (per XZ spec, same for 7-Zip):
592
+ # dict_size = (2 | (props & 1)) << (props / 2 + 11)
593
+ #
594
+ # This gives sizes from 4KB (props=0) to 4GB (props=40)
595
+ #
596
+ # @param dict_size [Integer] Dictionary size in bytes
597
+ # @return [Integer] Property byte (0-40)
598
+ def encode_lzma2_dict_size(dict_size)
599
+ # Find the smallest prop value that gives >= dict_size
600
+ (0..40).each do |prop|
601
+ # Decode formula: dict_size = (2 | (prop & 1)) << (prop / 2 + 11)
602
+ base = 2 | (prop & 1)
603
+ size = base << ((prop / 2) + 11)
604
+ return prop if size >= dict_size
605
+ end
606
+
607
+ # Maximum property value
608
+ 40
609
+ end
610
+
611
+ def build_next_header_metadata(compressed_size, unpack_size)
612
+ metadata = String.new(encoding: "BINARY")
613
+
614
+ # kHeader property (0x01)
615
+ metadata << [PropertyId::HEADER].pack("C")
616
+
617
+ # kMainStreamsInfo property (0x04) - WRAPPER for stream info
618
+ metadata << [PropertyId::MAIN_STREAMS_INFO].pack("C")
619
+
620
+ # kPackInfo property (0x06)
621
+ metadata << [PropertyId::PACK_INFO].pack("C")
622
+ metadata << write_number(0) # Pack position
623
+ metadata << write_number(1) # Number of pack streams
624
+
625
+ # kSize property
626
+ metadata << [PropertyId::SIZE].pack("C")
627
+ metadata << write_number(compressed_size)
628
+
629
+ # kEnd for PackInfo
630
+ metadata << [PropertyId::K_END].pack("C")
631
+
632
+ # kUnpackInfo property (0x07)
633
+ metadata << [PropertyId::UNPACK_INFO].pack("C")
634
+
635
+ # kFolder property (0x0B)
636
+ metadata << [PropertyId::FOLDER].pack("C")
637
+ metadata << write_number(1) # Number of folders
638
+
639
+ # Folder content
640
+ metadata << [0].pack("C") # External flag (0 = inline)
641
+
642
+ # Number of coders
643
+ metadata << write_number(1)
644
+
645
+ # Coder info for LZMA2
646
+ # Method ID: LZMA2 = 0x21
647
+ # Main byte: 1 byte for ID + 0x00 for no properties
648
+ metadata << [1].pack("C") # 1 byte for ID
649
+ metadata << [0x21].pack("C") # LZMA2 method ID
650
+
651
+ # kCodersUnpackSize
652
+ metadata << [PropertyId::CODERS_UNPACK_SIZE].pack("C")
653
+ metadata << write_number(unpack_size)
654
+
655
+ # kSubStreamsInfo property (0x08)
656
+ metadata << [PropertyId::SUBSTREAMS_INFO].pack("C")
657
+
658
+ # kCRC property (0x0a)
659
+ metadata << [PropertyId::CRC].pack("C")
660
+ metadata << [1].pack("C") # All streams have CRC (1 = yes, 0 = no)
661
+
662
+ # EMPTY_STREAM (0x0e): Number of empty unpack streams
663
+ metadata << [PropertyId::EMPTY_STREAM].pack("C")
664
+ metadata << [0].pack("C") # 0 empty streams (file has data)
665
+
666
+ # kEnd for SubStreamsInfo
667
+ metadata << [PropertyId::K_END].pack("C")
668
+
669
+ # kEnd for UnpackInfo
670
+ metadata << [PropertyId::K_END].pack("C")
671
+
672
+ # kEnd for MainStreamsInfo
673
+ metadata << [PropertyId::K_END].pack("C")
674
+
675
+ # kEnd for Header
676
+ metadata << [PropertyId::K_END].pack("C")
677
+
678
+ metadata
679
+ end
680
+
681
+ def build_metadata_footer
682
+ footer = String.new(encoding: "BINARY")
683
+
684
+ # Single loop to add filename, attributes, and timestamps for each entry
685
+ @entries.each do |entry|
686
+ # Add filename in UTF-16LE with null terminator
687
+ entry.name.encode("UTF-16LE").each_byte do |byte|
688
+ footer << [byte].pack("C")
689
+ end
690
+ footer << NULL_TERMINATOR
691
+
692
+ # Add file attributes (Windows FILE attributes)
693
+ footer << FILE_ATTRIBUTE_ARCHIVE
694
+
695
+ # Add Windows FILETIME for modification time
696
+ unix_time = entry.mtime.to_i
697
+ windows_time = (unix_time + 11_644_473_600) * 10_000_000
698
+ footer << [windows_time].pack("Q<")
699
+ end
700
+
701
+ footer
702
+ end
703
+
704
+ def write_start_header(io, next_header_offset, next_header_size,
705
+ next_header_data)
706
+ header = String.new(encoding: "BINARY")
707
+
708
+ # Signature (6 bytes)
709
+ header << SIGNATURE
710
+
711
+ # Version (2 bytes)
712
+ header << [MAJOR_VERSION, MINOR_VERSION].pack("CC")
713
+
714
+ # Calculate CRC for next header info
715
+ next_header_info = String.new(encoding: "BINARY")
716
+ next_header_info << [next_header_offset].pack("Q<")
717
+ next_header_info << [next_header_size].pack("Q<")
718
+
719
+ # Calculate CRC for next header
720
+ crc = Omnizip::Checksums::Crc32.new
721
+ crc.update(next_header_data)
722
+ next_header_crc = crc.finalize
723
+ next_header_info << [next_header_crc].pack("V")
724
+
725
+ # Calculate CRC for next header info
726
+ info_crc = Omnizip::Checksums::Crc32.new
727
+ info_crc.update(next_header_info)
728
+
729
+ # Start header CRC (4 bytes)
730
+ header << [info_crc.finalize].pack("V")
731
+
732
+ # Next header info (20 bytes)
733
+ header << next_header_info
734
+
735
+ # Write at the beginning
736
+ io.seek(0)
737
+ io.write(header)
738
+ end
739
+
740
+ # Encode variable-length integer (7-Zip VLI format)
741
+ #
742
+ # 7-Zip VLI encoding uses the first byte's high bits to determine
743
+ # the number of additional bytes:
744
+ # 0xxxxxxx : value = xxxxxxx (0-127)
745
+ # 10xxxxxx BYTE y[1] : value = (xxxxxx << 8) + y
746
+ # 110xxxxx BYTE y[2] : value = (xxxxx << 16) + y
747
+ # 1110xxxx BYTE y[3] : value = (xxxx << 24) + y
748
+ # ...up to 8 bytes total
749
+ # Raw MSB-first packed bit bytes, no marker
750
+ def write_bit_bytes(bits)
751
+ bytes = Array.new((bits.size + 7) / 8, 0)
752
+ bits.each_with_index do |bit, i|
753
+ bytes[i / 8] |= (1 << (7 - (i % 8))) if bit
754
+ end
755
+ bytes.pack("C*")
756
+ end
757
+
758
+ # Bit vector with an all-defined marker byte, MSB-first
759
+ # packing (same layout as HeaderWriter's)
760
+ def write_bit_vector(bits)
761
+ return [1].pack("C") if bits.all?
762
+
763
+ bytes = Array.new((bits.size + 7) / 8, 0)
764
+ bits.each_with_index do |bit, i|
765
+ bytes[i / 8] |= (1 << (7 - (i % 8))) if bit
766
+ end
767
+ [0].pack("C") + bytes.pack("C*")
768
+ end
769
+
770
+ def write_number(value)
771
+ # Single byte encoding (0-127)
772
+ return [value].pack("C") if value < 0x80
773
+
774
+ # 7-Zip VLI encoding: extra bytes are little-endian per SDK ReadNumberSpec().
775
+ # Reference: https://py7zr.readthedocs.io/en/latest/archive_format.html
776
+ result = String.new(encoding: "BINARY")
777
+
778
+ # Determine how many extra bytes are needed
779
+ thresholds = [0x80, 0x4000, 0x20_0000, 0x1000_0000,
780
+ 0x8_0000_0000, 0x400_0000_0000,
781
+ 0x2_0000_0000_0000, 0x100_0000_0000_0000]
782
+ num_extra = thresholds.index { |t| value < t } || 8
783
+
784
+ # Build first byte: num_extra leading 1-bits, then 0-bit, then data bits
785
+ first_byte = 0
786
+ num_extra.times { |i| first_byte |= (0x80 >> i) }
787
+ data_bits_shift = 8 * num_extra
788
+ first_byte |= (value >> data_bits_shift) & ((0x80 >> num_extra) - 1)
789
+ result << [first_byte].pack("C")
790
+
791
+ # Extra bytes in little-endian order (low byte first)
792
+ num_extra.times do |i|
793
+ result << [(value >> (8 * i)) & 0xFF].pack("C")
794
+ end
795
+
796
+ result
797
+ end
798
+ end
799
+ end
800
+ end
801
+ end