hexapdf 0.19.2 → 0.20.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +67 -0
  3. data/data/hexapdf/cert/demo_cert.rb +22 -0
  4. data/data/hexapdf/cert/root-ca.crt +119 -0
  5. data/data/hexapdf/cert/signing.crt +125 -0
  6. data/data/hexapdf/cert/signing.key +52 -0
  7. data/data/hexapdf/cert/sub-ca.crt +125 -0
  8. data/data/hexapdf/encoding/glyphlist.txt +4283 -4282
  9. data/data/hexapdf/encoding/zapfdingbats.txt +203 -202
  10. data/lib/hexapdf/cli/info.rb +21 -1
  11. data/lib/hexapdf/configuration.rb +26 -0
  12. data/lib/hexapdf/content/processor.rb +1 -1
  13. data/lib/hexapdf/document/signatures.rb +327 -0
  14. data/lib/hexapdf/document.rb +26 -0
  15. data/lib/hexapdf/font/encoding/glyph_list.rb +5 -6
  16. data/lib/hexapdf/importer.rb +1 -1
  17. data/lib/hexapdf/object.rb +5 -3
  18. data/lib/hexapdf/parser.rb +14 -9
  19. data/lib/hexapdf/rectangle.rb +0 -6
  20. data/lib/hexapdf/revision.rb +13 -6
  21. data/lib/hexapdf/task/dereference.rb +12 -4
  22. data/lib/hexapdf/task/optimize.rb +3 -3
  23. data/lib/hexapdf/type/acro_form/appearance_generator.rb +2 -4
  24. data/lib/hexapdf/type/acro_form/field.rb +2 -0
  25. data/lib/hexapdf/type/acro_form/form.rb +9 -1
  26. data/lib/hexapdf/type/annotation.rb +36 -3
  27. data/lib/hexapdf/type/font_simple.rb +1 -1
  28. data/lib/hexapdf/type/object_stream.rb +3 -1
  29. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +121 -0
  30. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +95 -0
  31. data/lib/hexapdf/type/signature/handler.rb +140 -0
  32. data/lib/hexapdf/type/signature/verification_result.rb +92 -0
  33. data/lib/hexapdf/type/signature.rb +236 -0
  34. data/lib/hexapdf/type.rb +1 -0
  35. data/lib/hexapdf/version.rb +1 -1
  36. data/lib/hexapdf/writer.rb +16 -8
  37. data/test/hexapdf/content/test_processor.rb +1 -1
  38. data/test/hexapdf/document/test_signatures.rb +225 -0
  39. data/test/hexapdf/task/test_optimize.rb +4 -1
  40. data/test/hexapdf/test_document.rb +28 -0
  41. data/test/hexapdf/test_object.rb +7 -2
  42. data/test/hexapdf/test_parser.rb +12 -0
  43. data/test/hexapdf/test_rectangle.rb +0 -7
  44. data/test/hexapdf/test_revision.rb +44 -14
  45. data/test/hexapdf/test_writer.rb +4 -3
  46. data/test/hexapdf/type/acro_form/test_field.rb +11 -1
  47. data/test/hexapdf/type/acro_form/test_form.rb +5 -0
  48. data/test/hexapdf/type/signature/common.rb +71 -0
  49. data/test/hexapdf/type/signature/test_adbe_pkcs7_detached.rb +99 -0
  50. data/test/hexapdf/type/signature/test_adbe_x509_rsa_sha1.rb +66 -0
  51. data/test/hexapdf/type/signature/test_handler.rb +102 -0
  52. data/test/hexapdf/type/signature/test_verification_result.rb +47 -0
  53. data/test/hexapdf/type/test_annotation.rb +40 -2
  54. data/test/hexapdf/type/test_font_simple.rb +5 -5
  55. data/test/hexapdf/type/test_object_stream.rb +9 -0
  56. data/test/hexapdf/type/test_signature.rb +131 -0
  57. metadata +21 -3
@@ -78,6 +78,25 @@ module HexaPDF
78
78
  self[:D] || self[:N]
79
79
  end
80
80
 
81
+ APPEARANCE_TYPE_TO_KEY = {normal: :N, rollover: :R, down: :D}.freeze #:nodoc:
82
+
83
+ # Sets the appearance of the given appearance +type+, which can either be :normal, :rollover
84
+ # or :down, to +appearance+.
85
+ #
86
+ # If the +state_name+ argument is provided, the +appearance+ is stored under the
87
+ # +state_name+ key in a sub-dictionary of the appearance.
88
+ def set_appearance(appearance, type: :normal, state_name: nil)
89
+ key = APPEARANCE_TYPE_TO_KEY.fetch(type) do
90
+ raise ArgumentError, "Invalid value for type specified: #{type.inspect}"
91
+ end
92
+ if state_name
93
+ self[key] = {} unless value[key].kind_of?(Hash)
94
+ self[key][state_name] = appearance
95
+ else
96
+ self[key] = appearance
97
+ end
98
+ end
99
+
81
100
  end
