# frozen_string_literal: true require 'spec_helper' require 'tempfile' describe Prawn::Document::Security do describe 'Password padding' do include described_class it 'truncates long passwords' do pw = 'Long long string' * 30 padded = pad_password(pw) expect(padded.length).to eq(32) expect(padded).to eq(pw[0, 32]) end it 'pads short passwords' do pw = 'abcd' padded = pad_password(pw) expect(padded.length).to eq(32) expect(padded).to eq( pw + Prawn::Document::Security::PASSWORD_PADDING[0, 28] ) end it 'fullies pad null passwords' do pw = '' padded = pad_password(pw) expect(padded.length).to eq(32) expect(padded).to eq(Prawn::Document::Security::PASSWORD_PADDING) end end describe 'Setting permissions' do def doc_with_permissions(permissions) pdf = Prawn::Document.new # Make things easier to test pdf.singleton_class.__send__ :public, :permissions_value # class << pdf # public :permissions_value # end pdf.encrypt_document(permissions: permissions) pdf end it 'defaults to full permissions' do expect(doc_with_permissions({}).permissions_value).to eq(0xFFFFFFFF) expect(doc_with_permissions( print_document: true, modify_contents: true, copy_contents: true, modify_annotations: true ).permissions_value) .to eq(0xFFFFFFFF) end it 'clears the appropriate bits for each permission flag' do expect(doc_with_permissions(print_document: false).permissions_value) .to eq(0b1111_1111_1111_1111_1111_1111_1111_1011) expect(doc_with_permissions(modify_contents: false).permissions_value) .to eq(0b1111_1111_1111_1111_1111_1111_1111_0111) expect(doc_with_permissions(copy_contents: false).permissions_value) .to eq(0b1111_1111_1111_1111_1111_1111_1110_1111) expect(doc_with_permissions(modify_annotations: false).permissions_value) .to eq(0b1111_1111_1111_1111_1111_1111_1101_1111) end it 'raise_errors ArgumentError if invalid option is provided' do expect do doc_with_permissions(modify_document: false) end.to raise_error(ArgumentError) end end describe 'Encryption keys' do # Since PDF::Reader doesn't read encrypted PDF files, we just take the # roundabout method of verifying each step of the encryption. This works # fine because the encryption method is deterministic. let(:pdf) do Prawn::Document.new do |pdf| class << pdf public :owner_password_hash, :user_password_hash, :user_encryption_key end pdf.encrypt_document( user_password: 'foo', owner_password: 'bar', permissions: { print_document: false } ) end end it 'calculates the correct owner hash' do expect(pdf.owner_password_hash.unpack1('H*')) .to match(/^61CA855012/i) end it 'calculates the correct user hash' do expect(pdf.user_password_hash.unpack1('H*')) .to match(/^6BC8C51031/i) end it 'calculates the correct user_encryption_key' do expect(pdf.user_encryption_key.unpack1('H*').upcase) .to eq('B100AB6429') end end describe 'encrypted_pdf_object' do it 'delegates to PdfObject for simple types' do expect(PDF::Core.encrypted_pdf_object(true, nil, nil, nil)).to eq('true') expect(PDF::Core.encrypted_pdf_object(42, nil, nil, nil)).to eq('42') end it 'encrypts strings properly' do expect(PDF::Core.encrypted_pdf_object('foo', '12345', 123, 0)) .to eq('<4ad6e3>') end it 'encrypts literal strings properly' do expect(PDF::Core.encrypted_pdf_object( PDF::Core::LiteralString.new('foo'), '12345', 123, 0 )).to eq(bin_string("(J\xD6\xE3)")) expect(PDF::Core.encrypted_pdf_object( PDF::Core::LiteralString.new('lhfbqg3do5u0satu3fjf'), nil, 123, 0 )).to eq(bin_string( "(\xF1\x8B\\(\b\xBB\xE18S\x130~4*#\\(%\x87\xE7\x8E\\\n)" )) end it 'encrypts time properly' do expect(PDF::Core.encrypted_pdf_object( Time.utc(2050, 0o4, 26, 10, 17, 10), '12345', 123, 0 )).to eq(bin_string( "(h\x83\xBE\xDC\xEC\x99\x0F\xD7\\)%\x13\xD4$\xB8\xF0\x16\xB8\x80\xC5"\ "\xE91+\xCF)" )) end it 'properlies handle compound types' do expect(PDF::Core.encrypted_pdf_object({ Bar: 'foo' }, '12345', 123, 0)) .to eq( "<< /Bar <4ad6e3>\n>>" ) expect(PDF::Core.encrypted_pdf_object(%w[foo bar], '12345', 123, 0)) .to eq('[<4ad6e3> <4ed8fe>]') end end describe 'Reference#encrypted_object' do it 'encrypts references properly' do ref = PDF::Core::Reference.new(1, ['foo']) expect(ref.encrypted_object(nil)).to eq("1 0 obj\n[<4fca3f>]\nendobj\n") end it 'encrypts references with streams properly' do ref = PDF::Core::Reference.new(1, {}) ref << 'foo' result = bin_string( "1 0 obj\n<< /Length 3\n>>\nstream\nO\xCA?\nendstream\nendobj\n" ) expect(ref.encrypted_object(nil)).to eq(result) end end describe 'String#encrypted_object' do it 'encrypts stream properly' do stream = PDF::Core::Stream.new stream << 'foo' result = bin_string("stream\nO\xCA?\nendstream\n") expect(stream.encrypted_object(nil, 1, 0)).to eq(result) end end end