hexapdf 0.1.0

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 (346) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTERS +3 -0
  3. data/LICENSE +26 -0
  4. data/README.md +88 -0
  5. data/Rakefile +121 -0
  6. data/VERSION +1 -0
  7. data/agpl-3.0.txt +661 -0
  8. data/bin/hexapdf +6 -0
  9. data/data/hexapdf/afm/Courier-Bold.afm +342 -0
  10. data/data/hexapdf/afm/Courier-BoldOblique.afm +342 -0
  11. data/data/hexapdf/afm/Courier-Oblique.afm +342 -0
  12. data/data/hexapdf/afm/Courier.afm +342 -0
  13. data/data/hexapdf/afm/Helvetica-Bold.afm +2827 -0
  14. data/data/hexapdf/afm/Helvetica-BoldOblique.afm +2827 -0
  15. data/data/hexapdf/afm/Helvetica-Oblique.afm +3051 -0
  16. data/data/hexapdf/afm/Helvetica.afm +3051 -0
  17. data/data/hexapdf/afm/MustRead.html +1 -0
  18. data/data/hexapdf/afm/Symbol.afm +213 -0
  19. data/data/hexapdf/afm/Times-Bold.afm +2588 -0
  20. data/data/hexapdf/afm/Times-BoldItalic.afm +2384 -0
  21. data/data/hexapdf/afm/Times-Italic.afm +2667 -0
  22. data/data/hexapdf/afm/Times-Roman.afm +2419 -0
  23. data/data/hexapdf/afm/ZapfDingbats.afm +225 -0
  24. data/data/hexapdf/encoding/glyphlist.txt +4305 -0
  25. data/data/hexapdf/encoding/zapfdingbats.txt +225 -0
  26. data/examples/arc.rb +50 -0
  27. data/examples/graphics.rb +274 -0
  28. data/examples/hello_world.rb +16 -0
  29. data/examples/machupicchu.jpg +0 -0
  30. data/examples/merging.rb +24 -0
  31. data/examples/optimizing.rb +20 -0
  32. data/examples/show_char_bboxes.rb +55 -0
  33. data/examples/standard_pdf_fonts.rb +72 -0
  34. data/examples/truetype.rb +45 -0
  35. data/lib/hexapdf/cli/extract.rb +128 -0
  36. data/lib/hexapdf/cli/info.rb +121 -0
  37. data/lib/hexapdf/cli/inspect.rb +157 -0
  38. data/lib/hexapdf/cli/modify.rb +218 -0
  39. data/lib/hexapdf/cli.rb +121 -0
  40. data/lib/hexapdf/configuration.rb +392 -0
  41. data/lib/hexapdf/content/canvas.rb +1974 -0
  42. data/lib/hexapdf/content/color_space.rb +364 -0
  43. data/lib/hexapdf/content/graphic_object/arc.rb +267 -0
  44. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +208 -0
  45. data/lib/hexapdf/content/graphic_object/solid_arc.rb +173 -0
  46. data/lib/hexapdf/content/graphic_object.rb +81 -0
  47. data/lib/hexapdf/content/graphics_state.rb +579 -0
  48. data/lib/hexapdf/content/operator.rb +1072 -0
  49. data/lib/hexapdf/content/parser.rb +204 -0
  50. data/lib/hexapdf/content/processor.rb +451 -0
  51. data/lib/hexapdf/content/transformation_matrix.rb +172 -0
  52. data/lib/hexapdf/content.rb +47 -0
  53. data/lib/hexapdf/data_dir.rb +51 -0
  54. data/lib/hexapdf/dictionary.rb +303 -0
  55. data/lib/hexapdf/dictionary_fields.rb +382 -0
  56. data/lib/hexapdf/document.rb +589 -0
  57. data/lib/hexapdf/document_utils.rb +209 -0
  58. data/lib/hexapdf/encryption/aes.rb +206 -0
  59. data/lib/hexapdf/encryption/arc4.rb +93 -0
  60. data/lib/hexapdf/encryption/fast_aes.rb +79 -0
  61. data/lib/hexapdf/encryption/fast_arc4.rb +67 -0
  62. data/lib/hexapdf/encryption/identity.rb +63 -0
  63. data/lib/hexapdf/encryption/ruby_aes.rb +447 -0
  64. data/lib/hexapdf/encryption/ruby_arc4.rb +96 -0
  65. data/lib/hexapdf/encryption/security_handler.rb +494 -0
  66. data/lib/hexapdf/encryption/standard_security_handler.rb +616 -0
  67. data/lib/hexapdf/encryption.rb +94 -0
  68. data/lib/hexapdf/error.rb +73 -0
  69. data/lib/hexapdf/filter/ascii85_decode.rb +160 -0
  70. data/lib/hexapdf/filter/ascii_hex_decode.rb +87 -0
  71. data/lib/hexapdf/filter/dct_decode.rb +57 -0
  72. data/lib/hexapdf/filter/encryption.rb +59 -0
  73. data/lib/hexapdf/filter/flate_decode.rb +93 -0
  74. data/lib/hexapdf/filter/jpx_decode.rb +56 -0
  75. data/lib/hexapdf/filter/lzw_decode.rb +191 -0
  76. data/lib/hexapdf/filter/predictor.rb +266 -0
  77. data/lib/hexapdf/filter/run_length_decode.rb +108 -0
  78. data/lib/hexapdf/filter.rb +176 -0
  79. data/lib/hexapdf/font/cmap/parser.rb +146 -0
  80. data/lib/hexapdf/font/cmap/writer.rb +176 -0
  81. data/lib/hexapdf/font/cmap.rb +90 -0
  82. data/lib/hexapdf/font/encoding/base.rb +77 -0
  83. data/lib/hexapdf/font/encoding/difference_encoding.rb +64 -0
  84. data/lib/hexapdf/font/encoding/glyph_list.rb +150 -0
  85. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +221 -0
  86. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +265 -0
  87. data/lib/hexapdf/font/encoding/standard_encoding.rb +205 -0
  88. data/lib/hexapdf/font/encoding/symbol_encoding.rb +244 -0
  89. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +280 -0
  90. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +250 -0
  91. data/lib/hexapdf/font/encoding.rb +68 -0
  92. data/lib/hexapdf/font/true_type/font.rb +179 -0
  93. data/lib/hexapdf/font/true_type/table/cmap.rb +103 -0
  94. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +384 -0
  95. data/lib/hexapdf/font/true_type/table/directory.rb +92 -0
  96. data/lib/hexapdf/font/true_type/table/glyf.rb +166 -0
  97. data/lib/hexapdf/font/true_type/table/head.rb +143 -0
  98. data/lib/hexapdf/font/true_type/table/hhea.rb +109 -0
  99. data/lib/hexapdf/font/true_type/table/hmtx.rb +79 -0
  100. data/lib/hexapdf/font/true_type/table/loca.rb +79 -0
  101. data/lib/hexapdf/font/true_type/table/maxp.rb +112 -0
  102. data/lib/hexapdf/font/true_type/table/name.rb +218 -0
  103. data/lib/hexapdf/font/true_type/table/os2.rb +200 -0
  104. data/lib/hexapdf/font/true_type/table/post.rb +230 -0
  105. data/lib/hexapdf/font/true_type/table.rb +155 -0
  106. data/lib/hexapdf/font/true_type.rb +48 -0
  107. data/lib/hexapdf/font/true_type_wrapper.rb +240 -0
  108. data/lib/hexapdf/font/type1/afm_parser.rb +230 -0
  109. data/lib/hexapdf/font/type1/character_metrics.rb +67 -0
  110. data/lib/hexapdf/font/type1/font.rb +123 -0
  111. data/lib/hexapdf/font/type1/font_metrics.rb +117 -0
  112. data/lib/hexapdf/font/type1/pfb_parser.rb +71 -0
  113. data/lib/hexapdf/font/type1.rb +52 -0
  114. data/lib/hexapdf/font/type1_wrapper.rb +193 -0
  115. data/lib/hexapdf/font_loader/from_configuration.rb +70 -0
  116. data/lib/hexapdf/font_loader/standard14.rb +98 -0
  117. data/lib/hexapdf/font_loader.rb +85 -0
  118. data/lib/hexapdf/font_utils.rb +89 -0
  119. data/lib/hexapdf/image_loader/jpeg.rb +166 -0
  120. data/lib/hexapdf/image_loader/pdf.rb +89 -0
  121. data/lib/hexapdf/image_loader/png.rb +410 -0
  122. data/lib/hexapdf/image_loader.rb +68 -0
  123. data/lib/hexapdf/importer.rb +139 -0
  124. data/lib/hexapdf/name_tree_node.rb +78 -0
  125. data/lib/hexapdf/number_tree_node.rb +67 -0
  126. data/lib/hexapdf/object.rb +363 -0
  127. data/lib/hexapdf/parser.rb +349 -0
  128. data/lib/hexapdf/rectangle.rb +99 -0
  129. data/lib/hexapdf/reference.rb +98 -0
  130. data/lib/hexapdf/revision.rb +206 -0
  131. data/lib/hexapdf/revisions.rb +194 -0
  132. data/lib/hexapdf/serializer.rb +326 -0
  133. data/lib/hexapdf/stream.rb +279 -0
  134. data/lib/hexapdf/task/dereference.rb +109 -0
  135. data/lib/hexapdf/task/optimize.rb +230 -0
  136. data/lib/hexapdf/task.rb +68 -0
  137. data/lib/hexapdf/tokenizer.rb +406 -0
  138. data/lib/hexapdf/type/catalog.rb +107 -0
  139. data/lib/hexapdf/type/embedded_file.rb +87 -0
  140. data/lib/hexapdf/type/file_specification.rb +232 -0
  141. data/lib/hexapdf/type/font.rb +81 -0
  142. data/lib/hexapdf/type/font_descriptor.rb +109 -0
  143. data/lib/hexapdf/type/font_simple.rb +190 -0
  144. data/lib/hexapdf/type/font_true_type.rb +47 -0
  145. data/lib/hexapdf/type/font_type1.rb +162 -0
  146. data/lib/hexapdf/type/form.rb +103 -0
  147. data/lib/hexapdf/type/graphics_state_parameter.rb +79 -0
  148. data/lib/hexapdf/type/image.rb +73 -0
  149. data/lib/hexapdf/type/info.rb +70 -0
  150. data/lib/hexapdf/type/names.rb +69 -0
  151. data/lib/hexapdf/type/object_stream.rb +224 -0
  152. data/lib/hexapdf/type/page.rb +355 -0
  153. data/lib/hexapdf/type/page_tree_node.rb +269 -0
  154. data/lib/hexapdf/type/resources.rb +212 -0
  155. data/lib/hexapdf/type/trailer.rb +128 -0
  156. data/lib/hexapdf/type/viewer_preferences.rb +73 -0
  157. data/lib/hexapdf/type/xref_stream.rb +204 -0
  158. data/lib/hexapdf/type.rb +67 -0
  159. data/lib/hexapdf/utils/bit_field.rb +87 -0
  160. data/lib/hexapdf/utils/bit_stream.rb +148 -0
  161. data/lib/hexapdf/utils/lru_cache.rb +65 -0
  162. data/lib/hexapdf/utils/math_helpers.rb +55 -0
  163. data/lib/hexapdf/utils/object_hash.rb +130 -0
  164. data/lib/hexapdf/utils/pdf_doc_encoding.rb +93 -0
  165. data/lib/hexapdf/utils/sorted_tree_node.rb +339 -0
  166. data/lib/hexapdf/version.rb +39 -0
  167. data/lib/hexapdf/writer.rb +199 -0
  168. data/lib/hexapdf/xref_section.rb +152 -0
  169. data/lib/hexapdf.rb +34 -0
  170. data/man/man1/hexapdf.1 +249 -0
  171. data/test/data/aes-test-vectors/CBCGFSbox-128-decrypt.data.gz +0 -0
  172. data/test/data/aes-test-vectors/CBCGFSbox-128-encrypt.data.gz +0 -0
  173. data/test/data/aes-test-vectors/CBCGFSbox-192-decrypt.data.gz +0 -0
  174. data/test/data/aes-test-vectors/CBCGFSbox-192-encrypt.data.gz +0 -0
  175. data/test/data/aes-test-vectors/CBCGFSbox-256-decrypt.data.gz +0 -0
  176. data/test/data/aes-test-vectors/CBCGFSbox-256-encrypt.data.gz +0 -0
  177. data/test/data/aes-test-vectors/CBCKeySbox-128-decrypt.data.gz +0 -0
  178. data/test/data/aes-test-vectors/CBCKeySbox-128-encrypt.data.gz +0 -0
  179. data/test/data/aes-test-vectors/CBCKeySbox-192-decrypt.data.gz +0 -0
  180. data/test/data/aes-test-vectors/CBCKeySbox-192-encrypt.data.gz +0 -0
  181. data/test/data/aes-test-vectors/CBCKeySbox-256-decrypt.data.gz +0 -0
  182. data/test/data/aes-test-vectors/CBCKeySbox-256-encrypt.data.gz +0 -0
  183. data/test/data/aes-test-vectors/CBCVarKey-128-decrypt.data.gz +0 -0
  184. data/test/data/aes-test-vectors/CBCVarKey-128-encrypt.data.gz +0 -0
  185. data/test/data/aes-test-vectors/CBCVarKey-192-decrypt.data.gz +0 -0
  186. data/test/data/aes-test-vectors/CBCVarKey-192-encrypt.data.gz +0 -0
  187. data/test/data/aes-test-vectors/CBCVarKey-256-decrypt.data.gz +0 -0
  188. data/test/data/aes-test-vectors/CBCVarKey-256-encrypt.data.gz +0 -0
  189. data/test/data/aes-test-vectors/CBCVarTxt-128-decrypt.data.gz +0 -0
  190. data/test/data/aes-test-vectors/CBCVarTxt-128-encrypt.data.gz +0 -0
  191. data/test/data/aes-test-vectors/CBCVarTxt-192-decrypt.data.gz +0 -0
  192. data/test/data/aes-test-vectors/CBCVarTxt-192-encrypt.data.gz +0 -0
  193. data/test/data/aes-test-vectors/CBCVarTxt-256-decrypt.data.gz +0 -0
  194. data/test/data/aes-test-vectors/CBCVarTxt-256-encrypt.data.gz +0 -0
  195. data/test/data/fonts/Ubuntu-Title.ttf +0 -0
  196. data/test/data/images/cmyk.jpg +0 -0
  197. data/test/data/images/fillbytes.jpg +0 -0
  198. data/test/data/images/gray.jpg +0 -0
  199. data/test/data/images/greyscale-1bit.png +0 -0
  200. data/test/data/images/greyscale-2bit.png +0 -0
  201. data/test/data/images/greyscale-4bit.png +0 -0
  202. data/test/data/images/greyscale-8bit.png +0 -0
  203. data/test/data/images/greyscale-alpha-8bit.png +0 -0
  204. data/test/data/images/greyscale-trns-8bit.png +0 -0
  205. data/test/data/images/greyscale-with-gamma1.0.png +0 -0
  206. data/test/data/images/greyscale-with-gamma1.5.png +0 -0
  207. data/test/data/images/indexed-1bit.png +0 -0
  208. data/test/data/images/indexed-2bit.png +0 -0
  209. data/test/data/images/indexed-4bit.png +0 -0
  210. data/test/data/images/indexed-8bit.png +0 -0
  211. data/test/data/images/indexed-alpha-4bit.png +0 -0
  212. data/test/data/images/indexed-alpha-8bit.png +0 -0
  213. data/test/data/images/rgb.jpg +0 -0
  214. data/test/data/images/truecolour-8bit.png +0 -0
  215. data/test/data/images/truecolour-alpha-8bit.png +0 -0
  216. data/test/data/images/truecolour-gama-chrm-8bit.png +0 -0
  217. data/test/data/images/truecolour-srgb-8bit.png +0 -0
  218. data/test/data/minimal.pdf +44 -0
  219. data/test/data/standard-security-handler/README +9 -0
  220. data/test/data/standard-security-handler/bothpwd-aes-128bit-V4.pdf +44 -0
  221. data/test/data/standard-security-handler/bothpwd-aes-256bit-V5.pdf +0 -0
  222. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V2.pdf +43 -0
  223. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V4.pdf +43 -0
  224. data/test/data/standard-security-handler/bothpwd-arc4-40bit-V1.pdf +0 -0
  225. data/test/data/standard-security-handler/nopwd-aes-128bit-V4.pdf +43 -0
  226. data/test/data/standard-security-handler/nopwd-aes-256bit-V5.pdf +0 -0
  227. data/test/data/standard-security-handler/nopwd-arc4-128bit-V2.pdf +43 -0
  228. data/test/data/standard-security-handler/nopwd-arc4-128bit-V4.pdf +43 -0
  229. data/test/data/standard-security-handler/nopwd-arc4-40bit-V1.pdf +43 -0
  230. data/test/data/standard-security-handler/ownerpwd-aes-128bit-V4.pdf +0 -0
  231. data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5.pdf +43 -0
  232. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V2.pdf +43 -0
  233. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V4.pdf +43 -0
  234. data/test/data/standard-security-handler/ownerpwd-arc4-40bit-V1.pdf +43 -0
  235. data/test/data/standard-security-handler/userpwd-aes-128bit-V4.pdf +43 -0
  236. data/test/data/standard-security-handler/userpwd-aes-256bit-V5.pdf +43 -0
  237. data/test/data/standard-security-handler/userpwd-arc4-128bit-V2.pdf +0 -0
  238. data/test/data/standard-security-handler/userpwd-arc4-128bit-V4.pdf +0 -0
  239. data/test/data/standard-security-handler/userpwd-arc4-40bit-V1.pdf +43 -0
  240. data/test/hexapdf/common_tokenizer_tests.rb +204 -0
  241. data/test/hexapdf/content/common.rb +31 -0
  242. data/test/hexapdf/content/graphic_object/test_arc.rb +93 -0
  243. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +91 -0
  244. data/test/hexapdf/content/graphic_object/test_solid_arc.rb +86 -0
  245. data/test/hexapdf/content/test_canvas.rb +1113 -0
  246. data/test/hexapdf/content/test_color_space.rb +97 -0
  247. data/test/hexapdf/content/test_graphics_state.rb +138 -0
  248. data/test/hexapdf/content/test_operator.rb +619 -0
  249. data/test/hexapdf/content/test_parser.rb +66 -0
  250. data/test/hexapdf/content/test_processor.rb +156 -0
  251. data/test/hexapdf/content/test_transformation_matrix.rb +64 -0
  252. data/test/hexapdf/encryption/common.rb +87 -0
  253. data/test/hexapdf/encryption/test_aes.rb +121 -0
  254. data/test/hexapdf/encryption/test_arc4.rb +39 -0
  255. data/test/hexapdf/encryption/test_fast_aes.rb +17 -0
  256. data/test/hexapdf/encryption/test_fast_arc4.rb +12 -0
  257. data/test/hexapdf/encryption/test_identity.rb +21 -0
  258. data/test/hexapdf/encryption/test_ruby_aes.rb +23 -0
  259. data/test/hexapdf/encryption/test_ruby_arc4.rb +20 -0
  260. data/test/hexapdf/encryption/test_security_handler.rb +356 -0
  261. data/test/hexapdf/encryption/test_standard_security_handler.rb +274 -0
  262. data/test/hexapdf/filter/common.rb +53 -0
  263. data/test/hexapdf/filter/test_ascii85_decode.rb +60 -0
  264. data/test/hexapdf/filter/test_ascii_hex_decode.rb +33 -0
  265. data/test/hexapdf/filter/test_encryption.rb +24 -0
  266. data/test/hexapdf/filter/test_flate_decode.rb +35 -0
  267. data/test/hexapdf/filter/test_lzw_decode.rb +52 -0
  268. data/test/hexapdf/filter/test_predictor.rb +183 -0
  269. data/test/hexapdf/filter/test_run_length_decode.rb +32 -0
  270. data/test/hexapdf/font/cmap/test_parser.rb +67 -0
  271. data/test/hexapdf/font/cmap/test_writer.rb +58 -0
  272. data/test/hexapdf/font/encoding/test_base.rb +35 -0
  273. data/test/hexapdf/font/encoding/test_difference_encoding.rb +21 -0
  274. data/test/hexapdf/font/encoding/test_glyph_list.rb +59 -0
  275. data/test/hexapdf/font/encoding/test_zapf_dingbats_encoding.rb +16 -0
  276. data/test/hexapdf/font/test_encoding.rb +27 -0
  277. data/test/hexapdf/font/test_true_type_wrapper.rb +110 -0
  278. data/test/hexapdf/font/test_type1_wrapper.rb +66 -0
  279. data/test/hexapdf/font/true_type/common.rb +19 -0
  280. data/test/hexapdf/font/true_type/table/test_cmap.rb +59 -0
  281. data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +133 -0
  282. data/test/hexapdf/font/true_type/table/test_directory.rb +35 -0
  283. data/test/hexapdf/font/true_type/table/test_glyf.rb +58 -0
  284. data/test/hexapdf/font/true_type/table/test_head.rb +76 -0
  285. data/test/hexapdf/font/true_type/table/test_hhea.rb +40 -0
  286. data/test/hexapdf/font/true_type/table/test_hmtx.rb +38 -0
  287. data/test/hexapdf/font/true_type/table/test_loca.rb +43 -0
  288. data/test/hexapdf/font/true_type/table/test_maxp.rb +62 -0
  289. data/test/hexapdf/font/true_type/table/test_name.rb +95 -0
  290. data/test/hexapdf/font/true_type/table/test_os2.rb +65 -0
  291. data/test/hexapdf/font/true_type/table/test_post.rb +89 -0
  292. data/test/hexapdf/font/true_type/test_font.rb +120 -0
  293. data/test/hexapdf/font/true_type/test_table.rb +41 -0
  294. data/test/hexapdf/font/type1/test_afm_parser.rb +51 -0
  295. data/test/hexapdf/font/type1/test_font.rb +68 -0
  296. data/test/hexapdf/font/type1/test_pfb_parser.rb +37 -0
  297. data/test/hexapdf/font_loader/test_from_configuration.rb +28 -0
  298. data/test/hexapdf/font_loader/test_standard14.rb +22 -0
  299. data/test/hexapdf/image_loader/test_jpeg.rb +83 -0
  300. data/test/hexapdf/image_loader/test_pdf.rb +47 -0
  301. data/test/hexapdf/image_loader/test_png.rb +258 -0
  302. data/test/hexapdf/task/test_dereference.rb +46 -0
  303. data/test/hexapdf/task/test_optimize.rb +137 -0
  304. data/test/hexapdf/test_configuration.rb +82 -0
  305. data/test/hexapdf/test_data_dir.rb +32 -0
  306. data/test/hexapdf/test_dictionary.rb +284 -0
  307. data/test/hexapdf/test_dictionary_fields.rb +185 -0
  308. data/test/hexapdf/test_document.rb +574 -0
  309. data/test/hexapdf/test_document_utils.rb +144 -0
  310. data/test/hexapdf/test_filter.rb +96 -0
  311. data/test/hexapdf/test_font_utils.rb +47 -0
  312. data/test/hexapdf/test_importer.rb +78 -0
  313. data/test/hexapdf/test_object.rb +177 -0
  314. data/test/hexapdf/test_parser.rb +394 -0
  315. data/test/hexapdf/test_rectangle.rb +36 -0
  316. data/test/hexapdf/test_reference.rb +41 -0
  317. data/test/hexapdf/test_revision.rb +139 -0
  318. data/test/hexapdf/test_revisions.rb +93 -0
  319. data/test/hexapdf/test_serializer.rb +169 -0
  320. data/test/hexapdf/test_stream.rb +262 -0
  321. data/test/hexapdf/test_tokenizer.rb +30 -0
  322. data/test/hexapdf/test_writer.rb +120 -0
  323. data/test/hexapdf/test_xref_section.rb +35 -0
  324. data/test/hexapdf/type/test_catalog.rb +30 -0
  325. data/test/hexapdf/type/test_embedded_file.rb +16 -0
  326. data/test/hexapdf/type/test_file_specification.rb +148 -0
  327. data/test/hexapdf/type/test_font.rb +35 -0
  328. data/test/hexapdf/type/test_font_descriptor.rb +51 -0
  329. data/test/hexapdf/type/test_font_simple.rb +190 -0
  330. data/test/hexapdf/type/test_font_type1.rb +128 -0
  331. data/test/hexapdf/type/test_form.rb +60 -0
  332. data/test/hexapdf/type/test_info.rb +14 -0
  333. data/test/hexapdf/type/test_names.rb +9 -0
  334. data/test/hexapdf/type/test_object_stream.rb +84 -0
  335. data/test/hexapdf/type/test_page.rb +260 -0
  336. data/test/hexapdf/type/test_page_tree_node.rb +255 -0
  337. data/test/hexapdf/type/test_resources.rb +167 -0
  338. data/test/hexapdf/type/test_trailer.rb +109 -0
  339. data/test/hexapdf/type/test_xref_stream.rb +131 -0
  340. data/test/hexapdf/utils/test_bit_field.rb +47 -0
  341. data/test/hexapdf/utils/test_lru_cache.rb +22 -0
  342. data/test/hexapdf/utils/test_object_hash.rb +115 -0
  343. data/test/hexapdf/utils/test_pdf_doc_encoding.rb +18 -0
  344. data/test/hexapdf/utils/test_sorted_tree_node.rb +232 -0
  345. data/test/test_helper.rb +56 -0
  346. metadata +427 -0
