hexapdf 0.17.1 → 0.17.2

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 (255) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -0
  3. data/LICENSE +29 -0
  4. data/README.md +129 -0
  5. data/Rakefile +109 -0
  6. data/agpl-3.0.txt +661 -0
  7. data/examples/001-hello_world.rb +16 -0
  8. data/examples/002-graphics.rb +275 -0
  9. data/examples/003-arcs.rb +50 -0
  10. data/examples/004-optimizing.rb +23 -0
  11. data/examples/005-merging.rb +27 -0
  12. data/examples/006-standard_pdf_fonts.rb +73 -0
  13. data/examples/007-truetype.rb +42 -0
  14. data/examples/008-show_char_bboxes.rb +55 -0
  15. data/examples/009-text_layouter_alignment.rb +47 -0
  16. data/examples/010-text_layouter_inline_boxes.rb +64 -0
  17. data/examples/011-text_layouter_line_wrapping.rb +57 -0
  18. data/examples/012-text_layouter_styling.rb +122 -0
  19. data/examples/013-text_layouter_shapes.rb +176 -0
  20. data/examples/014-text_in_polygon.rb +60 -0
  21. data/examples/015-boxes.rb +76 -0
  22. data/examples/016-frame_automatic_box_placement.rb +90 -0
  23. data/examples/017-frame_text_flow.rb +60 -0
  24. data/examples/018-composer.rb +44 -0
  25. data/examples/019-acro_form.rb +88 -0
  26. data/examples/emoji-smile.png +0 -0
  27. data/examples/emoji-wink.png +0 -0
  28. data/examples/machupicchu.jpg +0 -0
  29. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -0
  30. data/lib/hexapdf/content/graphic_object/geom2d.rb +13 -0
  31. data/lib/hexapdf/version.rb +1 -1
  32. data/test/data/aes-test-vectors/CBCGFSbox-128-decrypt.data.gz +0 -0
  33. data/test/data/aes-test-vectors/CBCGFSbox-128-encrypt.data.gz +0 -0
  34. data/test/data/aes-test-vectors/CBCGFSbox-192-decrypt.data.gz +0 -0
  35. data/test/data/aes-test-vectors/CBCGFSbox-192-encrypt.data.gz +0 -0
  36. data/test/data/aes-test-vectors/CBCGFSbox-256-decrypt.data.gz +0 -0
  37. data/test/data/aes-test-vectors/CBCGFSbox-256-encrypt.data.gz +0 -0
  38. data/test/data/aes-test-vectors/CBCKeySbox-128-decrypt.data.gz +0 -0
  39. data/test/data/aes-test-vectors/CBCKeySbox-128-encrypt.data.gz +0 -0
  40. data/test/data/aes-test-vectors/CBCKeySbox-192-decrypt.data.gz +0 -0
  41. data/test/data/aes-test-vectors/CBCKeySbox-192-encrypt.data.gz +0 -0
  42. data/test/data/aes-test-vectors/CBCKeySbox-256-decrypt.data.gz +0 -0
  43. data/test/data/aes-test-vectors/CBCKeySbox-256-encrypt.data.gz +0 -0
  44. data/test/data/aes-test-vectors/CBCVarKey-128-decrypt.data.gz +0 -0
  45. data/test/data/aes-test-vectors/CBCVarKey-128-encrypt.data.gz +0 -0
  46. data/test/data/aes-test-vectors/CBCVarKey-192-decrypt.data.gz +0 -0
  47. data/test/data/aes-test-vectors/CBCVarKey-192-encrypt.data.gz +0 -0
  48. data/test/data/aes-test-vectors/CBCVarKey-256-decrypt.data.gz +0 -0
  49. data/test/data/aes-test-vectors/CBCVarKey-256-encrypt.data.gz +0 -0
  50. data/test/data/aes-test-vectors/CBCVarTxt-128-decrypt.data.gz +0 -0
  51. data/test/data/aes-test-vectors/CBCVarTxt-128-encrypt.data.gz +0 -0
  52. data/test/data/aes-test-vectors/CBCVarTxt-192-decrypt.data.gz +0 -0
  53. data/test/data/aes-test-vectors/CBCVarTxt-192-encrypt.data.gz +0 -0
  54. data/test/data/aes-test-vectors/CBCVarTxt-256-decrypt.data.gz +0 -0
  55. data/test/data/aes-test-vectors/CBCVarTxt-256-encrypt.data.gz +0 -0
  56. data/test/data/fonts/Ubuntu-Title.ttf +0 -0
  57. data/test/data/images/cmyk.jpg +0 -0
  58. data/test/data/images/fillbytes.jpg +0 -0
  59. data/test/data/images/gray.jpg +0 -0
  60. data/test/data/images/greyscale-1bit.png +0 -0
  61. data/test/data/images/greyscale-2bit.png +0 -0
  62. data/test/data/images/greyscale-4bit.png +0 -0
  63. data/test/data/images/greyscale-8bit.png +0 -0
  64. data/test/data/images/greyscale-alpha-8bit.png +0 -0
  65. data/test/data/images/greyscale-trns-8bit.png +0 -0
  66. data/test/data/images/greyscale-with-gamma1.0.png +0 -0
  67. data/test/data/images/greyscale-with-gamma1.5.png +0 -0
  68. data/test/data/images/indexed-1bit.png +0 -0
  69. data/test/data/images/indexed-2bit.png +0 -0
  70. data/test/data/images/indexed-4bit.png +0 -0
  71. data/test/data/images/indexed-8bit.png +0 -0
  72. data/test/data/images/indexed-alpha-4bit.png +0 -0
  73. data/test/data/images/indexed-alpha-8bit.png +0 -0
  74. data/test/data/images/rgb.jpg +0 -0
  75. data/test/data/images/truecolour-8bit.png +0 -0
  76. data/test/data/images/truecolour-alpha-8bit.png +0 -0
  77. data/test/data/images/truecolour-gama-chrm-8bit.png +0 -0
  78. data/test/data/images/truecolour-srgb-8bit.png +0 -0
  79. data/test/data/images/ycck.jpg +0 -0
  80. data/test/data/minimal.pdf +44 -0
  81. data/test/data/standard-security-handler/README +9 -0
  82. data/test/data/standard-security-handler/bothpwd-aes-128bit-V4.pdf +44 -0
  83. data/test/data/standard-security-handler/bothpwd-aes-256bit-V5.pdf +0 -0
  84. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V2.pdf +43 -0
  85. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V4.pdf +43 -0
  86. data/test/data/standard-security-handler/bothpwd-arc4-40bit-V1.pdf +0 -0
  87. data/test/data/standard-security-handler/nopwd-aes-128bit-V4.pdf +43 -0
  88. data/test/data/standard-security-handler/nopwd-aes-256bit-V5.pdf +0 -0
  89. data/test/data/standard-security-handler/nopwd-arc4-128bit-V2.pdf +43 -0
  90. data/test/data/standard-security-handler/nopwd-arc4-128bit-V4.pdf +43 -0
  91. data/test/data/standard-security-handler/nopwd-arc4-40bit-V1.pdf +43 -0
  92. data/test/data/standard-security-handler/ownerpwd-aes-128bit-V4.pdf +0 -0
  93. data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5.pdf +43 -0
  94. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V2.pdf +43 -0
  95. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V4.pdf +43 -0
  96. data/test/data/standard-security-handler/ownerpwd-arc4-40bit-V1.pdf +43 -0
  97. data/test/data/standard-security-handler/userpwd-aes-128bit-V4.pdf +43 -0
  98. data/test/data/standard-security-handler/userpwd-aes-256bit-V5.pdf +43 -0
  99. data/test/data/standard-security-handler/userpwd-arc4-128bit-V2.pdf +0 -0
  100. data/test/data/standard-security-handler/userpwd-arc4-128bit-V4.pdf +0 -0
  101. data/test/data/standard-security-handler/userpwd-arc4-40bit-V1.pdf +43 -0
  102. data/test/hexapdf/common_tokenizer_tests.rb +236 -0
  103. data/test/hexapdf/content/common.rb +39 -0
  104. data/test/hexapdf/content/graphic_object/test_arc.rb +102 -0
  105. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +90 -0
  106. data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
  107. data/test/hexapdf/content/graphic_object/test_solid_arc.rb +86 -0
  108. data/test/hexapdf/content/test_canvas.rb +1279 -0
  109. data/test/hexapdf/content/test_color_space.rb +176 -0
  110. data/test/hexapdf/content/test_graphics_state.rb +151 -0
  111. data/test/hexapdf/content/test_operator.rb +619 -0
  112. data/test/hexapdf/content/test_parser.rb +99 -0
  113. data/test/hexapdf/content/test_processor.rb +163 -0
  114. data/test/hexapdf/content/test_transformation_matrix.rb +64 -0
  115. data/test/hexapdf/document/test_files.rb +72 -0
  116. data/test/hexapdf/document/test_fonts.rb +60 -0
  117. data/test/hexapdf/document/test_images.rb +72 -0
  118. data/test/hexapdf/document/test_pages.rb +130 -0
  119. data/test/hexapdf/encryption/common.rb +87 -0
  120. data/test/hexapdf/encryption/test_aes.rb +129 -0
  121. data/test/hexapdf/encryption/test_arc4.rb +39 -0
  122. data/test/hexapdf/encryption/test_fast_aes.rb +17 -0
  123. data/test/hexapdf/encryption/test_fast_arc4.rb +12 -0
  124. data/test/hexapdf/encryption/test_identity.rb +21 -0
  125. data/test/hexapdf/encryption/test_ruby_aes.rb +23 -0
  126. data/test/hexapdf/encryption/test_ruby_arc4.rb +20 -0
  127. data/test/hexapdf/encryption/test_security_handler.rb +380 -0
  128. data/test/hexapdf/encryption/test_standard_security_handler.rb +322 -0
  129. data/test/hexapdf/filter/common.rb +53 -0
  130. data/test/hexapdf/filter/test_ascii85_decode.rb +59 -0
  131. data/test/hexapdf/filter/test_ascii_hex_decode.rb +38 -0
  132. data/test/hexapdf/filter/test_crypt.rb +21 -0
  133. data/test/hexapdf/filter/test_encryption.rb +24 -0
  134. data/test/hexapdf/filter/test_flate_decode.rb +44 -0
  135. data/test/hexapdf/filter/test_lzw_decode.rb +52 -0
  136. data/test/hexapdf/filter/test_predictor.rb +219 -0
  137. data/test/hexapdf/filter/test_run_length_decode.rb +32 -0
  138. data/test/hexapdf/font/cmap/test_parser.rb +102 -0
  139. data/test/hexapdf/font/cmap/test_writer.rb +66 -0
  140. data/test/hexapdf/font/encoding/test_base.rb +45 -0
  141. data/test/hexapdf/font/encoding/test_difference_encoding.rb +29 -0
  142. data/test/hexapdf/font/encoding/test_glyph_list.rb +59 -0
  143. data/test/hexapdf/font/encoding/test_zapf_dingbats_encoding.rb +16 -0
  144. data/test/hexapdf/font/test_cmap.rb +104 -0
  145. data/test/hexapdf/font/test_encoding.rb +27 -0
  146. data/test/hexapdf/font/test_invalid_glyph.rb +34 -0
  147. data/test/hexapdf/font/test_true_type_wrapper.rb +186 -0
  148. data/test/hexapdf/font/test_type1_wrapper.rb +107 -0
  149. data/test/hexapdf/font/true_type/common.rb +17 -0
  150. data/test/hexapdf/font/true_type/table/common.rb +27 -0
  151. data/test/hexapdf/font/true_type/table/test_cmap.rb +47 -0
  152. data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +141 -0
  153. data/test/hexapdf/font/true_type/table/test_directory.rb +30 -0
  154. data/test/hexapdf/font/true_type/table/test_glyf.rb +58 -0
  155. data/test/hexapdf/font/true_type/table/test_head.rb +56 -0
  156. data/test/hexapdf/font/true_type/table/test_hhea.rb +26 -0
  157. data/test/hexapdf/font/true_type/table/test_hmtx.rb +30 -0
  158. data/test/hexapdf/font/true_type/table/test_kern.rb +61 -0
  159. data/test/hexapdf/font/true_type/table/test_loca.rb +33 -0
  160. data/test/hexapdf/font/true_type/table/test_maxp.rb +50 -0
  161. data/test/hexapdf/font/true_type/table/test_name.rb +76 -0
  162. data/test/hexapdf/font/true_type/table/test_os2.rb +55 -0
  163. data/test/hexapdf/font/true_type/table/test_post.rb +78 -0
  164. data/test/hexapdf/font/true_type/test_builder.rb +42 -0
  165. data/test/hexapdf/font/true_type/test_font.rb +116 -0
  166. data/test/hexapdf/font/true_type/test_optimizer.rb +26 -0
  167. data/test/hexapdf/font/true_type/test_subsetter.rb +73 -0
  168. data/test/hexapdf/font/true_type/test_table.rb +48 -0
  169. data/test/hexapdf/font/type1/common.rb +6 -0
  170. data/test/hexapdf/font/type1/test_afm_parser.rb +65 -0
  171. data/test/hexapdf/font/type1/test_font.rb +104 -0
  172. data/test/hexapdf/font/type1/test_font_metrics.rb +22 -0
  173. data/test/hexapdf/font/type1/test_pfb_parser.rb +37 -0
  174. data/test/hexapdf/font_loader/test_from_configuration.rb +43 -0
  175. data/test/hexapdf/font_loader/test_from_file.rb +36 -0
  176. data/test/hexapdf/font_loader/test_standard14.rb +33 -0
  177. data/test/hexapdf/image_loader/test_jpeg.rb +93 -0
  178. data/test/hexapdf/image_loader/test_pdf.rb +47 -0
  179. data/test/hexapdf/image_loader/test_png.rb +259 -0
  180. data/test/hexapdf/layout/test_box.rb +154 -0
  181. data/test/hexapdf/layout/test_frame.rb +350 -0
  182. data/test/hexapdf/layout/test_image_box.rb +73 -0
  183. data/test/hexapdf/layout/test_inline_box.rb +71 -0
  184. data/test/hexapdf/layout/test_line.rb +206 -0
  185. data/test/hexapdf/layout/test_style.rb +790 -0
  186. data/test/hexapdf/layout/test_text_box.rb +140 -0
  187. data/test/hexapdf/layout/test_text_fragment.rb +375 -0
  188. data/test/hexapdf/layout/test_text_layouter.rb +758 -0
  189. data/test/hexapdf/layout/test_text_shaper.rb +62 -0
  190. data/test/hexapdf/layout/test_width_from_polygon.rb +109 -0
  191. data/test/hexapdf/task/test_dereference.rb +51 -0
  192. data/test/hexapdf/task/test_optimize.rb +162 -0
  193. data/test/hexapdf/test_composer.rb +258 -0
  194. data/test/hexapdf/test_configuration.rb +93 -0
  195. data/test/hexapdf/test_data_dir.rb +32 -0
  196. data/test/hexapdf/test_dictionary.rb +340 -0
  197. data/test/hexapdf/test_dictionary_fields.rb +269 -0
  198. data/test/hexapdf/test_document.rb +641 -0
  199. data/test/hexapdf/test_filter.rb +100 -0
  200. data/test/hexapdf/test_importer.rb +106 -0
  201. data/test/hexapdf/test_object.rb +258 -0
  202. data/test/hexapdf/test_parser.rb +645 -0
  203. data/test/hexapdf/test_pdf_array.rb +169 -0
  204. data/test/hexapdf/test_rectangle.rb +73 -0
  205. data/test/hexapdf/test_reference.rb +50 -0
  206. data/test/hexapdf/test_revision.rb +188 -0
  207. data/test/hexapdf/test_revisions.rb +196 -0
  208. data/test/hexapdf/test_serializer.rb +195 -0
  209. data/test/hexapdf/test_stream.rb +274 -0
  210. data/test/hexapdf/test_tokenizer.rb +80 -0
  211. data/test/hexapdf/test_type.rb +18 -0
  212. data/test/hexapdf/test_writer.rb +140 -0
  213. data/test/hexapdf/test_xref_section.rb +61 -0
  214. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +795 -0
  215. data/test/hexapdf/type/acro_form/test_button_field.rb +308 -0
  216. data/test/hexapdf/type/acro_form/test_choice_field.rb +220 -0
  217. data/test/hexapdf/type/acro_form/test_field.rb +259 -0
  218. data/test/hexapdf/type/acro_form/test_form.rb +357 -0
  219. data/test/hexapdf/type/acro_form/test_signature_field.rb +38 -0
  220. data/test/hexapdf/type/acro_form/test_text_field.rb +201 -0
  221. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +88 -0
  222. data/test/hexapdf/type/actions/test_launch.rb +24 -0
  223. data/test/hexapdf/type/actions/test_uri.rb +23 -0
  224. data/test/hexapdf/type/annotations/test_markup_annotation.rb +22 -0
  225. data/test/hexapdf/type/annotations/test_text.rb +34 -0
  226. data/test/hexapdf/type/annotations/test_widget.rb +225 -0
  227. data/test/hexapdf/type/test_annotation.rb +97 -0
  228. data/test/hexapdf/type/test_catalog.rb +48 -0
  229. data/test/hexapdf/type/test_cid_font.rb +61 -0
  230. data/test/hexapdf/type/test_file_specification.rb +141 -0
  231. data/test/hexapdf/type/test_font.rb +67 -0
  232. data/test/hexapdf/type/test_font_descriptor.rb +61 -0
  233. data/test/hexapdf/type/test_font_simple.rb +176 -0
  234. data/test/hexapdf/type/test_font_true_type.rb +31 -0
  235. data/test/hexapdf/type/test_font_type0.rb +120 -0
  236. data/test/hexapdf/type/test_font_type1.rb +142 -0
  237. data/test/hexapdf/type/test_font_type3.rb +26 -0
  238. data/test/hexapdf/type/test_form.rb +120 -0
  239. data/test/hexapdf/type/test_image.rb +261 -0
  240. data/test/hexapdf/type/test_info.rb +9 -0
  241. data/test/hexapdf/type/test_object_stream.rb +117 -0
  242. data/test/hexapdf/type/test_page.rb +598 -0
  243. data/test/hexapdf/type/test_page_tree_node.rb +315 -0
  244. data/test/hexapdf/type/test_resources.rb +209 -0
  245. data/test/hexapdf/type/test_trailer.rb +116 -0
  246. data/test/hexapdf/type/test_xref_stream.rb +143 -0
  247. data/test/hexapdf/utils/test_bit_field.rb +63 -0
  248. data/test/hexapdf/utils/test_bit_stream.rb +69 -0
  249. data/test/hexapdf/utils/test_graphics_helpers.rb +37 -0
  250. data/test/hexapdf/utils/test_lru_cache.rb +22 -0
  251. data/test/hexapdf/utils/test_object_hash.rb +120 -0
  252. data/test/hexapdf/utils/test_pdf_doc_encoding.rb +18 -0
  253. data/test/hexapdf/utils/test_sorted_tree_node.rb +239 -0
  254. data/test/test_helper.rb +58 -0
  255. metadata +263 -3
