hexapdf 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CONTRIBUTERS +3 -0
- data/LICENSE +26 -0
- data/README.md +88 -0
- data/Rakefile +121 -0
- data/VERSION +1 -0
- data/agpl-3.0.txt +661 -0
- data/bin/hexapdf +6 -0
- data/data/hexapdf/afm/Courier-Bold.afm +342 -0
- data/data/hexapdf/afm/Courier-BoldOblique.afm +342 -0
- data/data/hexapdf/afm/Courier-Oblique.afm +342 -0
- data/data/hexapdf/afm/Courier.afm +342 -0
- data/data/hexapdf/afm/Helvetica-Bold.afm +2827 -0
- data/data/hexapdf/afm/Helvetica-BoldOblique.afm +2827 -0
- data/data/hexapdf/afm/Helvetica-Oblique.afm +3051 -0
- data/data/hexapdf/afm/Helvetica.afm +3051 -0
- data/data/hexapdf/afm/MustRead.html +1 -0
- data/data/hexapdf/afm/Symbol.afm +213 -0
- data/data/hexapdf/afm/Times-Bold.afm +2588 -0
- data/data/hexapdf/afm/Times-BoldItalic.afm +2384 -0
- data/data/hexapdf/afm/Times-Italic.afm +2667 -0
- data/data/hexapdf/afm/Times-Roman.afm +2419 -0
- data/data/hexapdf/afm/ZapfDingbats.afm +225 -0
- data/data/hexapdf/encoding/glyphlist.txt +4305 -0
- data/data/hexapdf/encoding/zapfdingbats.txt +225 -0
- data/examples/arc.rb +50 -0
- data/examples/graphics.rb +274 -0
- data/examples/hello_world.rb +16 -0
- data/examples/machupicchu.jpg +0 -0
- data/examples/merging.rb +24 -0
- data/examples/optimizing.rb +20 -0
- data/examples/show_char_bboxes.rb +55 -0
- data/examples/standard_pdf_fonts.rb +72 -0
- data/examples/truetype.rb +45 -0
- data/lib/hexapdf/cli/extract.rb +128 -0
- data/lib/hexapdf/cli/info.rb +121 -0
- data/lib/hexapdf/cli/inspect.rb +157 -0
- data/lib/hexapdf/cli/modify.rb +218 -0
- data/lib/hexapdf/cli.rb +121 -0
- data/lib/hexapdf/configuration.rb +392 -0
- data/lib/hexapdf/content/canvas.rb +1974 -0
- data/lib/hexapdf/content/color_space.rb +364 -0
- data/lib/hexapdf/content/graphic_object/arc.rb +267 -0
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +208 -0
- data/lib/hexapdf/content/graphic_object/solid_arc.rb +173 -0
- data/lib/hexapdf/content/graphic_object.rb +81 -0
- data/lib/hexapdf/content/graphics_state.rb +579 -0
- data/lib/hexapdf/content/operator.rb +1072 -0
- data/lib/hexapdf/content/parser.rb +204 -0
- data/lib/hexapdf/content/processor.rb +451 -0
- data/lib/hexapdf/content/transformation_matrix.rb +172 -0
- data/lib/hexapdf/content.rb +47 -0
- data/lib/hexapdf/data_dir.rb +51 -0
- data/lib/hexapdf/dictionary.rb +303 -0
- data/lib/hexapdf/dictionary_fields.rb +382 -0
- data/lib/hexapdf/document.rb +589 -0
- data/lib/hexapdf/document_utils.rb +209 -0
- data/lib/hexapdf/encryption/aes.rb +206 -0
- data/lib/hexapdf/encryption/arc4.rb +93 -0
- data/lib/hexapdf/encryption/fast_aes.rb +79 -0
- data/lib/hexapdf/encryption/fast_arc4.rb +67 -0
- data/lib/hexapdf/encryption/identity.rb +63 -0
- data/lib/hexapdf/encryption/ruby_aes.rb +447 -0
- data/lib/hexapdf/encryption/ruby_arc4.rb +96 -0
- data/lib/hexapdf/encryption/security_handler.rb +494 -0
- data/lib/hexapdf/encryption/standard_security_handler.rb +616 -0
- data/lib/hexapdf/encryption.rb +94 -0
- data/lib/hexapdf/error.rb +73 -0
- data/lib/hexapdf/filter/ascii85_decode.rb +160 -0
- data/lib/hexapdf/filter/ascii_hex_decode.rb +87 -0
- data/lib/hexapdf/filter/dct_decode.rb +57 -0
- data/lib/hexapdf/filter/encryption.rb +59 -0
- data/lib/hexapdf/filter/flate_decode.rb +93 -0
- data/lib/hexapdf/filter/jpx_decode.rb +56 -0
- data/lib/hexapdf/filter/lzw_decode.rb +191 -0
- data/lib/hexapdf/filter/predictor.rb +266 -0
- data/lib/hexapdf/filter/run_length_decode.rb +108 -0
- data/lib/hexapdf/filter.rb +176 -0
- data/lib/hexapdf/font/cmap/parser.rb +146 -0
- data/lib/hexapdf/font/cmap/writer.rb +176 -0
- data/lib/hexapdf/font/cmap.rb +90 -0
- data/lib/hexapdf/font/encoding/base.rb +77 -0
- data/lib/hexapdf/font/encoding/difference_encoding.rb +64 -0
- data/lib/hexapdf/font/encoding/glyph_list.rb +150 -0
- data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +221 -0
- data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +265 -0
- data/lib/hexapdf/font/encoding/standard_encoding.rb +205 -0
- data/lib/hexapdf/font/encoding/symbol_encoding.rb +244 -0
- data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +280 -0
- data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +250 -0
- data/lib/hexapdf/font/encoding.rb +68 -0
- data/lib/hexapdf/font/true_type/font.rb +179 -0
- data/lib/hexapdf/font/true_type/table/cmap.rb +103 -0
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +384 -0
- data/lib/hexapdf/font/true_type/table/directory.rb +92 -0
- data/lib/hexapdf/font/true_type/table/glyf.rb +166 -0
- data/lib/hexapdf/font/true_type/table/head.rb +143 -0
- data/lib/hexapdf/font/true_type/table/hhea.rb +109 -0
- data/lib/hexapdf/font/true_type/table/hmtx.rb +79 -0
- data/lib/hexapdf/font/true_type/table/loca.rb +79 -0
- data/lib/hexapdf/font/true_type/table/maxp.rb +112 -0
- data/lib/hexapdf/font/true_type/table/name.rb +218 -0
- data/lib/hexapdf/font/true_type/table/os2.rb +200 -0
- data/lib/hexapdf/font/true_type/table/post.rb +230 -0
- data/lib/hexapdf/font/true_type/table.rb +155 -0
- data/lib/hexapdf/font/true_type.rb +48 -0
- data/lib/hexapdf/font/true_type_wrapper.rb +240 -0
- data/lib/hexapdf/font/type1/afm_parser.rb +230 -0
- data/lib/hexapdf/font/type1/character_metrics.rb +67 -0
- data/lib/hexapdf/font/type1/font.rb +123 -0
- data/lib/hexapdf/font/type1/font_metrics.rb +117 -0
- data/lib/hexapdf/font/type1/pfb_parser.rb +71 -0
- data/lib/hexapdf/font/type1.rb +52 -0
- data/lib/hexapdf/font/type1_wrapper.rb +193 -0
- data/lib/hexapdf/font_loader/from_configuration.rb +70 -0
- data/lib/hexapdf/font_loader/standard14.rb +98 -0
- data/lib/hexapdf/font_loader.rb +85 -0
- data/lib/hexapdf/font_utils.rb +89 -0
- data/lib/hexapdf/image_loader/jpeg.rb +166 -0
- data/lib/hexapdf/image_loader/pdf.rb +89 -0
- data/lib/hexapdf/image_loader/png.rb +410 -0
- data/lib/hexapdf/image_loader.rb +68 -0
- data/lib/hexapdf/importer.rb +139 -0
- data/lib/hexapdf/name_tree_node.rb +78 -0
- data/lib/hexapdf/number_tree_node.rb +67 -0
- data/lib/hexapdf/object.rb +363 -0
- data/lib/hexapdf/parser.rb +349 -0
- data/lib/hexapdf/rectangle.rb +99 -0
- data/lib/hexapdf/reference.rb +98 -0
- data/lib/hexapdf/revision.rb +206 -0
- data/lib/hexapdf/revisions.rb +194 -0
- data/lib/hexapdf/serializer.rb +326 -0
- data/lib/hexapdf/stream.rb +279 -0
- data/lib/hexapdf/task/dereference.rb +109 -0
- data/lib/hexapdf/task/optimize.rb +230 -0
- data/lib/hexapdf/task.rb +68 -0
- data/lib/hexapdf/tokenizer.rb +406 -0
- data/lib/hexapdf/type/catalog.rb +107 -0
- data/lib/hexapdf/type/embedded_file.rb +87 -0
- data/lib/hexapdf/type/file_specification.rb +232 -0
- data/lib/hexapdf/type/font.rb +81 -0
- data/lib/hexapdf/type/font_descriptor.rb +109 -0
- data/lib/hexapdf/type/font_simple.rb +190 -0
- data/lib/hexapdf/type/font_true_type.rb +47 -0
- data/lib/hexapdf/type/font_type1.rb +162 -0
- data/lib/hexapdf/type/form.rb +103 -0
- data/lib/hexapdf/type/graphics_state_parameter.rb +79 -0
- data/lib/hexapdf/type/image.rb +73 -0
- data/lib/hexapdf/type/info.rb +70 -0
- data/lib/hexapdf/type/names.rb +69 -0
- data/lib/hexapdf/type/object_stream.rb +224 -0
- data/lib/hexapdf/type/page.rb +355 -0
- data/lib/hexapdf/type/page_tree_node.rb +269 -0
- data/lib/hexapdf/type/resources.rb +212 -0
- data/lib/hexapdf/type/trailer.rb +128 -0
- data/lib/hexapdf/type/viewer_preferences.rb +73 -0
- data/lib/hexapdf/type/xref_stream.rb +204 -0
- data/lib/hexapdf/type.rb +67 -0
- data/lib/hexapdf/utils/bit_field.rb +87 -0
- data/lib/hexapdf/utils/bit_stream.rb +148 -0
- data/lib/hexapdf/utils/lru_cache.rb +65 -0
- data/lib/hexapdf/utils/math_helpers.rb +55 -0
- data/lib/hexapdf/utils/object_hash.rb +130 -0
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +93 -0
- data/lib/hexapdf/utils/sorted_tree_node.rb +339 -0
- data/lib/hexapdf/version.rb +39 -0
- data/lib/hexapdf/writer.rb +199 -0
- data/lib/hexapdf/xref_section.rb +152 -0
- data/lib/hexapdf.rb +34 -0
- data/man/man1/hexapdf.1 +249 -0
- data/test/data/aes-test-vectors/CBCGFSbox-128-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCGFSbox-128-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCGFSbox-192-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCGFSbox-192-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCGFSbox-256-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCGFSbox-256-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-128-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-128-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-192-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-192-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-256-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-256-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-128-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-128-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-192-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-192-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-256-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-256-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-128-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-128-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-192-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-192-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-256-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-256-encrypt.data.gz +0 -0
- data/test/data/fonts/Ubuntu-Title.ttf +0 -0
- data/test/data/images/cmyk.jpg +0 -0
- data/test/data/images/fillbytes.jpg +0 -0
- data/test/data/images/gray.jpg +0 -0
- data/test/data/images/greyscale-1bit.png +0 -0
- data/test/data/images/greyscale-2bit.png +0 -0
- data/test/data/images/greyscale-4bit.png +0 -0
- data/test/data/images/greyscale-8bit.png +0 -0
- data/test/data/images/greyscale-alpha-8bit.png +0 -0
- data/test/data/images/greyscale-trns-8bit.png +0 -0
- data/test/data/images/greyscale-with-gamma1.0.png +0 -0
- data/test/data/images/greyscale-with-gamma1.5.png +0 -0
- data/test/data/images/indexed-1bit.png +0 -0
- data/test/data/images/indexed-2bit.png +0 -0
- data/test/data/images/indexed-4bit.png +0 -0
- data/test/data/images/indexed-8bit.png +0 -0
- data/test/data/images/indexed-alpha-4bit.png +0 -0
- data/test/data/images/indexed-alpha-8bit.png +0 -0
- data/test/data/images/rgb.jpg +0 -0
- data/test/data/images/truecolour-8bit.png +0 -0
- data/test/data/images/truecolour-alpha-8bit.png +0 -0
- data/test/data/images/truecolour-gama-chrm-8bit.png +0 -0
- data/test/data/images/truecolour-srgb-8bit.png +0 -0
- data/test/data/minimal.pdf +44 -0
- data/test/data/standard-security-handler/README +9 -0
- data/test/data/standard-security-handler/bothpwd-aes-128bit-V4.pdf +44 -0
- data/test/data/standard-security-handler/bothpwd-aes-256bit-V5.pdf +0 -0
- data/test/data/standard-security-handler/bothpwd-arc4-128bit-V2.pdf +43 -0
- data/test/data/standard-security-handler/bothpwd-arc4-128bit-V4.pdf +43 -0
- data/test/data/standard-security-handler/bothpwd-arc4-40bit-V1.pdf +0 -0
- data/test/data/standard-security-handler/nopwd-aes-128bit-V4.pdf +43 -0
- data/test/data/standard-security-handler/nopwd-aes-256bit-V5.pdf +0 -0
- data/test/data/standard-security-handler/nopwd-arc4-128bit-V2.pdf +43 -0
- data/test/data/standard-security-handler/nopwd-arc4-128bit-V4.pdf +43 -0
- data/test/data/standard-security-handler/nopwd-arc4-40bit-V1.pdf +43 -0
- data/test/data/standard-security-handler/ownerpwd-aes-128bit-V4.pdf +0 -0
- data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5.pdf +43 -0
- data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V2.pdf +43 -0
- data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V4.pdf +43 -0
- data/test/data/standard-security-handler/ownerpwd-arc4-40bit-V1.pdf +43 -0
- data/test/data/standard-security-handler/userpwd-aes-128bit-V4.pdf +43 -0
- data/test/data/standard-security-handler/userpwd-aes-256bit-V5.pdf +43 -0
- data/test/data/standard-security-handler/userpwd-arc4-128bit-V2.pdf +0 -0
- data/test/data/standard-security-handler/userpwd-arc4-128bit-V4.pdf +0 -0
- data/test/data/standard-security-handler/userpwd-arc4-40bit-V1.pdf +43 -0
- data/test/hexapdf/common_tokenizer_tests.rb +204 -0
- data/test/hexapdf/content/common.rb +31 -0
- data/test/hexapdf/content/graphic_object/test_arc.rb +93 -0
- data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +91 -0
- data/test/hexapdf/content/graphic_object/test_solid_arc.rb +86 -0
- data/test/hexapdf/content/test_canvas.rb +1113 -0
- data/test/hexapdf/content/test_color_space.rb +97 -0
- data/test/hexapdf/content/test_graphics_state.rb +138 -0
- data/test/hexapdf/content/test_operator.rb +619 -0
- data/test/hexapdf/content/test_parser.rb +66 -0
- data/test/hexapdf/content/test_processor.rb +156 -0
- data/test/hexapdf/content/test_transformation_matrix.rb +64 -0
- data/test/hexapdf/encryption/common.rb +87 -0
- data/test/hexapdf/encryption/test_aes.rb +121 -0
- data/test/hexapdf/encryption/test_arc4.rb +39 -0
- data/test/hexapdf/encryption/test_fast_aes.rb +17 -0
- data/test/hexapdf/encryption/test_fast_arc4.rb +12 -0
- data/test/hexapdf/encryption/test_identity.rb +21 -0
- data/test/hexapdf/encryption/test_ruby_aes.rb +23 -0
- data/test/hexapdf/encryption/test_ruby_arc4.rb +20 -0
- data/test/hexapdf/encryption/test_security_handler.rb +356 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +274 -0
- data/test/hexapdf/filter/common.rb +53 -0
- data/test/hexapdf/filter/test_ascii85_decode.rb +60 -0
- data/test/hexapdf/filter/test_ascii_hex_decode.rb +33 -0
- data/test/hexapdf/filter/test_encryption.rb +24 -0
- data/test/hexapdf/filter/test_flate_decode.rb +35 -0
- data/test/hexapdf/filter/test_lzw_decode.rb +52 -0
- data/test/hexapdf/filter/test_predictor.rb +183 -0
- data/test/hexapdf/filter/test_run_length_decode.rb +32 -0
- data/test/hexapdf/font/cmap/test_parser.rb +67 -0
- data/test/hexapdf/font/cmap/test_writer.rb +58 -0
- data/test/hexapdf/font/encoding/test_base.rb +35 -0
- data/test/hexapdf/font/encoding/test_difference_encoding.rb +21 -0
- data/test/hexapdf/font/encoding/test_glyph_list.rb +59 -0
- data/test/hexapdf/font/encoding/test_zapf_dingbats_encoding.rb +16 -0
- data/test/hexapdf/font/test_encoding.rb +27 -0
- data/test/hexapdf/font/test_true_type_wrapper.rb +110 -0
- data/test/hexapdf/font/test_type1_wrapper.rb +66 -0
- data/test/hexapdf/font/true_type/common.rb +19 -0
- data/test/hexapdf/font/true_type/table/test_cmap.rb +59 -0
- data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +133 -0
- data/test/hexapdf/font/true_type/table/test_directory.rb +35 -0
- data/test/hexapdf/font/true_type/table/test_glyf.rb +58 -0
- data/test/hexapdf/font/true_type/table/test_head.rb +76 -0
- data/test/hexapdf/font/true_type/table/test_hhea.rb +40 -0
- data/test/hexapdf/font/true_type/table/test_hmtx.rb +38 -0
- data/test/hexapdf/font/true_type/table/test_loca.rb +43 -0
- data/test/hexapdf/font/true_type/table/test_maxp.rb +62 -0
- data/test/hexapdf/font/true_type/table/test_name.rb +95 -0
- data/test/hexapdf/font/true_type/table/test_os2.rb +65 -0
- data/test/hexapdf/font/true_type/table/test_post.rb +89 -0
- data/test/hexapdf/font/true_type/test_font.rb +120 -0
- data/test/hexapdf/font/true_type/test_table.rb +41 -0
- data/test/hexapdf/font/type1/test_afm_parser.rb +51 -0
- data/test/hexapdf/font/type1/test_font.rb +68 -0
- data/test/hexapdf/font/type1/test_pfb_parser.rb +37 -0
- data/test/hexapdf/font_loader/test_from_configuration.rb +28 -0
- data/test/hexapdf/font_loader/test_standard14.rb +22 -0
- data/test/hexapdf/image_loader/test_jpeg.rb +83 -0
- data/test/hexapdf/image_loader/test_pdf.rb +47 -0
- data/test/hexapdf/image_loader/test_png.rb +258 -0
- data/test/hexapdf/task/test_dereference.rb +46 -0
- data/test/hexapdf/task/test_optimize.rb +137 -0
- data/test/hexapdf/test_configuration.rb +82 -0
- data/test/hexapdf/test_data_dir.rb +32 -0
- data/test/hexapdf/test_dictionary.rb +284 -0
- data/test/hexapdf/test_dictionary_fields.rb +185 -0
- data/test/hexapdf/test_document.rb +574 -0
- data/test/hexapdf/test_document_utils.rb +144 -0
- data/test/hexapdf/test_filter.rb +96 -0
- data/test/hexapdf/test_font_utils.rb +47 -0
- data/test/hexapdf/test_importer.rb +78 -0
- data/test/hexapdf/test_object.rb +177 -0
- data/test/hexapdf/test_parser.rb +394 -0
- data/test/hexapdf/test_rectangle.rb +36 -0
- data/test/hexapdf/test_reference.rb +41 -0
- data/test/hexapdf/test_revision.rb +139 -0
- data/test/hexapdf/test_revisions.rb +93 -0
- data/test/hexapdf/test_serializer.rb +169 -0
- data/test/hexapdf/test_stream.rb +262 -0
- data/test/hexapdf/test_tokenizer.rb +30 -0
- data/test/hexapdf/test_writer.rb +120 -0
- data/test/hexapdf/test_xref_section.rb +35 -0
- data/test/hexapdf/type/test_catalog.rb +30 -0
- data/test/hexapdf/type/test_embedded_file.rb +16 -0
- data/test/hexapdf/type/test_file_specification.rb +148 -0
- data/test/hexapdf/type/test_font.rb +35 -0
- data/test/hexapdf/type/test_font_descriptor.rb +51 -0
- data/test/hexapdf/type/test_font_simple.rb +190 -0
- data/test/hexapdf/type/test_font_type1.rb +128 -0
- data/test/hexapdf/type/test_form.rb +60 -0
- data/test/hexapdf/type/test_info.rb +14 -0
- data/test/hexapdf/type/test_names.rb +9 -0
- data/test/hexapdf/type/test_object_stream.rb +84 -0
- data/test/hexapdf/type/test_page.rb +260 -0
- data/test/hexapdf/type/test_page_tree_node.rb +255 -0
- data/test/hexapdf/type/test_resources.rb +167 -0
- data/test/hexapdf/type/test_trailer.rb +109 -0
- data/test/hexapdf/type/test_xref_stream.rb +131 -0
- data/test/hexapdf/utils/test_bit_field.rb +47 -0
- data/test/hexapdf/utils/test_lru_cache.rb +22 -0
- data/test/hexapdf/utils/test_object_hash.rb +115 -0
- data/test/hexapdf/utils/test_pdf_doc_encoding.rb +18 -0
- data/test/hexapdf/utils/test_sorted_tree_node.rb +232 -0
- data/test/test_helper.rb +56 -0
- metadata +427 -0
@@ -0,0 +1,356 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'hexapdf/encryption/security_handler'
|
5
|
+
require 'hexapdf/document'
|
6
|
+
require 'hexapdf/stream'
|
7
|
+
|
8
|
+
describe HexaPDF::Encryption::EncryptionDictionary do
|
9
|
+
before do
|
10
|
+
@document = HexaPDF::Document.new
|
11
|
+
@dict = HexaPDF::Encryption::EncryptionDictionary.new({}, document: @document)
|
12
|
+
@dict[:Filter] = :Standard
|
13
|
+
@dict[:V] = 1
|
14
|
+
end
|
15
|
+
|
16
|
+
it "validates the /V value" do
|
17
|
+
@dict[:V] = 1
|
18
|
+
assert(@dict.validate)
|
19
|
+
@dict[:V] = 3
|
20
|
+
refute(@dict.validate)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "validates the /Length field when /V=2" do
|
24
|
+
@dict[:V] = 2
|
25
|
+
refute(@dict.validate)
|
26
|
+
|
27
|
+
@dict[:Length] = 32
|
28
|
+
refute(@dict.validate)
|
29
|
+
@dict[:Length] = 136
|
30
|
+
refute(@dict.validate)
|
31
|
+
@dict[:Length] = 55
|
32
|
+
refute(@dict.validate)
|
33
|
+
|
34
|
+
@dict[:Length] = 120
|
35
|
+
assert(@dict.validate)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe HexaPDF::Encryption::SecurityHandler do
|
40
|
+
class TestHandler < HexaPDF::Encryption::SecurityHandler
|
41
|
+
|
42
|
+
attr_accessor :strf, :myopt
|
43
|
+
public :dict
|
44
|
+
|
45
|
+
def prepare_encryption(**_options)
|
46
|
+
dict[:Filter] = :Test
|
47
|
+
@key = "a" * key_length
|
48
|
+
@strf ||= :aes
|
49
|
+
@stmf ||= :arc4
|
50
|
+
@eff ||= :identity
|
51
|
+
[@key, @strf, @stmf, @eff]
|
52
|
+
end
|
53
|
+
|
54
|
+
def prepare_decryption(myopt: nil)
|
55
|
+
@myopt = myopt
|
56
|
+
@key = "a" * key_length
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
before do
|
63
|
+
@document = HexaPDF::Document.new
|
64
|
+
@obj = @document.add({})
|
65
|
+
@handler = TestHandler.new(@document)
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "class methods" do
|
69
|
+
before do
|
70
|
+
HexaPDF::GlobalConfiguration['encryption.filter_map'][:Test] = TestHandler
|
71
|
+
end
|
72
|
+
|
73
|
+
after do
|
74
|
+
HexaPDF::GlobalConfiguration['encryption.filter_map'].delete(:Test)
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "class set_up_encryption" do
|
78
|
+
it "fails if the requested security handler cannot be found" do
|
79
|
+
assert_raises(HexaPDF::EncryptionError) do
|
80
|
+
HexaPDF::Encryption::SecurityHandler.set_up_encryption(@document, :non_standard)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it "updates the trailer's /Encrypt entry to be wrapped by an encryption dictionary" do
|
85
|
+
HexaPDF::Encryption::SecurityHandler.set_up_encryption(@document, :Test)
|
86
|
+
assert_kind_of(HexaPDF::Encryption::EncryptionDictionary, @document.trailer[:Encrypt])
|
87
|
+
end
|
88
|
+
|
89
|
+
it "returns the frozen security handler" do
|
90
|
+
handler = HexaPDF::Encryption::SecurityHandler.set_up_encryption(@document, :Test)
|
91
|
+
assert(handler.frozen?)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "set_up_decryption" do
|
96
|
+
it "fails if the document has no /Encrypt dictionary" do
|
97
|
+
exp = assert_raises(HexaPDF::EncryptionError) do
|
98
|
+
HexaPDF::Encryption::SecurityHandler.set_up_decryption(@document)
|
99
|
+
end
|
100
|
+
assert_match(/No \/Encrypt/i, exp.message)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "fails if the requested security handler cannot be found" do
|
104
|
+
@document.trailer[:Encrypt] = {Filter: :NonStandard}
|
105
|
+
assert_raises(HexaPDF::EncryptionError) do
|
106
|
+
HexaPDF::Encryption::SecurityHandler.set_up_decryption(@document)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
it "updates the trailer's /Encrypt entry to be wrapped by an encryption dictionary" do
|
111
|
+
@document.trailer[:Encrypt] = {Filter: :Test, V: 1}
|
112
|
+
HexaPDF::Encryption::SecurityHandler.set_up_decryption(@document)
|
113
|
+
assert_kind_of(HexaPDF::Encryption::EncryptionDictionary, @document.trailer[:Encrypt])
|
114
|
+
end
|
115
|
+
|
116
|
+
it "returns the frozen security handler" do
|
117
|
+
@document.trailer[:Encrypt] = {Filter: :Test, V: 1}
|
118
|
+
handler = HexaPDF::Encryption::SecurityHandler.set_up_decryption(@document)
|
119
|
+
assert(handler.frozen?)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
it "doesn't have a valid encryption key directly after creation" do
|
125
|
+
refute(@handler.encryption_key_valid?)
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "set_up_encryption" do
|
129
|
+
it "sets the correct /V value for the given key length and algorithm" do
|
130
|
+
[[40, :arc4, 1], [128, :arc4, 2], [128, :arc4, 4],
|
131
|
+
[128, :aes, 4], [256, :aes, 5]].each do |length, algorithm, version|
|
132
|
+
@handler.set_up_encryption(key_length: length, algorithm: algorithm, force_V4: version == 4)
|
133
|
+
assert_equal(version, @handler.dict[:V])
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
it "sets the correct /Length value for the given key length" do
|
138
|
+
[[40, nil], [48, 48], [128, 128], [256, nil]].each do |key_length, result|
|
139
|
+
algorithm = (key_length == 256 ? :aes : :arc4)
|
140
|
+
@handler.set_up_encryption(key_length: key_length, algorithm: algorithm)
|
141
|
+
assert_equal(result, @handler.dict[:Length])
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
it "calls the prepare_encryption method" do
|
146
|
+
@handler.set_up_encryption
|
147
|
+
assert_equal(:Test, @handler.dict[:Filter])
|
148
|
+
end
|
149
|
+
|
150
|
+
it "returns the generated encryption dictionary wrapped in an encryption class" do
|
151
|
+
dict = @handler.set_up_encryption
|
152
|
+
assert_kind_of(HexaPDF::Encryption::EncryptionDictionary, dict)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "set's up the handler for encryption" do
|
156
|
+
[:arc4, :aes].each do |algorithm|
|
157
|
+
@handler.set_up_encryption(key_length: 128, algorithm: algorithm)
|
158
|
+
@obj[:X] = @handler.encrypt_string('data', @obj)
|
159
|
+
assert_equal('data', @handler.decrypt(@obj)[:X])
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
it "generates a valid encryption key" do
|
164
|
+
@document.trailer[:Encrypt] = @handler.set_up_encryption
|
165
|
+
assert(@handler.encryption_key_valid?)
|
166
|
+
end
|
167
|
+
|
168
|
+
it "provides correct encryption details" do
|
169
|
+
@handler.set_up_encryption
|
170
|
+
assert_equal({version: 4, string_algorithm: :aes, stream_algorithm: :arc4,
|
171
|
+
embedded_file_algorithm: :identity, key_length: 128},
|
172
|
+
@handler.encryption_details)
|
173
|
+
assert_equal(HexaPDF::Encryption::Identity, @handler.send(:embedded_file_algorithm))
|
174
|
+
assert_equal(HexaPDF::Encryption::FastAES, @handler.send(:string_algorithm))
|
175
|
+
assert_equal(HexaPDF::Encryption::FastARC4, @handler.send(:stream_algorithm))
|
176
|
+
end
|
177
|
+
|
178
|
+
it "fails for unsupported encryption key lengths" do
|
179
|
+
exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
|
180
|
+
@handler.set_up_encryption(key_length: 43)
|
181
|
+
end
|
182
|
+
assert_match(/Invalid key length/i, exp.message)
|
183
|
+
end
|
184
|
+
|
185
|
+
it "fails for unsupported encryption algorithms" do
|
186
|
+
exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
|
187
|
+
@handler.set_up_encryption(algorithm: :test)
|
188
|
+
end
|
189
|
+
assert_match(/Unsupported encryption algorithm/i, exp.message)
|
190
|
+
end
|
191
|
+
|
192
|
+
it "fails for the aes algorithm with key lengths != 128 or 256" do
|
193
|
+
exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
|
194
|
+
@handler.set_up_encryption(algorithm: :aes, key_length: 40)
|
195
|
+
end
|
196
|
+
assert_match(/AES algorithm.*key length/i, exp.message)
|
197
|
+
end
|
198
|
+
|
199
|
+
it "fails for the arc4 algorithm with a key length of 256" do
|
200
|
+
exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
|
201
|
+
@handler.set_up_encryption(algorithm: :arc4, key_length: 256)
|
202
|
+
end
|
203
|
+
assert_match(/ARC4 algorithm.*key length/i, exp.message)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
describe "set_up_decryption" do
|
209
|
+
it "sets the handlers's dictionary to the encryption dictionary wrapped in a custom class and returns it" do
|
210
|
+
dict = @handler.set_up_decryption(Filter: :test, V: 1)
|
211
|
+
assert_equal(dict, @handler.dict)
|
212
|
+
assert_kind_of(HexaPDF::Encryption::EncryptionDictionary, @handler.dict)
|
213
|
+
assert_equal({Filter: :test, V: 1}, @handler.dict.value)
|
214
|
+
end
|
215
|
+
|
216
|
+
it "doesn't modify the trailer's /Encrypt dictionary" do
|
217
|
+
@handler.set_up_decryption(Filter: :test, V: 4, Length: 128)
|
218
|
+
assert_nil(@document.trailer[:Encrypt])
|
219
|
+
end
|
220
|
+
|
221
|
+
it "calls prepare_decryption" do
|
222
|
+
@handler.set_up_decryption({Filter: :test, V: 4, Length: 128}, myopt: 5)
|
223
|
+
assert_equal(5, @handler.myopt)
|
224
|
+
end
|
225
|
+
|
226
|
+
it "selects the correct algorithm based on the /V and /CF values" do
|
227
|
+
@enc = @handler.dup
|
228
|
+
|
229
|
+
[[:arc4, 40, {V: 1}],
|
230
|
+
[:arc4, 80, {V: 2, Length: 80}],
|
231
|
+
[:arc4, 128, {V: 4, StrF: :Mine, CF: {Mine: {CFM: :V2}}}],
|
232
|
+
[:aes, 128, {V: 4, StrF: :Mine, CF: {Mine: {CFM: :AESV2}}}],
|
233
|
+
[:aes, 256, {V: 5, StrF: :Mine, CF: {Mine: {CFM: :AESV3}}}],
|
234
|
+
[:identity, 128, {V: 4, StrF: :Mine, CF: {Mine: {CFM: :None}}}],
|
235
|
+
[:identity, 128, {V: 4, CF: {Mine: {CFM: :AESV2}}}],
|
236
|
+
].each do |alg, length, dict|
|
237
|
+
@enc.strf = alg
|
238
|
+
@enc.set_up_encryption(key_length: length, algorithm: (alg == :identity ? :aes : alg))
|
239
|
+
@obj[:X] = @enc.encrypt_string('data', @obj)
|
240
|
+
@handler.set_up_decryption(dict)
|
241
|
+
assert_equal('data', @handler.decrypt(@obj)[:X])
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
it "selects the correct algorithm for string, stream and embedded file decryption" do
|
246
|
+
@handler.set_up_decryption({V: 4, StrF: :Mine, StmF: :Mine, EFF: :Mine,
|
247
|
+
CF: {Mine: {CFM: :V2}}})
|
248
|
+
assert_equal(HexaPDF::Encryption::FastARC4, @handler.send(:embedded_file_algorithm))
|
249
|
+
assert_equal(HexaPDF::Encryption::FastARC4, @handler.send(:string_algorithm))
|
250
|
+
assert_equal(HexaPDF::Encryption::FastARC4, @handler.send(:stream_algorithm))
|
251
|
+
end
|
252
|
+
|
253
|
+
it "provides correct encryption details" do
|
254
|
+
@handler.set_up_decryption({Filter: :test, V: 2, Length: 128}, myopt: 5)
|
255
|
+
assert_equal({version: 2, string_algorithm: :arc4, stream_algorithm: :arc4,
|
256
|
+
embedded_file_algorithm: :arc4, key_length: 128},
|
257
|
+
@handler.encryption_details)
|
258
|
+
end
|
259
|
+
|
260
|
+
it "fails for unsupported /V values in the dict" do
|
261
|
+
exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
|
262
|
+
@handler.set_up_decryption(V: 3)
|
263
|
+
end
|
264
|
+
assert_match(/Unsupported encryption version/i, exp.message)
|
265
|
+
end
|
266
|
+
|
267
|
+
it "fails for unsupported crypt filter encryption methods" do
|
268
|
+
exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
|
269
|
+
@handler.set_up_decryption(V: 4, StrF: :Mine, CF: {Mine: {CFM: :Unknown}})
|
270
|
+
end
|
271
|
+
assert_match(/Unsupported encryption method/i, exp.message)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
|
276
|
+
describe "decrypt" do
|
277
|
+
before do
|
278
|
+
@handler.set_up_decryption(V: 1)
|
279
|
+
@encrypted = @handler.encrypt_string('string', @obj)
|
280
|
+
@obj.value = {Key: @encrypted.dup, Array: [@encrypted.dup], Hash: {Another: @encrypted.dup}}
|
281
|
+
end
|
282
|
+
|
283
|
+
it "decrypts all strings in an object" do
|
284
|
+
@handler.decrypt(@obj)
|
285
|
+
assert_equal('string', @obj[:Key])
|
286
|
+
assert_equal('string', @obj[:Array][0])
|
287
|
+
assert_equal('string', @obj[:Hash][:Another])
|
288
|
+
end
|
289
|
+
|
290
|
+
it "decrypts the content of a stream object" do
|
291
|
+
data = HexaPDF::StreamData.new(proc { @encrypted })
|
292
|
+
obj = @document.wrap({}, oid: @obj.oid, stream: data)
|
293
|
+
@handler.decrypt(obj)
|
294
|
+
assert_equal('string', obj.stream)
|
295
|
+
end
|
296
|
+
|
297
|
+
it "doesn't decrypt a document's Encrypt dictionary" do
|
298
|
+
@document.trailer[:Encrypt] = @obj
|
299
|
+
assert_equal(@encrypted, @handler.decrypt(@obj)[:Key])
|
300
|
+
end
|
301
|
+
|
302
|
+
it "doesn't decrypt XRef streams" do
|
303
|
+
@obj[:Type] = :XRef
|
304
|
+
assert_equal(@encrypted, @handler.decrypt(@obj)[:Key])
|
305
|
+
end
|
306
|
+
|
307
|
+
it "fails if V < 5 and the object number changes" do
|
308
|
+
@obj.oid = 55
|
309
|
+
@handler.decrypt(@obj)
|
310
|
+
refute_equal('string', @obj[:Key])
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
|
315
|
+
describe "encryption" do
|
316
|
+
before do
|
317
|
+
@handler.set_up_encryption(key_length: 128, algorithm: :arc4)
|
318
|
+
@stream = @document.wrap({}, oid: 1, stream: HexaPDF::StreamData.new(proc { "string" }))
|
319
|
+
end
|
320
|
+
|
321
|
+
it "encrypts strings of indirect objects" do
|
322
|
+
@obj[:Key] = @handler.encrypt_string('string', @obj)
|
323
|
+
assert_equal('string', @handler.decrypt(@obj)[:Key])
|
324
|
+
end
|
325
|
+
|
326
|
+
it "encrypts streams" do
|
327
|
+
result = TestHelper.collector(@handler.encrypt_stream(@stream))
|
328
|
+
@stream.stream = HexaPDF::StreamData.new(proc { result })
|
329
|
+
assert_equal('string', @handler.decrypt(@stream).stream)
|
330
|
+
end
|
331
|
+
|
332
|
+
it "doesn't encrypt strings in a document's Encrypt dictionary" do
|
333
|
+
@document.trailer[:Encrypt] = @handler.dict
|
334
|
+
@document.trailer[:Encrypt][:Mine] = 'string'
|
335
|
+
assert_equal('string', @handler.encrypt_string('string', @document.trailer[:Encrypt]))
|
336
|
+
end
|
337
|
+
|
338
|
+
it "doesn't encrypt XRef streams" do
|
339
|
+
@stream[:Type] = :XRef
|
340
|
+
assert_equal('string', @handler.encrypt_stream(@stream).resume)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
|
345
|
+
it "works correctly with different decryption and encryption handlers" do
|
346
|
+
test_file = File.join(TEST_DATA_DIR, 'standard-security-handler', 'nopwd-arc4-40bit-V1.pdf')
|
347
|
+
doc = HexaPDF::Document.new(io: StringIO.new(File.read(test_file)))
|
348
|
+
doc.encrypt(algorithm: :aes, password: 'test')
|
349
|
+
out = StringIO.new(''.b)
|
350
|
+
doc.write(out, update_fields: false)
|
351
|
+
|
352
|
+
assert_raises(HexaPDF::EncryptionError) { HexaPDF::Document.new(io: out) }
|
353
|
+
doc = HexaPDF::Document.new(io: out, decryption_opts: {password: 'test'})
|
354
|
+
assert_equal('D:20150409164600', doc.trailer[:Info].value[:ModDate])
|
355
|
+
end
|
356
|
+
end
|
@@ -0,0 +1,274 @@
|
|
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
|
+
if basename !~ /\Auserpwd/
|
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
|
+
|
93
|
+
before do
|
94
|
+
@document = HexaPDF::Document.new
|
95
|
+
@handler = HexaPDF::Encryption::StandardSecurityHandler.new(@document)
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "prepare_encryption" do
|
99
|
+
it "returns the encryption dictionary wrapped with a custom class" do
|
100
|
+
dict = @handler.set_up_encryption
|
101
|
+
assert_kind_of(HexaPDF::Encryption::StandardEncryptionDictionary, dict)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "sets the correct revision independent /Filter value" do
|
105
|
+
dict = @handler.set_up_encryption
|
106
|
+
assert_equal(:Standard, dict[:Filter])
|
107
|
+
end
|
108
|
+
|
109
|
+
it "sets the correct revision independent /P value" do
|
110
|
+
dict = @handler.set_up_encryption
|
111
|
+
assert_equal(@handler.class::Permissions::ALL | @handler.class::Permissions::RESERVED,
|
112
|
+
dict[:P])
|
113
|
+
dict = @handler.set_up_encryption(permissions: @handler.class::Permissions::COPY_CONTENT)
|
114
|
+
assert_equal(@handler.class::Permissions::COPY_CONTENT | @handler.class::Permissions::RESERVED,
|
115
|
+
dict[:P])
|
116
|
+
dict = @handler.set_up_encryption(permissions: [:modify_content, :modify_annotation])
|
117
|
+
assert_equal(@handler.class::Permissions::MODIFY_CONTENT | @handler.class::Permissions::MODIFY_ANNOTATION | @handler.class::Permissions::RESERVED,
|
118
|
+
dict[:P])
|
119
|
+
end
|
120
|
+
|
121
|
+
it "sets the correct revision independent /EncryptMetadata value" do
|
122
|
+
dict = @handler.set_up_encryption
|
123
|
+
assert(dict[:EncryptMetadata])
|
124
|
+
dict = @handler.set_up_encryption(encrypt_metadata: false)
|
125
|
+
refute(dict[:EncryptMetadata])
|
126
|
+
end
|
127
|
+
|
128
|
+
it "sets the correct encryption dictionary values for revision 2 and 3" do
|
129
|
+
arc4_assertions = lambda do |d|
|
130
|
+
assert_equal(32, d[:U].length)
|
131
|
+
assert_equal(32, d[:O].length)
|
132
|
+
refute(d.value.key?(:UE))
|
133
|
+
refute(d.value.key?(:OE))
|
134
|
+
refute(d.value.key?(:Perms))
|
135
|
+
end
|
136
|
+
dict = @handler.set_up_encryption(key_length: 40, algorithm: :arc4)
|
137
|
+
assert_equal(2, dict[:R])
|
138
|
+
arc4_assertions.call(dict)
|
139
|
+
|
140
|
+
dict = @handler.set_up_encryption(key_length: 128, algorithm: :arc4)
|
141
|
+
assert_equal(3, dict[:R])
|
142
|
+
arc4_assertions.call(dict)
|
143
|
+
end
|
144
|
+
|
145
|
+
it "sets the correct encryption dictionary values for revisions 4 and 6" do
|
146
|
+
crypt_filter = lambda do |d, r, alg, length|
|
147
|
+
assert_equal(r, d[:R])
|
148
|
+
assert_equal(alg == :AESV3 ? 48 : 32, d[:U].length)
|
149
|
+
assert_equal(alg == :AESV3 ? 48 : 32, d[:O].length)
|
150
|
+
assert_equal({CFM: alg, Length: length, AuthEvent: :DocOpen}, d[:CF][:StdCF])
|
151
|
+
assert_equal(:StdCF, d[:StrF])
|
152
|
+
assert_equal(:StdCF, d[:StmF])
|
153
|
+
assert_equal(:StdCF, d[:EFF])
|
154
|
+
end
|
155
|
+
|
156
|
+
dict = @handler.set_up_encryption(key_length: 128, algorithm: :arc4, force_V4: true)
|
157
|
+
refute(dict.value.key?(:UE))
|
158
|
+
refute(dict.value.key?(:OE))
|
159
|
+
refute(dict.value.key?(:Perms))
|
160
|
+
crypt_filter.call(dict, 4, :V2, 16)
|
161
|
+
|
162
|
+
dict = @handler.set_up_encryption(key_length: 128, algorithm: :aes)
|
163
|
+
refute(dict.value.key?(:UE))
|
164
|
+
refute(dict.value.key?(:OE))
|
165
|
+
refute(dict.value.key?(:Perms))
|
166
|
+
crypt_filter.call(dict, 4, :AESV2, 16)
|
167
|
+
|
168
|
+
dict = @handler.set_up_encryption(key_length: 256, algorithm: :aes)
|
169
|
+
assert_equal(32, dict[:UE].length)
|
170
|
+
assert_equal(32, dict[:OE].length)
|
171
|
+
assert_equal(16, dict[:Perms].length)
|
172
|
+
crypt_filter.call(dict, 6, :AESV3, 32)
|
173
|
+
end
|
174
|
+
|
175
|
+
it "uses the password keyword as fallback for the user and owner passwords" do
|
176
|
+
dict1 = @document.unwrap(@handler.set_up_encryption(password: 'user', owner_password: 'owner'))
|
177
|
+
dict2 = @document.unwrap(@handler.set_up_encryption(password: 'owner', user_password: 'user'))
|
178
|
+
dict3 = @document.unwrap(@handler.set_up_encryption(user_password: 'user', owner_password: 'owner'))
|
179
|
+
|
180
|
+
assert_equal(dict1[:U], dict2[:U])
|
181
|
+
assert_equal(dict2[:U], dict3[:U])
|
182
|
+
assert_equal(dict1[:O], dict2[:O])
|
183
|
+
assert_equal(dict2[:O], dict3[:O])
|
184
|
+
end
|
185
|
+
|
186
|
+
it "fails if the password contains invalid characters" do
|
187
|
+
assert_raises(HexaPDF::EncryptionError) { @handler.set_up_encryption(password: 'œ test') }
|
188
|
+
end
|
189
|
+
|
190
|
+
it "fails for unknown keywords" do
|
191
|
+
assert_raises(ArgumentError) { @handler.set_up_encryption(unknown: 'test') }
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
describe "prepare_decryption" do
|
197
|
+
it "fails if the /Filter value is incorrect" do
|
198
|
+
exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
|
199
|
+
@handler.set_up_decryption(Filter: :NonStandard, V: 2)
|
200
|
+
end
|
201
|
+
assert_match(/Invalid \/Filter/i, exp.message)
|
202
|
+
end
|
203
|
+
|
204
|
+
it "fails if the /R value is incorrect" do
|
205
|
+
exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
|
206
|
+
@handler.set_up_decryption(Filter: :Standard, V: 2, R: 5)
|
207
|
+
end
|
208
|
+
assert_match(/Invalid \/R/i, exp.message)
|
209
|
+
end
|
210
|
+
|
211
|
+
it "fails if the ID in the document's trailer is missing although it is needed" do
|
212
|
+
exp = assert_raises(HexaPDF::EncryptionError) do
|
213
|
+
@handler.set_up_decryption(Filter: :Standard, V: 2, R: 2)
|
214
|
+
end
|
215
|
+
assert_match(/Document ID/i, exp.message)
|
216
|
+
end
|
217
|
+
|
218
|
+
it "fails if the supplied password is invalid" do
|
219
|
+
exp = assert_raises(HexaPDF::EncryptionError) do
|
220
|
+
@handler.set_up_decryption(Filter: :Standard, V: 2, R: 6, U: 'a' * 48, O: 'a' * 48,
|
221
|
+
UE: 'a' * 32, OE: 'a' * 32)
|
222
|
+
end
|
223
|
+
assert_match(/Invalid password/i, exp.message)
|
224
|
+
end
|
225
|
+
|
226
|
+
describe "/Perms field checking" do
|
227
|
+
before do
|
228
|
+
@dict = @handler.set_up_encryption(key_length: 256, algorithm: :aes)
|
229
|
+
end
|
230
|
+
|
231
|
+
it "fails if the field cannot be decrypted" do
|
232
|
+
@dict[:Perms].succ!
|
233
|
+
exp = assert_raises(HexaPDF::EncryptionError) { @handler.set_up_decryption(@dict) }
|
234
|
+
assert_match(/cannot be decrypted/, exp.message)
|
235
|
+
end
|
236
|
+
|
237
|
+
it "fails if the /P field doesn't match" do
|
238
|
+
@dict[:P] = 500
|
239
|
+
exp = assert_raises(HexaPDF::EncryptionError) { @handler.set_up_decryption(@dict) }
|
240
|
+
assert_match(/\/P/, exp.message)
|
241
|
+
end
|
242
|
+
|
243
|
+
it "fails if the /EncryptMetadata field doesn't match" do
|
244
|
+
@dict[:EncryptMetadata] = false
|
245
|
+
exp = assert_raises(HexaPDF::EncryptionError) { @handler.set_up_decryption(@dict) }
|
246
|
+
assert_match(/\/EncryptMetadata/, exp.message)
|
247
|
+
end
|
248
|
+
|
249
|
+
it "ignores the /Perms when requested" do
|
250
|
+
obj = HexaPDF::Object.new(nil, oid: 1)
|
251
|
+
obj.value = @handler.encrypt_string('test', obj)
|
252
|
+
|
253
|
+
@dict[:P] = 500
|
254
|
+
@handler.set_up_decryption(@dict, check_permissions: false)
|
255
|
+
assert_equal('test', @handler.decrypt(obj).value)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
it "encryption key stays valid even if default dictionary values are set while setting up decryption" do
|
261
|
+
@document.encrypt(key_length: 128, algorithm: :aes)
|
262
|
+
assert(@document.security_handler.encryption_key_valid?)
|
263
|
+
|
264
|
+
@document.trailer[:Encrypt].delete(:EncryptMetadata)
|
265
|
+
handler = HexaPDF::Encryption::SecurityHandler.set_up_decryption(@document)
|
266
|
+
assert(handler.encryption_key_valid?)
|
267
|
+
end
|
268
|
+
|
269
|
+
it "returns an array of permission symbols" do
|
270
|
+
perms = @handler.class::Permissions::MODIFY_CONTENT | @handler.class::Permissions::COPY_CONTENT
|
271
|
+
@handler.set_up_encryption(permissions: perms)
|
272
|
+
assert_equal([:copy_content, :modify_content], @handler.permissions.sort)
|
273
|
+
end
|
274
|
+
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
|