@@ -0,0 +1,616 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2016 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #++
33
+
34
+ require 'hexapdf/encryption/security_handler'
35
+ require 'digest/md5'
36
+ require 'digest/sha2'
37
+
38
+ module HexaPDF
39
+ module Encryption
40
+
41
+ # The specialized encryption dictionary for the StandardSecurityHandler.
42
+ #
43
+ # Contains additional fields that are used for storing the information needed for retrieving
44
+ # the encryption key and a set of permissions.
45
+ class StandardEncryptionDictionary < EncryptionDictionary
46
+
47
+ define_field :R, type: Integer, required: true
48
+ define_field :O, type: PDFByteString, required: true
49
+ define_field :OE, type: PDFByteString, version: '2.0'
50
+ define_field :U, type: PDFByteString, required: true
51
+ define_field :UE, type: PDFByteString, version: '2.0'
52
+ define_field :P, type: Integer, required: true
53
+ define_field :Perms, type: PDFByteString, version: '2.0'
54
+ define_field :EncryptMetadata, type: Boolean, default: true, version: '1.5'
55
+
56
+ private
57
+
58
+ # Validates the fields special for this encryption dictionary.
59
+ def perform_validation
60
+ super
61
+ case value[:R]
62
+ when 2, 3, 4
63
+ if value[:U].length != 32 || value[:O].length != 32
64
+ yield("Invalid size for /U or /O values for revisions <= 4", false)
65
+ end
66
+ when 6
67
+ if !key?(:OE) || !key?(:UE) || !key?(:Perms)
68
+ yield("Value of /OE, /UE or /Perms is missing for dictionary revision 6", false)
69
+ end
70
+ if value[:U].length != 48 || value[:O].length != 48 || value[:UE].length == 32 ||
71
+ value[:OE].length != 32 || value[:Perms].length != 16
72
+ yield("Invalid size for /U, /O, /UE, /OE or /Perms values for revisions 6", false)
73
+ end
74
+ else
75
+ yield("Value of /R is not one of 2, 3, 4 or 6", false)
76
+ end
77
+ end
78
+
79
+ end
80
+
81
+
82
+ # The password-based standard security handler of the PDF specification, identified by a
83
+ # /Filter value of /Standard.
84
+ #
85
+ # == Overview
86
+ #
87
+ # The PDF specification defines one security handler that should be implemented by all PDF
88
+ # conform libraries and applications. This standard security handler allows access permissions
89
+ # and a user password as well as an owner password to be set. See
90
+ # StandardSecurityHandler::EncryptionOptions for all valid options that can be used with this
91
+ # security handler.
92
+ #
93
+ # The access permissions (see StandardSecurityHandler::Permissions) can be used to restrict what
94
+ # a user is allowed to do with a PDF file.
95
+ #
96
+ # When a user or owner password is specified, a PDF file can only be opened when the correct
97
+ # password is supplied.
98
+ #
99
+ # See: PDF1.7 s7.6.3, PDF2.0 s7.6.3
100
+ class StandardSecurityHandler < SecurityHandler
101
+
102
+
103
+ # Defines all available permissions.
104
+ #
105
+ # It is possible to use an array of permission symbols instead of an integer to describe the
106
+ # permission set. The used symbols are the lower case versions of the constants, i.e. the
107
+ # symbol for MODIFY_CONSTANT would be :modify_constant.
108
+ #
109
+ # See: PDF1.7 s7.6.3.2
110
+ module Permissions
111
+
112
+ # Printing (if HIGH_QUALITY_PRINT is also set, then high quality printing is allowed)
113
+ PRINT = 1 << 2
114
+
115
+ # Modification of the content by operations that are different from those controller by
116
+ # MODIFY_ANNOTATION, FILL_IN_FORMS and ASSEMBLE_DOCUMENT
117
+ MODIFY_CONTENT = 1 << 3
118
+
119
+ # Copying of content
120
+ COPY_CONTENT = 1 << 4
121
+
122
+ # Modifying annotations
123
+ MODIFY_ANNOTATION = 1 << 5
124
+
125
+ # Filling in form fields
126
+ FILL_IN_FORMS = 1 << 8
127
+
128
+ # Extracting content
129
+ EXTRACT_CONTENT = 1 << 9
130
+
131
+ # Assembling of the document (inserting, rotating or deleting of pages and creation of
132
+ # bookmarks or thumbnail images)
133
+ ASSEMBLE_DOCUMENT = 1 << 10
134
+
135
+ # High quality printing
136
+ HIGH_QUALITY_PRINT = 1 << 11
137
+
138
+ # Allows everything
139
+ ALL = PRINT | MODIFY_CONTENT | COPY_CONTENT | MODIFY_ANNOTATION | FILL_IN_FORMS |
140
+ EXTRACT_CONTENT | ASSEMBLE_DOCUMENT | HIGH_QUALITY_PRINT
141
+
142
+ # Reserved permission bits
143
+ RESERVED = 0xFFFFF000
144
+
145
+ # Maps permission symbols to their respective value
146
+ SYMBOL_TO_PERMISSION = {
147
+ print: PRINT,
148
+ modify_content: MODIFY_CONTENT,
149
+ copy_content: COPY_CONTENT,
150
+ modify_annotation: MODIFY_ANNOTATION,
151
+ fill_in_forms: FILL_IN_FORMS,
152
+ extract_content: EXTRACT_CONTENT,
153
+ assemble_document: ASSEMBLE_DOCUMENT,
154
+ high_quality_print: HIGH_QUALITY_PRINT,
155
+ }
156
+
157
+ # Maps a permission value to its symbol
158
+ PERMISSION_TO_SYMBOL = {
159
+ PRINT => :print,
160
+ MODIFY_CONTENT => :modify_content,
161
+ COPY_CONTENT => :copy_content,
162
+ MODIFY_ANNOTATION => :modify_annotation,
163
+ FILL_IN_FORMS => :fill_in_forms,
164
+ EXTRACT_CONTENT => :extract_content,
165
+ ASSEMBLE_DOCUMENT => :assemble_document,
166
+ HIGH_QUALITY_PRINT => :high_quality_print,
167
+ }
168
+
169
+ end
170
+
171
+
172
+ # Defines all possible options that can be passed to a StandardSecurityHandler when setting
173
+ # up encryption.
174
+ class EncryptionOptions
175
+
176
+ # The user password. If this attribute is not specified but the virtual +password+
177
+ # attribute is, then the latter is used.
178
+ attr_accessor :user_password
179
+
180
+ # The owner password. If this attribute is not specified but the virtual +password+
181
+ # attribute is, then the latter is used.
182
+ attr_accessor :owner_password
183
+
184
+ # The permissions. Either an integer with the needed permission bits set or an array of
185
+ # permission symbols.
186
+ #
187
+ # See: Permissions
188
+ attr_accessor :permissions
189
+
190
+ # The encryption algorithm.
191
+ attr_accessor :algorithm
192
+
193
+ # Specifies whether metadata should be encrypted.
194
+ attr_accessor :encrypt_metadata
195
+
196
+ # :nodoc:
197
+ def initialize(data = {})
198
+ fallback_pwd = data.delete(:password) { '' }
199
+ @user_password = data.delete(:user_password) { fallback_pwd }
200
+ @owner_password = data.delete(:owner_password) { fallback_pwd }
201
+ @permissions = process_permissions(data.delete(:permissions) { Permissions::ALL })
202
+ @algorithm = data.delete(:algorithm) { :arc4 }
203
+ @encrypt_metadata = data.delete(:encrypt_metadata) { true }
204
+ if data.size > 0
205
+ raise ArgumentError, "Invalid encryption options: #{data.keys.join(', ')}"
206
+ end
207
+ end
208
+
209
+ private
210
+
211
+ # Maps the permissions to an integer for use by the standard security handler.
212
+ def process_permissions(perms)
213
+ if perms.kind_of?(Array)
214
+ perms = perms.inject(0) do |result, perm|
215
+ result | Permissions::SYMBOL_TO_PERMISSION.fetch(perm, 0)
216
+ end
217
+ end
218
+ Permissions::RESERVED | perms
219
+ end
220
+
221
+ end
222
+
223
+ # Additionally checks that the document trailer's ID has not changed.
224
+ #
225
+ # See: SecurityHandler#encryption_key_valid?
226
+ def encryption_key_valid?
227
+ super && trailer_id_hash == @trailer_id_hash
228
+ end
229
+
230
+ # Returns the permissions of the managed dictionary as array of symbol values.
231
+ #
232
+ # See: Permissions
233
+ def permissions
234
+ Permissions::PERMISSION_TO_SYMBOL.each_with_object([]) do |(perm, sym), result|
235
+ result << sym if dict[:P] & perm == perm
236
+ end
237
+ end
238
+
239
+ private
240
+
241
+ # Prepares the security handler for use in encrypting the document.
242
+ #
243
+ # See the attributes of the EncryptionOptions class for all possible arguments.
244
+ def prepare_encryption(**kwoptions)
245
+ options = EncryptionOptions.new(kwoptions)
246
+
247
+ dict[:Filter] = :Standard
248
+ dict[:R] = case dict[:V]
249
+ when 1 then 2
250
+ when 2 then 3
251
+ when 4 then 4
252
+ when 5 then 6
253
+ end
254
+ dict[:EncryptMetadata] = options.encrypt_metadata
255
+ dict[:P] = options.permissions
256
+
257
+ if dict[:V] >= 4
258
+ cfm = if options.algorithm == :arc4
259
+ :V2
260
+ elsif key_length == 16
261
+ :AESV2
262
+ else
263
+ :AESV3
264
+ end
265
+ dict[:CF] = {
266
+ StdCF: {
267
+ CFM: cfm,
268
+ AuthEvent: :DocOpen,
269
+ Length: key_length,
270
+ },
271
+ }
272
+ dict[:StmF] = dict[:StrF] = dict[:EFF] = :StdCF
273
+ end
274
+
275
+ if dict[:R] <= 4 && !document.trailer[:ID].kind_of?(Array)
276
+ document.trailer.set_random_id
277
+ end
278
+
279
+ options.user_password = prepare_password(options.user_password)
280
+ options.owner_password = prepare_password(options.owner_password)
281
+
282
+ dict[:O] = compute_o_field(options.owner_password, options.user_password)
283
+ dict[:U] = compute_u_field(options.user_password)
284
+
285
+ if dict[:R] <= 4
286
+ encryption_key = compute_user_encryption_key(options.user_password)
287
+ else
288
+ encryption_key = random_bytes(32)
289
+ dict[:UE] = compute_ue_field(options.user_password, encryption_key)
290
+ dict[:OE] = compute_oe_field(options.owner_password, encryption_key)
291
+ dict[:Perms] = compute_perms_field(encryption_key)
292
+ end
293
+
294
+ @trailer_id_hash = trailer_id_hash
295
+ [encryption_key, options.algorithm, options.algorithm, options.algorithm]
296
+ end
297
+
298
+ # Uses the given password (or the default password if none given) to retrieve the encryption
299
+ # key.
300
+ #
301
+ # If the optional +check_permissions+ argument is +true+, the permissions for files
302
+ # encrypted with revision 6 are checked. Otherwise, permission changes are ignored.
303
+ def prepare_decryption(password: '', check_permissions: true)
304
+ if dict[:Filter] != :Standard
305
+ raise(HexaPDF::UnsupportedEncryptionError,
306
+ "Invalid /Filter value for standard security handler")
307
+ elsif ![2, 3, 4, 6].include?(dict[:R])
308
+ raise(HexaPDF::UnsupportedEncryptionError,
309
+ "Invalid /R value for standard security handler")
310
+ elsif dict[:R] <= 4 && !document.trailer[:ID].kind_of?(Array)
311
+ raise(HexaPDF::EncryptionError,
312
+ "Document ID for needed for decryption")
313
+ end
314
+ @trailer_id_hash = trailer_id_hash
315
+
316
+ password = prepare_password(password)
317
+
318
+ if user_password_valid?(prepare_password(''))
319
+ encryption_key = compute_user_encryption_key(prepare_password(''))
320
+ elsif user_password_valid?(password)
321
+ encryption_key = compute_user_encryption_key(password)
322
+ elsif owner_password_valid?(password)
323
+ encryption_key = compute_owner_encryption_key(password)
324
+ else
325
+ raise HexaPDF::EncryptionError, "Invalid password specified"
326
+ end
327
+
328
+ check_perms_field(encryption_key) if check_permissions && dict[:R] == 6
329
+
330
+ encryption_key
331
+ end
332
+
333
+ # Computes the hash value for the first string in the trailer ID array.
334
+ def trailer_id_hash # :nodoc:
335
+ id = document.unwrap(document.trailer[:ID])
336
+ (id.kind_of?(Array) ? id[0] : id).hash
337
+ end
338
+
339
+ # See SecurityHandler#encryption_dictionary_class
340
+ def encryption_dictionary_class
341
+ StandardEncryptionDictionary
342
+ end
343
+
344
+ # The padding used for passwords with fewer than 32 bytes. Only used for revisions <= 4.
345
+ #
346
+ # See: PDF1.7 s7.6.3.3
347
+ PASSWORD_PADDING = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08" \
348
+ "\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A".b
349
+
350
+ # Computes the user encryption key.
351
+ #
352
+ # For revisions <= 4 this is the *only* way for generating the encryption key needed to
353
+ # encrypt or decrypt a file.
354
+ #
355
+ # For revision 6 the file encryption key is a string of random bytes that has been encrypted
356
+ # with the user password. If the password is the owner password,
357
+ # #compute_owner_encryption_key has to be used instead.
358
+ #
359
+ # See: PDF1.7 s7.6.3.3 (algorithm 2), PDF2.0 s7.6.3.3.2 (algorithm 2.A (a)-(b),(e))
360
+ def compute_user_encryption_key(password)
361
+ if dict[:R] <= 4
362
+ data = password
363
+ data += dict[:O]
364
+ data << [dict[:P]].pack('V')
365
+ data << document.trailer[:ID][0]
366
+ data << [0xFFFFFFFF].pack('V') if dict[:R] == 4 && !dict[:EncryptMetadata]
367
+
368
+ n = key_length
369
+ data = Digest::MD5.digest(data)
370
+ if dict[:R] >= 3
371
+ 50.times { data = Digest::MD5.digest(data[0, n]) }
372
+ end
373
+
374
+ data[0, n]
375
+ elsif dict[:R] == 6
376
+ key = compute_hash(password, dict[:U][40, 8])
377
+ aes_algorithm.new(key, "\0" * 16, :decrypt).process(dict[:UE])
378
+ end
379
+ end
380
+
381
+ # Computes the owner encryption key.
382
+ #
383
+ # For revisions <= 4 this is done by first retrieving the user password through the use of
384
+ # the owner password and then using the #compute_user_encryption_key method.
385
+ #
386
+ # For revision 6 file encryption key is a string of random bytes that has been encrypted
387
+ # with the owner password. If the password is the user password,
388
+ # #compute_user_encryption_key has to be used.
389
+ #
390
+ # See: PDF2.0 s7.6.3.3.2 (algorithm 2.A (a)-(d))
391
+ def compute_owner_encryption_key(password)
392
+ if dict[:R] <= 4
393
+ compute_user_encryption_key(user_password_from_owner_password(password))
394
+ elsif dict[:R] == 6
395
+ key = compute_hash(password, dict[:O][40, 8], dict[:U])
396
+ aes_algorithm.new(key, "\0" * 16, :decrypt).process(dict[:OE])
397
+ end
398
+ end
399
+
400
+ # Computes the encryption dictionary's /O (owner password) value.
401
+ #
402
+ # Short explanation: For revisions <= 4 the user password is encrypted with a key based on
403
+ # the owner password. For revision 6 the /O value is a hash computed from the password and
404
+ # the /U value with added validation and key salts.
405
+ #
406
+ # *Attention*: If revision 6 is used, the /U value has to be computed and set before this
407
+ # method is used, otherwise the return value is incorrect!
408
+ #
409
+ # See: PDF1.7 s7.6.3.4 (algorithm 3), PDF2.0 s7.6.3.4.7 (algorithm 9 (a))
410
+ def compute_o_field(owner_password, user_password)
411
+ if dict[:R] <= 4
412
+ data = Digest::MD5.digest(owner_password)
413
+ if dict[:R] >= 3
414
+ 50.times { data = Digest::MD5.digest(data) }
415
+ end
416
+ key = data[0, key_length]
417
+
418
+ data = arc4_algorithm.encrypt(key, user_password)
419
+ if dict[:R] >= 3
420
+ 19.times {|i| data = arc4_algorithm.encrypt(xor_key(key, i + 1), data)}
421
+ end
422
+
423
+ data
424
+ elsif dict[:R] == 6
425
+ validation_salt = random_bytes(8)
426
+ key_salt = random_bytes(8)
427
+ compute_hash(owner_password, validation_salt, dict[:U]) << validation_salt << key_salt
428
+ end
429
+ end
430
+
431
+ # Computes the encryption dictionary's /OE (owner encryption key) value (for revision 6
432
+ # only).
433
+ #
434
+ # Short explanation: Encrypts the file encryption key with a key based on the password and
435
+ # the /O and /U values.
436
+ #
437
+ # See: PDF2.0 s7.6.3.4.7 (algorithm 9 (b))
438
+ def compute_oe_field(password, file_encryption_key)
439
+ key = compute_hash(password, dict[:O][40, 8], dict[:U])
440
+ aes_algorithm.new(key, "\0" * 16, :encrypt).process(file_encryption_key)
441
+ end
442
+
443
+ # Computes the encryption dictionary's /U (user password) value.
444
+ #
445
+ # Short explanation: For revisions <= 4, the password padding string is encrypted with a key
446
+ # based on the user password. For revision 6 the /U value is a hash computed from the
447
+ # password with added validation and key salts.
448
+ #
449
+ # See: PDF1.7 s7.6.3.4 (algorithm 4 for R=2, algorithm 5 for R=3 and R=4)
450
+ # PDF2.0 s7.6.3.4.6 (algorithm 8 (a) for R=6)
451
+ def compute_u_field(password)
452
+ if dict[:R] == 2
453
+ key = compute_user_encryption_key(password)
454
+ arc4_algorithm.encrypt(key, PASSWORD_PADDING)
455
+ elsif dict[:R] <= 4
456
+ key = compute_user_encryption_key(password)
457
+ data = Digest::MD5.digest(PASSWORD_PADDING + document.trailer[:ID][0])
458
+ data = arc4_algorithm.encrypt(key, data)
459
+ 19.times {|i| data = arc4_algorithm.encrypt(xor_key(key, i + 1), data)}
460
+ data << "hexapdfhexapdfhe"
461
+ elsif dict[:R] == 6
462
+ validation_salt = random_bytes(8)
463
+ key_salt = random_bytes(8)
464
+ compute_hash(password, validation_salt) << validation_salt << key_salt
465
+ end
466
+ end
467
+
468
+ # Computes the encryption dictionary's /UE (user encryption key) value (for revision 6
469
+ # only).
470
+ #
471
+ # Short explanation: Encrypts the file encryption key with a key based on the password and
472
+ # the /U value.
473
+ #
474
+ # See: PDF2.0 s7.6.3.4.6 (algorithm 8 (b))
475
+ def compute_ue_field(password, file_encryption_key)
476
+ key = compute_hash(password, dict[:U][40, 8])
477
+ aes_algorithm.new(key, "\0" * 16, :encrypt).process(file_encryption_key)
478
+ end
479
+
480
+ # Computes the encryption dictionary's /Perms (permissions) value (for revision 6 only).
481
+ #
482
+ # Uses /P and /EncryptMetadata values, so these have to be set beforehand.
483
+ #
484
+ # See: PDF2.0 s7.6.3.4.8 (algorithm 10)
485
+ def compute_perms_field(file_encryption_key)
486
+ data = [dict[:P]].pack('V')
487
+ data << [0xFFFFFFFF].pack('V')
488
+ data << (dict[:EncryptMetadata] ? 'T' : 'F')
489
+ data << 'adb'
490
+ data << 'hexa'
491
+ aes_algorithm.new(file_encryption_key, "\0" * 16, :encrypt).process(data)
492
+ end
493
+
494
+ # Authenticates the user password, i.e. decides whether the given user password is valid.
495
+ #
496
+ # See: PDF1.7 s7.6.3.4 (algorithm 6), PDF2.0 s7.6.3.4.9 (algorithm 11)
497
+ def user_password_valid?(password)
498
+ if dict[:R] == 2
499
+ compute_u_field(password) == dict[:U]
500
+ elsif dict[:R] <= 4
501
+ compute_u_field(password)[0, 16] == dict[:U][0, 16]
502
+ elsif dict[:R] == 6
503
+ compute_hash(password, dict[:U][32, 8]) == dict[:U][0, 32]
504
+ end
505
+ end
506
+
507
+ # Authenticates the owner password, i.e. decides whether the given owner password is valid.
508
+ #
509
+ # See: PDF1.7 s7.6.3.4 (algorithm 7), PDF2.0 s7.6.3.4.10 (algorithm 12)
510
+ def owner_password_valid?(password)
511
+ if dict[:R] <= 4
512
+ user_password_valid?(user_password_from_owner_password(password))
513
+ elsif dict[:R] == 6
514
+ compute_hash(password, dict[:O][32, 8], dict[:U]) == dict[:O][0, 32]
515
+ end
516
+ end
517
+
518
+ # Checks if the decrypted /Perms entry matches the /P and /EncryptMetadata entries.
519
+ #
520
+ # This method can only be used for revision 6.
521
+ #
522
+ # See: PDF2.0 s7.6.3.4.11 (algorithm 13)
523
+ def check_perms_field(encryption_key)
524
+ decrypted = aes_algorithm.new(encryption_key, "\0" * 16, :decrypt).process(dict[:Perms])
525
+ if decrypted[9, 3] != "adb"
526
+ raise HexaPDF::EncryptionError, "/Perms field cannot be decrypted"
527
+ elsif (dict[:P] & 0xFFFFFFFF) != (decrypted[0, 4].unpack('V').first & 0xFFFFFFFF)
528
+ raise HexaPDF::EncryptionError, "Decrypted permissions don't match /P"
529
+ elsif decrypted[8] != (dict[:EncryptMetadata] ? 'T' : 'F')
530
+ raise HexaPDF::EncryptionError, "Decrypted /Perms field doesn't match /EncryptMetadata"
531
+ end
532
+ end
533
+
534
+ # Returns the user password when given the owner password for revisions <= 4.
535
+ #
536
+ # See: PDF1.7 s7.6.3.4 (algorithm 7 (a) and (b))
537
+ def user_password_from_owner_password(owner_password)
538
+ data = Digest::MD5.digest(owner_password)
539
+ if dict[:R] >= 3
540
+ 50.times { data = Digest::MD5.digest(data) }
541
+ end
542
+ key = data[0, key_length]
543
+
544
+ if dict[:R] == 2
545
+ userpwd = arc4_algorithm.decrypt(key, dict[:O])
546
+ else
547
+ userpwd = dict[:O]
548
+ 20.times {|i| userpwd = arc4_algorithm.decrypt(xor_key(key, 19 - i), userpwd)}
549
+ end
550
+
551
+ userpwd
552
+ end
553
+
554
+ # Computes a hash that is used extensively for all operations in security handlers of
555
+ # revision 6.
556
+ #
557
+ # Note: The original input (as defined by the spec) is calculated as
558
+ # "#{password}#{salt}#{user_key}" where +user_key+ has to be empty when doing operations
559
+ # with the user password.
560
+ #
561
+ # See: PDF2.0 s7.6.3.3.3 (algorithm 2.B)
562
+ def compute_hash(password, salt, user_key = '')
563
+ k = Digest::SHA256.digest("#{password}#{salt}#{user_key}")
564
+ e = ''
565
+
566
+ i = 0
567
+ while i < 64 || e.getbyte(-1) > i - 32
568
+ k1 = "#{password}#{k}#{user_key}" * 64
569
+ e = aes_algorithm.new(k[0, 16], k[16, 16], :encrypt).process(k1)
570
+ k = case e.unpack('C16').inject(&:+) % 3 # 256 % 3 == 1 % 3 --> x*256 % 3 == x % 3
571
+ when 0 then Digest::SHA256.digest(e)
572
+ when 1 then Digest::SHA384.digest(e)
573
+ when 2 then Digest::SHA512.digest(e)
574
+ end
575
+ i += 1
576
+ end
577
+
578
+ k[0, 32]
579
+ end
580
+
581
+ # Returns the password modified so that if follows certain rules:
582
+ #
583
+ # * For revisions <= 4, the password is converted into ISO-8859-1 encoding, padded with
584
+ # PASSWORD_PADDING and truncated to a maximum of 32 bytes.
585
+ #
586
+ # * For revision 6 the password is converted into UTF-8 encoding that is normalized
587
+ # according to the PDF2.0 specification.
588
+ #
589
+ # See: PDF1.7 s7.6.3.3 (algorithm 2 step a)),
590
+ # PDF2.0 s7.6.3.3.2 (algorithm 2.A steps a) and b))
591
+ def prepare_password(password)
592
+ if dict[:R] <= 4
593
+ password.to_s[0, 32].encode(Encoding::ISO_8859_1).force_encoding(Encoding::BINARY).
594
+ ljust(32, PASSWORD_PADDING)
595
+ elsif dict[:R] == 6
596
+ password.to_s.encode(Encoding::UTF_8).force_encoding(Encoding::BINARY)[0, 127]
597
+ end
598
+ rescue Encoding::UndefinedConversionError => e
599
+ raise HexaPDF::EncryptionError, "Invalid character in password: #{e.error_char}"
600
+ end
601
+
602
+ # XORs each byte of the String +key+ with value and returns the resulting string.
603
+ def xor_key(key, value)
604
+ new_key = key.dup
605
+ i = 0
606
+ while i < new_key.length
607
+ new_key.setbyte(i, new_key.getbyte(i) ^ value)
608
+ i += 1
609
+ end
610
+ new_key
611
+ end
612
+
613
+ end
614
+
615
+ end
616
+ end