omnizip 0.3.1

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