82
101
 
83
102
  # Border style dictionary used by various annotation types.
@@ -132,11 +151,12 @@ module HexaPDF
132
151
  # Returns the annotation's appearance stream of the given type (:normal, :rollover, or :down)
133
152
  # or +nil+ if it doesn't exist.
134
153
  #
135
- # The appearance state is taken into account if necessary.
136
- def appearance(type = :normal)
154
+ # The appearance state in /AS or the one provided via +state_name+ is taken into account if
155
+ # necessary.
156
+ def appearance(type: :normal, state_name: self[:AS])
137
157
  entry = appearance_dict&.send("#{type}_appearance")
138
158
  if entry.kind_of?(HexaPDF::Dictionary) && !entry.kind_of?(HexaPDF::Stream)
139
- entry = entry[self[:AS]]
159
+ entry = entry[state_name]
140
160
  end
141
161
  return unless entry.kind_of?(HexaPDF::Stream)
142
162
 
@@ -149,6 +169,19 @@ module HexaPDF
149
169
  end
150
170
  alias appearance? appearance
151
171
 
172
+ # Creates an empty appearance stream (a Form XObject) of the given type (:normal, :rollover,
173
+ # or :down) and returns it. If an appearance stream already exist, it is overwritten.
174
+ #
175
+ # If there can be multiple appearance streams for the annotation, use the +state_name+
176
+ # argument to provide the appearance state name.
177
+ def create_appearance(type: :normal, state_name: self[:AS])
178
+ xobject = document.add({Type: :XObject, Subtype: :Form,
179
+ BBox: [0, 0, self[:Rect].width, self[:Rect].height]})
180
+ self[:AP] ||= {}
181
+ appearance_dict.set_appearance(xobject, type: type, state_name: state_name)
182
+ xobject
183
+ end
184
+
152
185
  private
153
186
 
154
187
  # Helper method for bit field getter access.
@@ -133,7 +133,7 @@ module HexaPDF
133
133
  #
134
134
  # This method has to be implemented in subclasses.
135
135
  def encoding_from_font
136
- raise NotImplementedError
136
+ raise "Needs to be implemented in subclass"
137
137
  end
138
138
 
139
139
  # Uses the given base encoding and the differences array to create a DifferenceEncoding
@@ -178,7 +178,9 @@ module HexaPDF
178
178
  # Due to a bug in Adobe Acrobat, the Catalog may not be in an object stream if the
179
179
  # document is encrypted
180
180
  if obj.nil? || obj.null? || obj.gen != 0 || obj.kind_of?(Stream) || obj == encrypt_dict ||
181
- (encrypt_dict && obj.type == :Catalog)
181
+ (encrypt_dict && obj.type == :Catalog) ||
182
+ obj.type == :Sig || obj.type == :DocTimeStamp ||
183
+ (obj.respond_to?(:key?) && obj.key?(:ByteRange) && obj.key?(:Contents))
182
184
  delete_object(objects[index])
183
185
  next
184
186
  end