@@ -0,0 +1,322 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/encryption/standard_security_handler'
5
+ require 'hexapdf/document'
6
+ require 'hexapdf/writer'
7
+ require 'stringio'
8
+
9
+ describe HexaPDF::Encryption::StandardEncryptionDictionary do
10
+ before do
11
+ @document = HexaPDF::Document.new
12
+ @dict = HexaPDF::Encryption::StandardEncryptionDictionary.new({}, document: @document)
13
+ @dict[:Filter] = :Standard
14
+ @dict[:V] = 1
15
+ @dict[:R] = 2
16
+ @dict[:U] = 'test' * 8
17
+ @dict[:O] = 'test' * 8
18
+ @dict[:P] = -5
19
+ @dict[:UE] = 'test' * 8
20
+ @dict[:OE] = 'test' * 8
21
+ @dict[:Perms] = 'test' * 8
22
+ end
23
+
24
+ it "validates the /R value" do
25
+ @dict[:R] = 2
26
+ assert(@dict.validate)
27
+ @dict[:R] = 5
28
+ refute(@dict.validate)
29
+ end
30
+
31
+ [:U, :O].each do |field|
32
+ it "validates the length of /#{field} field for R <= 4" do
33
+ @dict[field] = 'test'
34
+ refute(@dict.validate)
35
+ end
36
+ end
37
+
38
+ [:U, :O, :UE, :OE, :Perms].each do |field|
39
+ it "validates the length of /#{field} field for R=6" do
40
+ @dict[:R] = 6
41
+ @dict[field] = 'test'
42
+ refute(@dict.validate)
43
+ end
44
+ end
45
+
46
+ [:UE, :OE, :Perms].each do |field|
47
+ it "validates the existence of the /#{field} field for R=6" do
48
+ @dict[:R] = 6
49
+ @dict.delete(field)
50
+ refute(@dict.validate)
51
+ end
52
+ end
53
+ end
54
+
55
+ describe HexaPDF::Encryption::StandardSecurityHandler do
56
+ test_files = Dir[File.join(TEST_DATA_DIR, 'standard-security-handler', '*.pdf')].sort
57
+ user_password = 'uhexapdf'
58
+ owner_password = 'ohexapdf'
59
+
60
+ minimal_doc = HexaPDF::Document.new(io: StringIO.new(MINIMAL_PDF))
61
+
62
+ test_files.each do |file|
63
+ basename = File.basename(file)
64
+ it "can decrypt, encrypt and decrypt the encrypted file #{basename} with the user password" do
65
+ begin
66
+ doc = HexaPDF::Document.new(io: StringIO.new(File.binread(file)),
67
+ decryption_opts: {password: user_password})
68
+ assert_equal(minimal_doc.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
69
+
70
+ out = StringIO.new(''.b)
71
+ HexaPDF::Writer.new(doc, out).write
72
+ doc = HexaPDF::Document.new(io: out, decryption_opts: {password: user_password})
73
+ assert_equal(minimal_doc.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
74
+ rescue HexaPDF::EncryptionError => e
75
+ flunk("Error processing #{basename}: #{e}")
76
+ end
77
+ end
78
+
79
+ unless basename.start_with?("userpwd")
80
+ it "can decrypt the encrypted file #{basename} with the owner password" do
81
+ begin
82
+ doc = HexaPDF::Document.new(io: StringIO.new(File.binread(file)),
83
+ decryption_opts: {password: owner_password})
84
+ assert_equal(minimal_doc.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
85
+ rescue HexaPDF::EncryptionError => e
86
+ flunk("Error processing #{basename}: #{e}")
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ before do
93
+ @document = HexaPDF::Document.new
94
+ @handler = HexaPDF::Encryption::StandardSecurityHandler.new(@document)
95
+ end
96
+
97
+ it "can encrypt and then decrypt with all encryption variations" do
98
+ {arc4: [40, 48, 128], aes: [128, 256]}.each do |algorithm, key_lengths|
99
+ key_lengths.each do |key_length|
100
+ begin
101
+ doc = HexaPDF::Document.new
102
+ doc.encrypt(algorithm: algorithm, key_length: key_length)
103
+ sio = StringIO.new
104
+ doc.write(sio)
105
+ doc = HexaPDF::Document.new(io: sio)
106
+ assert_kind_of(Time, doc.trailer.info[:ModDate], "alg: #{algorithm} #{key_length} bits")
107
+ rescue HexaPDF::Error => e
108
+ flunk("Error using variation: #{algorithm} #{key_length} bits\n" << e.message)
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ describe "prepare_encryption" do
115
+ it "returns the encryption dictionary wrapped with a custom class" do
116
+ dict = @handler.set_up_encryption
117
+ assert_kind_of(HexaPDF::Encryption::StandardEncryptionDictionary, dict)
118
+ end
119
+
120
+ it "sets the correct revision independent /Filter value" do
121
+ dict = @handler.set_up_encryption
122
+ assert_equal(:Standard, dict[:Filter])
123
+ end
124
+
125
+ it "sets the correct revision independent /P value" do
126
+ dict = @handler.set_up_encryption
127
+ assert_equal(@handler.class::Permissions::ALL | \
128
+ @handler.class::Permissions::RESERVED - 2**32,
129
+ dict[:P])
130
+ dict = @handler.set_up_encryption(permissions: @handler.class::Permissions::COPY_CONTENT)
131
+ assert_equal(@handler.class::Permissions::COPY_CONTENT | \
132
+ @handler.class::Permissions::RESERVED - 2**32,
133
+ dict[:P])
134
+ dict = @handler.set_up_encryption(permissions: [:modify_content, :modify_annotation])
135
+ assert_equal(@handler.class::Permissions::MODIFY_CONTENT | \
136
+ @handler.class::Permissions::MODIFY_ANNOTATION | \
137
+ @handler.class::Permissions::RESERVED - 2**32,
138
+ dict[:P])
139
+ end
140
+
141
+ it "sets the correct revision independent /EncryptMetadata value" do
142
+ dict = @handler.set_up_encryption
143
+ assert(dict[:EncryptMetadata])
144
+ dict = @handler.set_up_encryption(encrypt_metadata: false)
145
+ refute(dict[:EncryptMetadata])
146
+ end
147
+
148
+ it "sets the correct encryption dictionary values for revision 2 and 3" do
149
+ arc4_assertions = lambda do |d|
150
+ assert_equal(32, d[:U].length)
151
+ assert_equal(32, d[:O].length)
152
+ refute(d.value.key?(:UE))
153
+ refute(d.value.key?(:OE))
154
+ refute(d.value.key?(:Perms))
155
+ end
156
+ dict = @handler.set_up_encryption(key_length: 40, algorithm: :arc4)
157
+ assert_equal(2, dict[:R])
158
+ arc4_assertions.call(dict)
159
+
160
+ dict = @handler.set_up_encryption(key_length: 128, algorithm: :arc4)
161
+ assert_equal(3, dict[:R])
162
+ arc4_assertions.call(dict)
163
+ end
164
+
165
+ it "sets the correct encryption dictionary values for revisions 4 and 6" do
166
+ crypt_filter = lambda do |d, r, alg, length|
167
+ assert_equal(r, d[:R])
168
+ assert_equal(alg == :AESV3 ? 48 : 32, d[:U].length)
169
+ assert_equal(alg == :AESV3 ? 48 : 32, d[:O].length)
170
+ assert_equal({CFM: alg, Length: length, AuthEvent: :DocOpen}, d[:CF][:StdCF])
171
+ assert_equal(:StdCF, d[:StrF])
172
+ assert_equal(:StdCF, d[:StmF])
173
+ end
174
+
175
+ dict = @handler.set_up_encryption(key_length: 128, algorithm: :arc4, force_v4: true)
176
+ refute(dict.value.key?(:UE))
177
+ refute(dict.value.key?(:OE))
178
+ refute(dict.value.key?(:Perms))
179
+ crypt_filter.call(dict, 4, :V2, 16)
180
+
181
+ dict = @handler.set_up_encryption(key_length: 128, algorithm: :aes)
182
+ refute(dict.value.key?(:UE))
183
+ refute(dict.value.key?(:OE))
184
+ refute(dict.value.key?(:Perms))
185
+ crypt_filter.call(dict, 4, :AESV2, 16)
186
+
187
+ dict = @handler.set_up_encryption(key_length: 256, algorithm: :aes)
188
+ assert_equal(32, dict[:UE].length)
189
+ assert_equal(32, dict[:OE].length)
190
+ assert_equal(16, dict[:Perms].length)
191
+ crypt_filter.call(dict, 6, :AESV3, 32)
192
+ end
193
+
194
+ it "uses the password keyword as fallback, the user password as owner password if necessary" do
195
+ dict1 = @handler.set_up_encryption(password: 'user', owner_password: 'owner')
196
+ dict2 = @handler.set_up_encryption(password: 'owner', user_password: 'user')
197
+ dict3 = @handler.set_up_encryption(user_password: 'user', owner_password: 'owner')
198
+ dict4 = @handler.set_up_encryption(user_password: 'test', owner_password: 'test')
199
+ dict5 = @handler.set_up_encryption(user_password: 'test')
200
+
201
+ assert_equal(dict1[:U], dict2[:U])
202
+ assert_equal(dict2[:U], dict3[:U])
203
+ assert_equal(dict1[:O], dict2[:O])
204
+ assert_equal(dict2[:O], dict3[:O])
205
+ assert_equal(dict4[:O], dict5[:O])
206
+ end
207
+
208
+ it "fails if the password contains invalid characters" do
209
+ assert_raises(HexaPDF::EncryptionError) { @handler.set_up_encryption(password: 'œ test') }
210
+ end
211
+
212
+ it "fails for unknown keywords" do
213
+ assert_raises(ArgumentError) { @handler.set_up_encryption(unknown: 'test') }
214
+ end
215
+ end
216
+
217
+ describe "prepare_decryption" do
218
+ it "fails if the /Filter value is incorrect" do
219
+ exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
220
+ @handler.set_up_decryption({Filter: :NonStandard, V: 2})
221
+ end
222
+ assert_match(/Invalid \/Filter/i, exp.message)
223
+ end
224
+
225
+ it "fails if the /R value is incorrect" do
226
+ exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
227
+ @handler.set_up_decryption({Filter: :Standard, V: 2, R: 5})
228
+ end
229
+ assert_match(/Invalid \/R/i, exp.message)
230
+ end
231
+
232
+ it "fails if the ID in the document's trailer is missing although it is needed" do
233
+ exp = assert_raises(HexaPDF::EncryptionError) do
234
+ @handler.set_up_decryption({Filter: :Standard, V: 2, R: 2})
235
+ end
236
+ assert_match(/Document ID/i, exp.message)
237
+ end
238
+
239
+ it "fails if the supplied password is invalid" do
240
+ exp = assert_raises(HexaPDF::EncryptionError) do
241
+ @handler.set_up_decryption({Filter: :Standard, V: 2, R: 6, U: 'a' * 48, O: 'a' * 48,
242
+ UE: 'a' * 32, OE: 'a' * 32})
243
+ end
244
+ assert_match(/Invalid password/i, exp.message)
245
+ end
246
+
247
+ describe "/Perms field checking" do
248
+ before do
249
+ @dict = @handler.set_up_encryption(key_length: 256, algorithm: :aes)
250
+ end
251
+
252
+ it "fails if the field cannot be decrypted" do
253
+ @dict[:Perms].succ!
254
+ exp = assert_raises(HexaPDF::EncryptionError) { @handler.set_up_decryption(@dict) }
255
+ assert_match(/cannot be decrypted/, exp.message)
256
+ end
257
+
258
+ it "fails if the /P field doesn't match" do
259
+ @dict[:P] = 500
260
+ exp = assert_raises(HexaPDF::EncryptionError) { @handler.set_up_decryption(@dict) }
261
+ assert_match(/\/P/, exp.message)
262
+ end
263
+
264
+ it "fails if the /EncryptMetadata field doesn't match" do
265
+ @dict[:EncryptMetadata] = false
266
+ exp = assert_raises(HexaPDF::EncryptionError) { @handler.set_up_decryption(@dict) }
267
+ assert_match(/\/EncryptMetadata/, exp.message)
268
+ end
269
+
270
+ it "ignores the /Perms when requested" do
271
+ obj = HexaPDF::Object.new(nil, oid: 1)
272
+ obj.value = @handler.encrypt_string('test', obj)
273
+
274
+ @dict[:P] = 500
275
+ @handler.set_up_decryption(@dict, check_permissions: false)
276
+ assert_equal('test', @handler.decrypt(obj).value)
277
+ end
278
+ end
279
+ end
280
+
281
+ it "encryption key stays valid even if default dict values are set while setting up decryption" do
282
+ @document.encrypt(key_length: 128, algorithm: :aes)
283
+ assert(@document.security_handler.encryption_key_valid?)
284
+
285
+ @document.trailer[:Encrypt].delete(:EncryptMetadata)
286
+ handler = HexaPDF::Encryption::SecurityHandler.set_up_decryption(@document)
287
+ assert(handler.encryption_key_valid?)
288
+ end
289
+
290
+ it "returns an array of permission symbols" do
291
+ perms = @handler.class::Permissions::MODIFY_CONTENT | @handler.class::Permissions::COPY_CONTENT
292
+ @handler.set_up_encryption(permissions: perms)
293
+ assert_equal([:copy_content, :modify_content], @handler.permissions.sort)
294
+ end
295
+
296
+ describe "handling of metadata streams" do
297
+ before do
298
+ @doc = HexaPDF::Document.new
299
+ @output = StringIO.new(''.b)
300
+ end
301
+
302
+ it "doesn't decrypt or encrypt a metadata stream if /EncryptMetadata is false" do
303
+ @doc.encrypt(encrypt_metadata: false)
304
+ @doc.catalog[:Metadata] = @doc.wrap({Type: :Metadata, Subtype: :XML}, stream: "HELLODATA")
305
+ @doc.write(@output)
306
+ assert_match(/stream\nHELLODATA\nendstream/, @output.string)
307
+
308
+ doc = HexaPDF::Document.new(io: @output)
309
+ assert_equal('HELLODATA', doc.catalog[:Metadata].stream)
310
+ end
311
+
312
+ it "doesn't modify decryption/encryption for metadata streams if /V is not 4 or 5" do
313
+ @doc.encrypt(encrypt_metadata: false, algorithm: :arc4)
314
+ @doc.catalog[:Metadata] = @doc.wrap({Type: :Metadata, Subtype: :XML}, stream: "HELLODATA")
315
+ @doc.write(@output)
316
+ refute_match(/stream\nHELLODATA\nendstream/, @output.string)
317
+
318
+ doc = HexaPDF::Document.new(io: @output)
319
+ assert_equal('HELLODATA', doc.catalog[:Metadata].stream)
320
+ end
321
+ end
322
+ end
@@ -0,0 +1,53 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+
5
+ # Provides common tests for all filter implementations.
6
+ #
7
+ # The filter object needs to be available in the @obj variable and the @all_test_cases variable
8
+ # needs to hold an array of test cases, i.e. [decoded, encoded] objects.
9
+ module CommonFilterTests
10
+ include TestHelper
11
+
12
+ TEST_BIG_STR = ''.b
13
+ TEST_BIG_STR << [rand(2**32)].pack('N') while TEST_BIG_STR.length < 2**16
14
+ TEST_BIG_STR.freeze
15
+
16
+ def test_decodes_correctly
17
+ @all_test_cases.each_with_index do |(result, str), index|
18
+ assert_equal(result, collector(@obj.decoder(feeder(str.dup))), "testcase #{index}")
19
+ end
20
+ end
21
+
22
+ def test_encodes_correctly
23
+ @all_test_cases.each_with_index do |(str, result), index|
24
+ assert_equal(result, collector(@obj.encoder(feeder(str.dup))), "testcase #{index}")
25
+ end
26
+ end
27
+
28
+ def test_works_with_big_data
29
+ assert_equal(TEST_BIG_STR, collector(@obj.decoder(@obj.encoder(feeder(TEST_BIG_STR.dup)))))
30
+ end
31
+
32
+ def test_decoder_returns_strings_in_binary_encoding
33
+ assert_encodings(@obj.decoder(@obj.encoder(feeder('some test data', 1))), "decoder")
34
+ end
35
+
36
+ def test_encoder_returns_strings_in_binary_encoding
37
+ assert_encodings(@obj.encoder(feeder('some test data', 1)), "encoder")
38
+ end
39
+
40
+ def assert_encodings(source, type)
41
+ while source.alive? && (data = source.resume)
42
+ assert_equal(Encoding::BINARY, data.encoding, "encoding problem in #{type}")
43
+ end
44
+ end
45
+
46
+ def test_decoder_works_with_single_byte_input
47
+ assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded.dup, 1))))
48
+ end
49
+
50
+ def test_encoder_works_with_single_byte_input
51
+ assert_equal(@encoded, collector(@obj.encoder(feeder(@decoded.dup, 1))))
52
+ end
53
+ end
@@ -0,0 +1,59 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require_relative 'common'
4
+ require 'hexapdf/filter/ascii85_decode'
5
+
6
+ describe HexaPDF::Filter::ASCII85Decode do
7
+ include CommonFilterTests
8
+
9
+ before do
10
+ @obj = HexaPDF::Filter::ASCII85Decode
11
+ @all_test_cases ||= [['Nov shmoz ka pop.', ':2b:uF(fE/H6@!3+E27</c~>'],
12
+ ['Nov shmoz ka pop.1', ':2b:uF(fE/H6@!3+E27</hm~>'],
13
+ ['Nov shmoz ka pop.12', ':2b:uF(fE/H6@!3+E27</ho*~>'],
14
+ ['Nov shmoz ka pop.123', ':2b:uF(fE/H6@!3+E27</ho+;~>'],
15
+ ["\0\0\0\0Nov shmoz ka pop.", 'z:2b:uF(fE/H6@!3+E27</c~>'],
16
+ ["Nov \x0\x0\x0\x0shmoz ka pop.", ':2b:uzF(fE/H6@!3+E27</c~>']]
17
+ @decoded = @all_test_cases[0][0]
18
+ @encoded = @all_test_cases[0][1]
19
+ end
20
+
21
+ describe "decoder" do
22
+ it "works with single byte input on specially crated input" do
23
+ assert_equal("Nov \0\0\0", collector(@obj.decoder(feeder(':2b:u!!!!~>', 1))))
24
+ end
25
+
26
+ it "ignores whitespace in the input" do
27
+ encoded = @encoded.dup.scan(/./).map {|a| "#{a} \r\t" }.join("\n")
28
+ assert_equal(@decoded, collector(@obj.decoder(feeder(encoded))))
29
+ end
30
+
31
+ it "works without the EOD marker" do
32
+ assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded.sub(/~>/, '')))))
33
+ end
34
+
35
+ it "ignores data after the EOD marker" do
36
+ assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded << "~>abcdefg"))))
37
+ end
38
+
39
+ it "fails if the input contains invalid characters" do
40
+ assert_raises(HexaPDF::FilterError) { collector(@obj.decoder(feeder(':2bwx!'))) }
41
+ end
42
+
43
+ it "fails if the input contains values outside the BASE85 range" do
44
+ assert_raises(HexaPDF::FilterError) { collector(@obj.decoder(feeder('uuuuu'))) }
45
+ end
46
+
47
+ it "fails if the last rest contains a 'z' character" do
48
+ assert_raises(HexaPDF::FilterError) { collector(@obj.decoder(feeder('uuz'))) }
49
+ end
50
+
51
+ it "fails if the last rest contains a '~' character" do
52
+ assert_raises(HexaPDF::FilterError) { collector(@obj.decoder(feeder('uu~'))) }
53
+ end
54
+
55
+ it "fails if the last rest contains values outside the BASE85 range" do
56
+ assert_raises(HexaPDF::FilterError) { collector(@obj.decoder(feeder('uuu>', 1))) }
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require_relative 'common'
4
+ require 'hexapdf/filter/ascii_hex_decode'
5
+
6
+ describe HexaPDF::Filter::ASCIIHexDecode do
7
+ include CommonFilterTests
8
+
9
+ before do
10
+ @obj = HexaPDF::Filter::ASCIIHexDecode
11
+ @all_test_cases = [['Nov shmoz ka pop.', '4e6f762073686d6f7a206b6120706f702e>']]
12
+ @decoded = @all_test_cases[0][0]
13
+ @encoded = @all_test_cases[0][1]
14
+ end
15
+
16
+ describe "decoder" do
17
+ it "ignores whitespace in the input" do
18
+ with_whitespace = @encoded.scan(/./).map {|a| "#{a} \r\t" }.join("\n")
19
+ assert_equal(@decoded, collector(@obj.decoder(feeder(with_whitespace, 1))))
20
+ end
21
+
22
+ it "works without the EOD marker" do
23
+ assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded.chop, 5))))
24
+ end
25
+
26
+ it "ignores data after the EOD marker" do
27
+ assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded << '4e6f7gzz'))))
28
+ end
29
+
30
+ it "assumes the missing char is '0' if the input length is odd" do
31
+ assert_equal(@decoded.chop << ' ', collector(@obj.decoder(feeder(@encoded.chop.chop))))
32
+ end
33
+
34
+ it "fails on invalid characters" do
35
+ assert_raises(HexaPDF::FilterError) { @obj.decoder(feeder('f0f0z')).resume }
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/filter/crypt'
5
+
6
+ describe HexaPDF::Filter::Crypt do
7
+ before do
8
+ @obj = HexaPDF::Filter::Crypt
9
+ @source = Fiber.new { "hallo" }
10
+ end
11
+
12
+ it "works with the Identity filter" do
13
+ assert_equal(@source, @obj.decoder(@source, nil))
14
+ assert_equal(@source, @obj.encoder(@source, {})) # sic: 'encoder'
15
+ assert_equal(@source, @obj.decoder(@source, {Name: :Identity}))
16
+ end
17
+
18
+ it "fails if crypt filter name is not Identity" do
19
+ assert_raises(HexaPDF::FilterError) { @obj.decoder(@source, {Name: :Other}) }
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require_relative 'common'
4
+ require 'hexapdf/filter/encryption'
5
+
6
+ describe HexaPDF::Filter::Encryption do
7
+ before do
8
+ @obj = HexaPDF::Filter::Encryption
9
+ end
10
+
11
+ it "returns the correct decryption fiber" do
12
+ algorithm = Minitest::Mock.new
13
+ algorithm.expect(:decryption_fiber, :fiber, [:key, :source])
14
+ assert_equal(:fiber, @obj.decoder(:source, key: :key, algorithm: algorithm))
15
+ algorithm.verify
16
+ end
17
+
18
+ it "returns the correct encryption fiber" do
19
+ algorithm = Minitest::Mock.new
20
+ algorithm.expect(:encryption_fiber, :fiber, [:key, :source])
21
+ assert_equal(:fiber, @obj.encoder(:source, key: :key, algorithm: algorithm))
22
+ algorithm.verify
23
+ end
24
+ end
@@ -0,0 +1,44 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require_relative 'common'
4
+ require 'hexapdf/filter/flate_decode'
5
+
6
+ describe HexaPDF::Filter::FlateDecode do
7
+ include CommonFilterTests
8
+
9
+ before do
10
+ @obj = HexaPDF::Filter::FlateDecode
11
+ @all_test_cases = [["abcdefg".force_encoding(Encoding::BINARY),
12
+ "x\xDAKLJNIMK\a\x00\n\xDB\x02\xBD".force_encoding(Encoding::BINARY)]]
13
+ @decoded = @all_test_cases[0][0]
14
+ @encoded = @all_test_cases[0][1]
15
+ @encoded_predictor = "x\xDAcJdbD@\x00\x05\x8F\x00v".force_encoding(Encoding::BINARY)
16
+ @predictor_opts = {Predictor: 12}
17
+ end
18
+
19
+ describe "decoder" do
20
+ it "works for empty input" do
21
+ assert_equal('', collector(@obj.decoder(Fiber.new { "" })))
22
+ assert_equal('', collector(@obj.decoder(Fiber.new {})))
23
+ end
24
+
25
+ it "applies the Predictor after decoding" do
26
+ assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded_predictor), @predictor_opts)))
27
+ end
28
+
29
+ it "fails on invalid input" do
30
+ assert_raises(HexaPDF::FilterError) do
31
+ collector(@obj.decoder(feeder(@encoded[0..-2], @encoded.length - 3)))
32
+ end
33
+ assert_raises(HexaPDF::FilterError) do
34
+ collector(@obj.decoder(feeder("some data")))
35
+ end
36
+ end
37
+ end
38
+
39
+ describe "encoder" do
40
+ it "applies the Predictor before encoding" do
41
+ assert_equal(@encoded_predictor, collector(@obj.encoder(feeder(@decoded), @predictor_opts)))
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,52 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require_relative 'common'
4
+ require 'hexapdf/filter/lzw_decode'
5
+
6
+ describe HexaPDF::Filter::LZWDecode do
7
+ include CommonFilterTests
8
+
9
+ before do
10
+ @obj = HexaPDF::Filter::LZWDecode
11
+ @all_test_cases ||= [["-----A---B".b, "\x80\x0b\x60\x50\x22\x0c\x0c\x85\x01".b],
12
+ ['abcabcaaaabbbcdeffffffagggggg'.b,
13
+ "\x80\x18LF8\x14\x10\xC3\a1BLfC)\x9A\x1D\x0F0\x99\xE2Q8\b".b]]
14
+ @decoded = @all_test_cases[0][0]
15
+ @encoded = @all_test_cases[0][1]
16
+ @encoded_predictor = "\x80\x00\x85\xA0 \x04\x12\r\x05\n\x00\x9D\x90p\x10V\x02".b
17
+ @predictor_opts = {Predictor: 12}
18
+ end
19
+
20
+ describe "decoder" do
21
+ it "applies the Predictor after decoding" do
22
+ assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded_predictor), @predictor_opts)))
23
+ end
24
+
25
+ it "fails if an unknown code is found after CLEAR_TABLE" do
26
+ assert_raises(HexaPDF::FilterError) { @obj.decoder(feeder("\xff\xff")).resume }
27
+ end
28
+
29
+ it "fails if an unknown code is found elsewhere" do
30
+ assert_raises(HexaPDF::FilterError) { @obj.decoder(feeder("\x00\x7f\xff\xf0")).resume }
31
+ end
32
+
33
+ it "fails if the code size would be more than 12bit" do
34
+ stream = HexaPDF::Utils::BitStreamWriter.new
35
+ result = stream.write(256, 9)
36
+ result << stream.write(65, 9)
37
+ 258.upto(510) {|i| result << stream.write(i, 9) }
38
+ 511.upto(1022) {|i| result << stream.write(i, 10) }
39
+ 1023.upto(2046) {|i| result << stream.write(i, 11) }
40
+ 2047.upto(4095) {|i| result << stream.write(i, 12) }
41
+ result << stream.write(96, 12)
42
+ result << stream.finalize
43
+ assert_raises(HexaPDF::FilterError) { @obj.decoder(feeder(result)).resume }
44
+ end
45
+ end
46
+
47
+ describe "encoder" do
48
+ it "applies the Predictor before encoding" do
49
+ assert_equal(@encoded_predictor, collector(@obj.encoder(feeder(@decoded), @predictor_opts)))
50
+ end
51
+ end
52
+ end