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,297 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omnizip
4
+ module Formats
5
+ module Iso
6
+ # ISO 9660 Directory Structure Builder
7
+ #
8
+ # Builds the hierarchical directory structure for an ISO image,
9
+ # allocating sectors for directories and files.
10
+ class DirectoryBuilder
11
+ # @return [Array<Hash>] Files to add
12
+ attr_reader :files
13
+
14
+ # @return [Array<Hash>] Directories to add
15
+ attr_reader :directories
16
+
17
+ # @return [Integer] ISO level
18
+ attr_reader :level
19
+
20
+ # @return [Boolean] Rock Ridge enabled
21
+ attr_reader :rock_ridge
22
+
23
+ # Initialize directory builder
24
+ #
25
+ # @param files [Array<Hash>] Files to include
26
+ # @param directories [Array<Hash>] Directories to include
27
+ # @param options [Hash] Builder options
28
+ def initialize(files, directories, options = {})
29
+ @files = files
30
+ @directories = directories
31
+ @level = options.fetch(:level, 2)
32
+ @rock_ridge = options.fetch(:rock_ridge, false)
33
+ @current_sector = 22 # After volume descriptors and path tables
34
+ end
35
+
36
+ # Build directory structure
37
+ #
38
+ # @return [Hash] Complete directory structure with allocations
39
+ def build
40
+ # Build directory tree
41
+ tree = build_directory_tree
42
+
43
+ # Allocate sectors for directories and files
44
+ allocate_sectors(tree)
45
+
46
+ # Build path table
47
+ path_table = build_path_table(tree)
48
+
49
+ {
50
+ root: tree,
51
+ directories: flatten_directories(tree),
52
+ files: @files,
53
+ path_table: path_table,
54
+ path_table_size: path_table.bytesize,
55
+ total_sectors: @current_sector,
56
+ }
57
+ end
58
+
59
+ private
60
+
61
+ # Build directory tree from files and directories
62
+ #
63
+ # @return [Hash] Root directory node
64
+ def build_directory_tree
65
+ root = {
66
+ name: "\x00", # Root directory identifier
67
+ iso_path: "",
68
+ children: [],
69
+ directory: true,
70
+ stat: nil,
71
+ }
72
+
73
+ # Add all directories first
74
+ @directories.each do |dir_info|
75
+ add_to_tree(root, dir_info[:iso_path], dir_info)
76
+ end
77
+
78
+ # Add all files
79
+ @files.each do |file_info|
80
+ add_to_tree(root, file_info[:iso_path], file_info)
81
+ end
82
+
83
+ root
84
+ end
85
+
86
+ # Add entry to directory tree
87
+ #
88
+ # @param root [Hash] Root node
89
+ # @param path [String] Entry path
90
+ # @param info [Hash] Entry information
91
+ def add_to_tree(root, path, info)
92
+ parts = path.split("/")
93
+ current = root
94
+
95
+ # Navigate/create directory structure
96
+ parts[0...-1].each do |part|
97
+ # Ensure current node has children array (it should be a directory)
98
+ current[:children] ||= []
99
+ child = current[:children].find { |c| c[:name] == part }
100
+
101
+ unless child
102
+ child = {
103
+ name: part,
104
+ iso_path: [current[:iso_path], part].reject(&:empty?).join("/"),
105
+ children: [],
106
+ directory: true,
107
+ stat: nil,
108
+ }
109
+ current[:children] << child
110
+ end
111
+
112
+ current = child
113
+ end
114
+
115
+ # Add the actual entry
116
+ entry_name = parts.last
117
+ entry = {
118
+ name: entry_name,
119
+ iso_path: path,
120
+ children: info[:directory] ? [] : nil,
121
+ directory: info[:directory] || false,
122
+ stat: info[:stat],
123
+ source: info[:source],
124
+ size: info[:directory] ? 0 : File.size(info[:source]),
125
+ }
126
+
127
+ # Ensure current node has children array
128
+ current[:children] ||= []
129
+ current[:children] << entry
130
+ end
131
+
132
+ # Allocate sectors for all entries
133
+ #
134
+ # @param tree [Hash] Directory tree
135
+ def allocate_sectors(tree)
136
+ # Allocate for root directory
137
+ allocate_directory_sectors(tree)
138
+
139
+ # Recursively allocate for children
140
+ allocate_children_sectors(tree)
141
+
142
+ # Allocate for files
143
+ allocate_file_sectors
144
+ end
145
+
146
+ # Allocate sectors for a directory
147
+ #
148
+ # @param dir_node [Hash] Directory node
149
+ def allocate_directory_sectors(dir_node)
150
+ # Calculate directory size
151
+ dir_size = calculate_directory_size(dir_node)
152
+ sectors_needed = (dir_size.to_f / Iso::SECTOR_SIZE).ceil
153
+
154
+ # Allocate location
155
+ dir_node[:location] = @current_sector
156
+ dir_node[:size] = dir_size
157
+
158
+ @current_sector += sectors_needed
159
+ end
160
+
161
+ # Calculate directory data size
162
+ #
163
+ # @param dir_node [Hash] Directory node
164
+ # @return [Integer] Size in bytes
165
+ def calculate_directory_size(dir_node)
166
+ size = 0
167
+
168
+ # Size for "." entry (34 bytes minimum)
169
+ size += 34
170
+
171
+ # Size for ".." entry
172
+ size += 34
173
+
174
+ # Size for each child entry
175
+ dir_node[:children].each do |child|
176
+ name_len = child[:name].bytesize
177
+ padding = name_len.even? ? 1 : 0
178
+ entry_size = 33 + name_len + padding
179
+
180
+ # Add Rock Ridge System Use fields if enabled
181
+ entry_size += calculate_rock_ridge_size(child) if @rock_ridge
182
+
183
+ size += entry_size
184
+ end
185
+
186
+ # Round up to sector boundary
187
+ ((size.to_f / Iso::SECTOR_SIZE).ceil * Iso::SECTOR_SIZE)
188
+ end
189
+
190
+ # Calculate Rock Ridge System Use field size
191
+ #
192
+ # @param entry [Hash] Entry information
193
+ # @return [Integer] Size in bytes
194
+ def calculate_rock_ridge_size(_entry)
195
+ # Basic Rock Ridge fields:
196
+ # - PX (POSIX attributes): 44 bytes
197
+ # - TF (timestamps): 26 bytes
198
+ # - NM (alternate name): variable
199
+ # For now, estimate 100 bytes
200
+ 100
201
+ end
202
+
203
+ # Allocate sectors for children recursively
204
+ #
205
+ # @param dir_node [Hash] Directory node
206
+ def allocate_children_sectors(dir_node)
207
+ dir_node[:children].each do |child|
208
+ next unless child[:directory]
209
+
210
+ allocate_directory_sectors(child)
211
+ allocate_children_sectors(child)
212
+ end
213
+ end
214
+
215
+ # Allocate sectors for files
216
+ def allocate_file_sectors
217
+ @files.each do |file_info|
218
+ file_size = file_info[:stat].size
219
+ sectors_needed = (file_size.to_f / Iso::SECTOR_SIZE).ceil
220
+
221
+ file_info[:location] = @current_sector
222
+ file_info[:size] = file_size
223
+
224
+ @current_sector += sectors_needed
225
+ end
226
+ end
227
+
228
+ # Build path table from directory tree
229
+ #
230
+ # @param tree [Hash] Directory tree
231
+ # @return [String] Path table data
232
+ def build_path_table(tree)
233
+ table = +""
234
+ directories = []
235
+
236
+ # Collect all directories in path table order
237
+ collect_directories_for_path_table(tree, directories)
238
+
239
+ # Build path table entries
240
+ directories.each_with_index do |dir, _idx|
241
+ name = dir[:name] == "\x00" ? "\x00" : dir[:name]
242
+ parent_idx = dir[:parent_idx] || 0
243
+
244
+ # Name length
245
+ table << [name.bytesize].pack("C")
246
+
247
+ # Extended attribute length
248
+ table << [0].pack("C")
249
+
250
+ # Location of extent
251
+ table << [dir[:location]].pack("V")
252
+
253
+ # Parent directory number (1-based)
254
+ table << [parent_idx + 1].pack("v")
255
+
256
+ # Directory name
257
+ table << name
258
+
259
+ # Pad to even length
260
+ table << "\x00" if name.bytesize.odd?
261
+ end
262
+
263
+ table
264
+ end
265
+
266
+ # Collect directories in depth-first order for path table
267
+ #
268
+ # @param node [Hash] Current node
269
+ # @param list [Array<Hash>] Output list
270
+ # @param parent_idx [Integer, nil] Parent index
271
+ def collect_directories_for_path_table(node, list, parent_idx = nil)
272
+ current_idx = list.size
273
+ node[:parent_idx] = parent_idx
274
+ list << node
275
+
276
+ node[:children].select { |c| c[:directory] }.each do |child|
277
+ collect_directories_for_path_table(child, list, current_idx)
278
+ end
279
+ end
280
+
281
+ # Flatten directory tree to array
282
+ #
283
+ # @param tree [Hash] Directory tree
284
+ # @return [Array<Hash>] Flat directory list
285
+ def flatten_directories(tree)
286
+ result = [tree]
287
+
288
+ tree[:children].select { |c| c[:directory] }.each do |child|
289
+ result.concat(flatten_directories(child))
290
+ end
291
+
292
+ result
293
+ end
294
+ end
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omnizip
4
+ module Formats
5
+ module Iso
6
+ # ISO 9660 Directory Record
7
+ # Represents a file or directory entry
8
+ class DirectoryRecord
9
+ attr_reader :length, :extended_attr_length, :location, :data_length,
10
+ :recording_date, :flags, :file_unit_size, :interleave_gap_size,
11
+ :volume_sequence_number, :name, :system_use
12
+
13
+ # Parse directory record from binary data
14
+ #
15
+ # @param data [String] Binary record data
16
+ # @param offset [Integer] Offset in data to start parsing
17
+ # @return [DirectoryRecord] Parsed record
18
+ def self.parse(data, offset = 0)
19
+ new.tap { |record| record.parse(data, offset) }
20
+ end
21
+
22
+ # Parse record data
23
+ #
24
+ # @param data [String] Binary data
25
+ # @param offset [Integer] Starting offset
26
+ def parse(data, offset = 0)
27
+ # Byte 0: Length of directory record
28
+ @length = data.getbyte(offset)
29
+ return if @length.zero? # Padding
30
+
31
+ # Byte 1: Extended attribute record length
32
+ @extended_attr_length = data.getbyte(offset + 1)
33
+
34
+ # Bytes 2-9: Location of extent (both-endian)
35
+ @location = data[offset + 2, 4].unpack1("V")
36
+
37
+ # Bytes 10-17: Data length (both-endian)
38
+ @data_length = data[offset + 10, 4].unpack1("V")
39
+
40
+ # Bytes 18-24: Recording date and time
41
+ @recording_date = parse_record_datetime(data[offset + 18, 7])
42
+
43
+ # Byte 25: File flags
44
+ @flags = data.getbyte(offset + 25)
45
+
46
+ # Byte 26: File unit size (for interleaved files)
47
+ @file_unit_size = data.getbyte(offset + 26)
48
+
49
+ # Byte 27: Interleave gap size
50
+ @interleave_gap_size = data.getbyte(offset + 27)
51
+
52
+ # Bytes 28-31: Volume sequence number (both-endian)
53
+ @volume_sequence_number = data[offset + 28, 2].unpack1("v")
54
+
55
+ # Byte 32: Length of file identifier
56
+ name_length = data.getbyte(offset + 32)
57
+
58
+ # Bytes 33+: File identifier
59
+ @name = data[offset + 33, name_length]
60
+
61
+ # Parse file identifier
62
+ parse_name
63
+
64
+ # System Use field (Rock Ridge extensions, etc.)
65
+ # Located after name and padding
66
+ su_offset = offset + 33 + name_length
67
+ su_offset += 1 if name_length.even? # Padding byte
68
+
69
+ return unless su_offset < offset + @length
70
+
71
+ @system_use = data[su_offset, offset + @length - su_offset]
72
+ end
73
+
74
+ # Check if entry is a directory
75
+ #
76
+ # @return [Boolean] true if directory
77
+ def directory?
78
+ @flags.anybits?(Iso::FLAG_DIRECTORY)
79
+ end
80
+
81
+ # Check if entry is hidden
82
+ #
83
+ # @return [Boolean] true if hidden
84
+ def hidden?
85
+ @flags.anybits?(Iso::FLAG_HIDDEN)
86
+ end
87
+
88
+ # Check if this is the current directory entry
89
+ #
90
+ # @return [Boolean] true if current directory
91
+ def current_directory?
92
+ @name == "\x00"
93
+ end
94
+
95
+ # Check if this is the parent directory entry
96
+ #
97
+ # @return [Boolean] true if parent directory
98
+ def parent_directory?
99
+ @name == "\x01"
100
+ end
101
+
102
+ # Get file size
103
+ #
104
+ # @return [Integer] Size in bytes
105
+ def size
106
+ @data_length
107
+ end
108
+
109
+ # Get modification time
110
+ #
111
+ # @return [Time, nil] Modification time
112
+ def mtime
113
+ @recording_date
114
+ end
115
+
116
+ private
117
+
118
+ # Parse directory record datetime (7-byte format)
119
+ #
120
+ # @param data [String] 7-byte datetime
121
+ # @return [Time, nil] Parsed time
122
+ def parse_record_datetime(data)
123
+ return nil if data.nil? || data.bytesize < 7
124
+
125
+ year = 1900 + data.getbyte(0)
126
+ month = data.getbyte(1)
127
+ day = data.getbyte(2)
128
+ hour = data.getbyte(3)
129
+ minute = data.getbyte(4)
130
+ second = data.getbyte(5)
131
+ # Timezone offset at byte 6 (15-minute intervals from GMT)
132
+
133
+ Time.new(year, month, day, hour, minute, second)
134
+ rescue ArgumentError
135
+ nil
136
+ end
137
+
138
+ # Parse file identifier name
139
+ def parse_name
140
+ # Special cases for current and parent directory
141
+ return if @name == "\x00" || @name == "\x01"
142
+
143
+ # Remove version number (;1) if present
144
+ @name = @name.split(";").first if @name.include?(";")
145
+
146
+ # Convert to UTF-8 and strip
147
+ @name = @name.force_encoding("UTF-8").strip
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omnizip
4
+ module Formats
5
+ module Iso
6
+ # Joliet Extensions for ISO 9660
7
+ #
8
+ # Implements Microsoft's Joliet extensions to support long filenames
9
+ # (up to 64 characters) and Unicode (UCS-2) encoding in ISO 9660 images.
10
+ #
11
+ # Joliet creates a parallel directory structure in a Supplementary Volume
12
+ # Descriptor with UCS-2 encoded filenames, allowing Windows systems to
13
+ # display proper long filenames while maintaining ISO 9660 compatibility.
14
+ module Joliet
15
+ # Joliet escape sequences for UCS-2 encoding
16
+ # Level 1: %/@
17
+ # Level 2: %/C
18
+ # Level 3: %/E (most common, supports UCS-2)
19
+ ESCAPE_SEQUENCE_LEVEL_3 = "%/E"
20
+
21
+ # Maximum Joliet filename length in characters
22
+ MAX_FILENAME_LENGTH = 64
23
+
24
+ # Encode string to UCS-2 (UTF-16BE)
25
+ #
26
+ # @param str [String] String to encode
27
+ # @param max_length [Integer] Maximum length in characters
28
+ # @return [String] UCS-2 encoded string
29
+ def self.encode_ucs2(str, max_length = MAX_FILENAME_LENGTH)
30
+ # Truncate to max length
31
+ str = str[0, max_length] if str.length > max_length
32
+
33
+ # Encode to UTF-16BE (UCS-2)
34
+ str.encode("UTF-16BE")
35
+ rescue Encoding::UndefinedConversionError
36
+ # Fallback to ASCII if conversion fails
37
+ str.encode("UTF-16BE", undef: :replace, replace: "_")
38
+ end
39
+
40
+ # Decode UCS-2 string to UTF-8
41
+ #
42
+ # @param ucs2_data [String] UCS-2 encoded data
43
+ # @return [String] UTF-8 string
44
+ def self.decode_ucs2(ucs2_data)
45
+ ucs2_data.force_encoding("UTF-16BE").encode("UTF-8")
46
+ rescue Encoding::UndefinedConversionError
47
+ # Fallback if decoding fails
48
+ ucs2_data.force_encoding("UTF-16BE").encode("UTF-8",
49
+ undef: :replace,
50
+ replace: "?")
51
+ end
52
+
53
+ # Build Joliet directory record
54
+ #
55
+ # @param name [String] Entry name
56
+ # @param entry_info [Hash] Entry information
57
+ # @param is_directory [Boolean] Is this a directory
58
+ # @return [String] Joliet directory record
59
+ def self.build_directory_record(name, entry_info, is_directory: false)
60
+ # Encode name to UCS-2
61
+ name_ucs2 = encode_ucs2(name)
62
+ name_len = name_ucs2.bytesize
63
+
64
+ # Calculate record length
65
+ # No padding needed for UCS-2 names (always even length)
66
+ record_len = 33 + name_len
67
+
68
+ record = +""
69
+
70
+ # Byte 0: Length of directory record
71
+ record << [record_len].pack("C")
72
+
73
+ # Byte 1: Extended attribute record length
74
+ record << [0].pack("C")
75
+
76
+ # Bytes 2-9: Location of extent (both-endian)
77
+ location = entry_info[:location] || 0
78
+ record << [location].pack("V")
79
+ record << [location].pack("N")
80
+
81
+ # Bytes 10-17: Data length (both-endian)
82
+ data_length = entry_info[:size] || 0
83
+ record << [data_length].pack("V")
84
+ record << [data_length].pack("N")
85
+
86
+ # Bytes 18-24: Recording date and time
87
+ mtime = entry_info[:mtime] || entry_info[:stat]&.mtime || Time.now
88
+ record << encode_record_datetime(mtime)
89
+
90
+ # Byte 25: File flags
91
+ flags = 0
92
+ flags |= Iso::FLAG_DIRECTORY if is_directory
93
+ record << [flags].pack("C")
94
+
95
+ # Byte 26: File unit size
96
+ record << [0].pack("C")
97
+
98
+ # Byte 27: Interleave gap size
99
+ record << [0].pack("C")
100
+
101
+ # Bytes 28-31: Volume sequence number (both-endian)
102
+ record << [1].pack("v")
103
+ record << [1].pack("n")
104
+
105
+ # Byte 32: Length of file identifier
106
+ record << [name_len].pack("C")
107
+
108
+ # Bytes 33+: File identifier (UCS-2 encoded)
109
+ record << name_ucs2
110
+
111
+ record
112
+ end
113
+
114
+ # Sanitize filename for Joliet
115
+ #
116
+ # @param name [String] Original filename
117
+ # @return [String] Sanitized filename
118
+ def self.sanitize_filename(name)
119
+ # Joliet allows most Unicode characters
120
+ # Maximum 64 characters
121
+ # Disallow: / * ? < > | " : \
122
+
123
+ sanitized = name.gsub(/[\/*?<>|":\\]/, "_")
124
+ if sanitized.length > MAX_FILENAME_LENGTH
125
+ sanitized = sanitized[0,
126
+ MAX_FILENAME_LENGTH]
127
+ end
128
+ sanitized
129
+ end
130
+
131
+ # Check if filename requires Joliet
132
+ #
133
+ # @param name [String] Filename
134
+ # @return [Boolean] true if Joliet needed
135
+ def self.requires_joliet?(name)
136
+ # Check if name exceeds ISO 9660 Level 2 limits
137
+ return true if name.length > 31
138
+
139
+ # Check if name contains non-ASCII or lowercase
140
+ return true if /[^A-Z0-9_.-]/.match?(name)
141
+
142
+ # Check if name contains Unicode
143
+ name.encoding != Encoding::ASCII && name.bytes.any? { |b| b > 127 }
144
+ end
145
+
146
+ # Build Joliet supplementary volume descriptor
147
+ #
148
+ # @param primary_vd [String] Primary volume descriptor data
149
+ # @param root_dir [Hash] Root directory information
150
+ # @return [String] Joliet SVD (2048 bytes)
151
+ def self.build_supplementary_vd(primary_vd, _root_dir)
152
+ # Start with primary VD as template
153
+ svd = primary_vd.dup
154
+
155
+ # Change type to supplementary
156
+ svd[0] = [Iso::VD_SUPPLEMENTARY].pack("C")
157
+
158
+ # Add Joliet escape sequence
159
+ # Bytes 88-90: Escape sequences
160
+ svd[88, 3] = ESCAPE_SEQUENCE_LEVEL_3
161
+
162
+ # Encode volume identifier to UCS-2
163
+ volume_id = primary_vd[40, 32].strip
164
+ volume_id_ucs2 = encode_ucs2(volume_id, 16)
165
+ svd[40, 32] = pad_ucs2_string(volume_id_ucs2, 32)
166
+
167
+ # Update root directory record with UCS-2 name
168
+ # Root is always "\x00" so no change needed
169
+
170
+ svd
171
+ end
172
+
173
+ # Pad UCS-2 string to specified length
174
+ #
175
+ # @param ucs2_str [String] UCS-2 string
176
+ # @param byte_length [Integer] Target length in bytes
177
+ # @return [String] Padded string
178
+ def self.pad_ucs2_string(ucs2_str, byte_length)
179
+ if ucs2_str.bytesize > byte_length
180
+ ucs2_str[0, byte_length]
181
+ else
182
+ ucs2_str + (" ".encode("UTF-16BE") * ((byte_length - ucs2_str.bytesize) / 2))
183
+ end
184
+ end
185
+
186
+ # Encode recording date/time (7-byte format)
187
+ #
188
+ # @param time [Time] Time to encode
189
+ # @return [String] 7-byte encoded time
190
+ def self.encode_record_datetime(time)
191
+ [
192
+ time.year - 1900,
193
+ time.month,
194
+ time.day,
195
+ time.hour,
196
+ time.min,
197
+ time.sec,
198
+ 0, # GMT offset
199
+ ].pack("C7")
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end