@@ -0,0 +1,121 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2021 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
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'openssl'
38
+ require 'hexapdf/type/signature'
39
+
40
+ module HexaPDF
41
+ module Type
42
+ class Signature
43
+
44
+ # The signature handler for the adbe.pkcs7.detached sub-filter.
45
+ class AdbePkcs7Detached < Handler
46
+
47
+ # Creates a new signature handler for the given signature dictionary.
48
+ def initialize(signature_dict)
49
+ super
50
+ @pkcs7 = OpenSSL::PKCS7.new(signature_dict.contents)
51
+ end
52
+
53
+ # Returns the common name of the signer.
54
+ def signer_name
55
+ signer_certificate.subject.to_a.assoc("CN")&.[](1) || super
56
+ end
57
+
58
+ # Returns the time of signing.
59
+ def signing_time
60
+ signer_info.signed_time rescue super
61
+ end
62
+
63
+ # Returns the certificate chain.
64
+ def certificate_chain
65
+ @pkcs7.certificates
66
+ end
67
+
68
+ # Returns the signer certificate (an instance of OpenSSL::X509::Certificate).
69
+ def signer_certificate
70
+ info = signer_info
71
+ certificate_chain.find {|cert| cert.issuer == info.issuer && cert.serial == info.serial }
72
+ end
73
+
74
+ # Returns the signer information object (an instance of OpenSSL::PKCS7::SignerInfo).
75
+ def signer_info
76
+ @pkcs7.signers.first
77
+ end
78
+
79
+ # Verifies the signature using the provided OpenSSL::X509::Store object.
80
+ def verify(store, allow_self_signed: false)
81
+ result = super
82
+
83
+ signer_info = self.signer_info
84
+ signer_certificate = self.signer_certificate
85
+ certificate_chain = self.certificate_chain
86
+
87
+ if certificate_chain.empty?
88
+ result.log(:error, "No certificates found in signature")
89
+ return result
90
+ end
91
+
92
+ if @pkcs7.signers.size != 1
93
+ result.log(:error, "Exactly one signer needed, found #{@pkcs7.signers.size}")
94
+ end
95
+
96
+ unless signer_certificate
97
+ result.log(:error, "Signer serial=#{signer_info.serial} issuer=#{signer_info.issuer} " \
98
+ "not found in certificates stored in PKCS7 object")
99
+ return result
100
+ end
101
+
102
+ key_usage = signer_certificate.extensions.find {|ext| ext.oid == 'keyUsage' }
103
+ unless key_usage.value.split(', ').include?("Digital Signature")
104
+ result.log(:error, "Certificate key usage is missing 'Digital Signature'")
105
+ end
106
+
107
+ if @pkcs7.verify(certificate_chain, store, signature_dict.signed_data,
108
+ OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY)
109
+ result.log(:info, "Signature valid")
110
+ else
111
+ result.log(:error, "Signature verification failed")
112
+ end
113
+
114
+ result
115
+ end
116
+
117
+ end
118
+
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,95 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2021 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
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'openssl'
38
+ require 'hexapdf/type/signature'
39
+
40
+ module HexaPDF
41
+ module Type
42
+ class Signature
43
+
44
+ # The signature handler for the adbe.x509.rsa_sha1 sub-filter.
45
+ #
46
+ # Since this handler is deprecated with PDF 2.0 it only provides the implementation for
47
+ # reading and verifying signatures.
48
+ class AdbeX509RsaSha1 < Handler
49
+
50
+ # Returns the certificate chain.
51
+ def certificate_chain
52
+ return [] unless signature_dict.key?(:Cert)
53
+ [signature_dict[:Cert]].flatten.map {|str| OpenSSL::X509::Certificate.new(str) }
54
+ end
55
+
56
+ # Returns the signer certificate (an instance of OpenSSL::X509::Certificate).
57
+ def signer_certificate
58
+ certificate_chain.first
59
+ end
60
+
61
+ # Verifies the signature using the provided OpenSSL::X509::Store object.
62
+ def verify(store, allow_self_signed: false)
63
+ result = super
64
+
65
+ signer_certificate = self.signer_certificate
66
+ certificate_chain = self.certificate_chain
67
+
68
+ if certificate_chain.empty?
69
+ result.log(:error, "No certificates for verification found")
70
+ return result
71
+ end
72
+
73
+ signature = OpenSSL::ASN1.decode(signature_dict.contents)
74
+ if signature.tag != OpenSSL::ASN1::OCTET_STRING
75
+ result.log(:error, "PKCS1 signature object invalid, octet string expected")
76
+ return result
77
+ end
78
+
79
+ store.verify(signer_certificate, certificate_chain)
80
+
81
+ if signer_certificate.public_key.verify(OpenSSL::Digest.new('SHA1'),
82
+ signature.value, signature_dict.signed_data)
83
+ result.log(:info, "Signature valid")
84
+ else
85
+ result.log(:error, "Signature verification failed")
86
+ end
87
+
88
+ result
89
+ end
90
+
91
+ end
92
+
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,140 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2021 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
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/type/signature'
38
+
39
+ module HexaPDF
40
+ module Type
41
+ class Signature
42
+
43
+ # The base signature handler providing common functionality.
44
+ #
45
+ # Specific signature handler need to override methods if necessary and implement the needed
46
+ # ones that don't have a default implementation.
47
+ class Handler
48
+
49
+ # The signature dictionary used by the handler.
50
+ attr_reader :signature_dict
51
+
52
+ # Creates a new signature handler for the given signature dictionary.
53
+ def initialize(signature_dict)
54
+ @signature_dict = signature_dict
55
+ end
56
+
57
+ # Returns the common name of the signer (/Name field of the signature dictionary).
58
+ def signer_name
59
+ @signature_dict[:Name]
60
+ end
61
+
62
+ # Returns the time of signing (/M field of the signature dictionary).
63
+ def signing_time
64
+ @signature_dict[:M]
65
+ end
66
+
67
+ # Returns the certificate chain.
68
+ #
69
+ # Needs to be implemented by specific handlers.
70
+ def certificate_chain
71
+ raise "Needs to be implemented by specific handlers"
72
+ end
73
+
74
+ # Returns the certificate used for signing.
75
+ #
76
+ # Needs to be implemented by specific handlers.
77
+ def signer_certificate
78
+ raise "Needs to be implemented by specific handlers"
79
+ end
80
+
81
+ # Verifies general signature properties and prepares the provided OpenSSL::X509::Store
82
+ # object for use by concrete implementations.
83
+ #
84
+ # Needs to be called by specific handlers.
85
+ def verify(store, allow_self_signed: false)
86
+ result = VerificationResult.new
87
+ check_certified_signature(result)
88
+ verify_signing_time(result)
89
+ store.verify_callback =
90
+ store_verification_callback(result, allow_self_signed: allow_self_signed)
91
+ result
92
+ end
93
+
94
+ protected
95
+
96
+ # Verifies that the signing time was within the validity period of the signer certificate.
97
+ def verify_signing_time(result)
98
+ time = signing_time
99
+ cert = signer_certificate
100
+ if time && cert && (time < cert.not_before || time > cert.not_after)
101
+ result.log(:error, "Signer certificate not valid at signing time")
102
+ end
103
+ end
104
+
105
+ DOCMDP_PERMS_MESSAGE_MAP = { # :nodoc:
106
+ 1 => "No changes allowed",
107
+ 2 => "Form filling and signing allowed",
108
+ 3 => "Form filling, signing and annotation manipulation allowed",
109
+ }
110
+
111
+ # Sets an informational message on +result+ whether the signature is a certified signature.
112
+ def check_certified_signature(result)
113
+ sigref = signature_dict[:Reference]&.find {|ref| ref[:TransformMethod] == :DocMDP }
114
+ if sigref && signature_dict.document.catalog[:Perms]&.[](:DocMDP) == signature_dict
115
+ perms = sigref[:TransformParams]&.[](:P) || 2
116
+ result.log(:info, "Certified signature (#{DOCMDP_PERMS_MESSAGE_MAP[perms]})")
117
+ end
118
+ end
119
+
120
+ # Returns the block that should be used as the OpenSSL::X509::Store verification callback.
121
+ #
122
+ # +result+:: The VerificationResult object that should be updated if problems are found.
123
+ #
124
+ # +allow_self_signed+:: Specifies whether self-signed certificates are allowed.
125
+ def store_verification_callback(result, allow_self_signed: false)
126
+ lambda do |_success, context|
127
+ if context.error == OpenSSL::X509::V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
128
+ context.error == OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN
129
+ result.log(allow_self_signed ? :info : :error, "Self-signed certificate found")
130
+ end
131
+
132
+ true
133
+ end
134
+ end
135
+
136
+ end
137
+
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,92 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2021 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
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/type/signature'
38
+
39
+ module HexaPDF
40
+ module Type
41
+ class Signature
42
+
43
+ # Holds the result information when verifying a signature.
44
+ class VerificationResult
45
+
46
+ # :nodoc:
47
+ MESSAGE_SORT_MAP = {
48
+ info: {warning: 1, error: 1, info: 0},
49
+ warning: {info: -1, error: 1, warning: 0},
50
+ error: {info: -1, warning: -1, error: 0},
51
+ }
52
+
53
+ # This structure represents a single status message, containing the type (:info, :warning,
54
+ # :error) and the content of the message.
55
+ Message = Struct.new(:type, :content) do
56
+ def <=>(other)
57
+ MESSAGE_SORT_MAP[type][other.type]
58
+ end
59
+ end
60
+
61
+ # An array with all result messages.
62
+ attr_reader :messages
63
+
64
+ # Creates an empty result object.
65
+ def initialize
66
+ @messages = []
67
+ end
68
+
69
+ # Returns +true+ if there are no error messages.
70
+ def success?
71
+ @messages.none? {|message| message.type == :error }
72
+ end
73
+
74
+ # Returns +true+ if there is at least one error message.
75
+ def failure?
76
+ !success?
77
+ end
78
+
79
+ # Adds a new message of the given type to this result object.
80
+ #
81
+ # +type+:: One of :info, :warning or :error.
82
+ #
83
+ # +content+:: The log message.
84
+ def log(type, content)
85
+ @messages << Message.new(type, content)
86
+ end
87
+
88
+ end
89
+
90
+ end
91
+ end
92
+ end