hexapdf 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b88ce85ee9bc603011b9f5a278829d588da10f53614c0b84a57e2d7fa38f52dc
4
- data.tar.gz: ec2c8739ed69038e1297435550371bf329e516e9ef970fa0456502b15720d07b
3
+ metadata.gz: de7c1790b3c958a91f071b5c20063eafea93fed12a034b89890242fec25c3026
4
+ data.tar.gz: 0e201dc452930a2a81461be5bf9cd27d2749b92815498c06b37a1e2635a20d7d
5
5
  SHA512:
6
- metadata.gz: 103edc366ef9f48ddd6579f7137b3ab23b4266dc2df0a77ee5b89cb4256419a00727776158a7c7570a0b10075e4f062506d524ec71ee801c00fdd9e4726c8232
7
- data.tar.gz: f1f4a1af54445b2e7c3c9fc1adfa81fb9fad84f32461f58378fc550bd5aec16e16b276dadc7516ca5b0b6212394886d06f2cc2a3506dc2b2745b5a1f4c8136d1
6
+ metadata.gz: 1d1b13a5c28c83ca8ec4730cfc0af3016ceb14831c16587b000d8b69d0c7482d166bed21542f4929a8e4614fc732208c5670451a34339776b78a66dea8374949
7
+ data.tar.gz: 309e3aa2a80ec92b4fd35e72e9ab0c114fe4022467be9fb6fb5805085d6616ea8301ab30345a7322b009aeeffa5d6b052abfb8f0bafbaa9ca8b2223af3a6b223
data/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## 1.5.0 - 2025-12-08
2
+
3
+ ### Added
4
+
5
+ * Support for basic authentication to
6
+ [HexaPDF::DigitalSignature::Signing::TimestampHandler]
7
+
8
+ ### Changed
9
+
10
+ * Dictionary validation to delete field entries that have an invalid type
11
+ * CLI command `hexapdf images` to create directories specified in the `--prefix`
12
+ * CLI command `hexapdf images` to omit the dash in the file names if `--prefix`
13
+ points to a directory
14
+
15
+ ## Fixed
16
+
17
+ * [HexaPDF::Type::Annotation#appearance] to work in case /AP contains a value of
18
+ an invalid type
19
+ * [HexaPDF::DigitalSignature::CMSHandler] to throw an appropriate error when
20
+ encountering invalid signature contents
21
+
22
+
1
23
  ## 1.4.1 - 2025-09-23
2
24
 
3
25
  ### Added
@@ -35,6 +35,7 @@
35
35
  #++
36
36
 
37
37
  require 'set'
38
+ require 'fileutils'
38
39
  require 'hexapdf/cli/command'
39
40
 
40
41
  module HexaPDF
@@ -145,14 +146,23 @@ module HexaPDF
145
146
 
146
147
  # Extracts the images with the given indices.
147
148
  def extract_images(doc)
149
+ FileUtils.mkdir_p(File.dirname("#{@prefix}filename"))
150
+ prefix = File.directory?(@prefix) ? @prefix : "@{prefix}-"
151
+
148
152
  done = Set.new
153
+ count = total = 0
149
154
  each_image(doc) do |image, index, _|
150
155
  next unless (@indices.include?(index) || @indices.include?(0)) && !done.include?(index)
156
+ total += 1
151
157
  info = image.info
152
158
  if info.writable
153
- path = "#{@prefix}-#{index}.#{image.info.extension}"
159
+ count += 1
160
+ path = "#{@prefix}#{index}.#{image.info.extension}"
154
161
  maybe_raise_on_existing_file(path)
155
- puts "Extracting #{path}..." if command_parser.verbosity_info?
162
+ if command_parser.verbosity_info?
163
+ puts "Extracting image #{index} (#{image.width}x#{image.height}, " \
164
+ "#{info.color_space}, #{info.type}) to #{path}..."
165
+ end
156
166
  image.write(path)
157
167
  done << index
158
168
  if info.color_space == :cmyk && info.type == :jpeg
@@ -163,6 +173,7 @@ module HexaPDF
163
173
  $stderr.puts "Warning (image #{index}): PDF image format not supported for writing"
164
174
  end
165
175
  end
176
+ puts "Created #{count} image files (out of #{total} selected)" if command_parser.verbosity_info?
166
177
  end
167
178
 
168
179
  # Iterates over all images.
@@ -301,7 +301,13 @@ module HexaPDF
301
301
  yield(msg, true)
302
302
  self[name] = obj.intern
303
303
  else
304
- yield(msg, false)
304
+ yield(msg, !field.required? || field.default?)
305
+ if field.required? && field.default?
306
+ self[name] = obj = field.default
307
+ else
308
+ delete(name)
309
+ next
310
+ end
305
311
  end
306
312
  end
307
313
 
@@ -49,7 +49,11 @@ module HexaPDF
49
49
  # Creates a new signature handler for the given signature dictionary.
50
50
  def initialize(signature_dict)
51
51
  super
52
- @pkcs7 = OpenSSL::PKCS7.new(signature_dict.contents)
52
+ begin
53
+ @pkcs7 = OpenSSL::PKCS7.new(signature_dict.contents)
54
+ rescue
55
+ raise HexaPDF::Error, "Signature contents is invalid"
56
+ end
53
57
  end
54
58
 
55
59
  # Returns the common name of the signer.
@@ -53,8 +53,8 @@ module HexaPDF
53
53
  # == Usage
54
54
  #
55
55
  # It is necessary to provide at least the URL of the timestamp authority server (TSA) via
56
- # #tsa_url, everything else is optional and uses default values. The TSA server must not use
57
- # authentication to be usable.
56
+ # #tsa_url, everything else is optional and uses default values. The TSA server can optionally
57
+ # use HTTP basic authentication.
58
58
  #
59
59
  # Example:
60
60
  #
@@ -66,6 +66,18 @@ module HexaPDF
66
66
  # This value is required.
67
67
  attr_accessor :tsa_url
68
68
 
69
+ # The username for basic authentication to the TSA server.
70
+ #
71
+ # If the username is not set, no basic authentication is done.
72
+ #
73
+ # See: #tsa_password
74
+ attr_accessor :tsa_username
75
+
76
+ # The password for basic authentication to the TSA server.
77
+ #
78
+ # See: #tsa_username
79
+ attr_accessor :tsa_password
80
+
69
81
  # The hash algorithm to use for timestamping. Defaults to SHA512.
70
82
  attr_accessor :tsa_hash_algorithm
71
83
 
@@ -127,8 +139,14 @@ module HexaPDF
127
139
  req.message_imprint = digest.digest
128
140
  req.policy_id = tsa_policy_id if tsa_policy_id
129
141
 
130
- http_response = Net::HTTP.post(URI(tsa_url), req.to_der,
131
- 'content-type' => 'application/timestamp-query')
142
+ url = URI(tsa_url)
143
+ http_request = Net::HTTP::Post.new(url, 'Content-Type' => 'application/timestamp-query')
144
+ http_request.body = req.to_der
145
+ http_request.basic_auth(tsa_username, tsa_password) if tsa_username
146
+ http_response = Net::HTTP.start(url.hostname, url.port, use_ssl: (url.scheme == 'https')) do |http|
147
+ http.request(http_request)
148
+ end
149
+
132
150
  if http_response.kind_of?(Net::HTTPOK)
133
151
  response = OpenSSL::Timestamp::Response.new(http_response.body)
134
152
  if response.status == 0
@@ -136,6 +154,8 @@ module HexaPDF
136
154
  else
137
155
  raise HexaPDF::Error, "Timestamp token could not be created: #{response.failure_info}"
138
156
  end
157
+ elsif http_response.kind_of?(Net::HTTPUnauthorized)
158
+ raise HexaPDF::Error, "Basic authentication to the server failed: #{http_response.body}"
139
159
  else
140
160
  raise HexaPDF::Error, "Invalid TSA server response: #{http_response.body}"
141
161
  end
@@ -243,7 +243,7 @@ module HexaPDF
243
243
  # The appearance state in /AS or the one provided via +state_name+ is taken into account if
244
244
  # necessary.
245
245
  def appearance(type: :normal, state_name: self[:AS])
246
- entry = appearance_dict&.send("#{type}_appearance")
246
+ entry = appearance_dict&.send("#{type}_appearance") rescue nil
247
247
  if entry.kind_of?(HexaPDF::Dictionary) && !entry.kind_of?(HexaPDF::Stream)
248
248
  entry = entry[state_name]
249
249
  end
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '1.4.1'
40
+ VERSION = '1.5.0'
41
41
 
42
42
  end
@@ -112,7 +112,12 @@ module HexaPDF
112
112
  @tsa_server.mount_proc('/') do |request, response|
113
113
  @tsr = OpenSSL::Timestamp::Request.new(request.body)
114
114
  case @tsr.policy_id || '1.2.3.4.0'
115
- when '1.2.3.4.0', '1.2.3.4.2'
115
+ when '1.2.3.4.0', '1.2.3.4.2', '1.2.3.4.3'
116
+ if @tsr.policy_id == '1.2.3.4.3'
117
+ WEBrick::HTTPAuth.basic_auth(request, response, 'HexaPDF Auth') do |username, password|
118
+ username == 'hexatest' && password == 'hexapwd'
119
+ end
120
+ end
116
121
  fac = OpenSSL::Timestamp::Factory.new
117
122
  fac.gen_time = Time.now
118
123
  fac.serial_number = 1
@@ -67,6 +67,18 @@ describe HexaPDF::DigitalSignature::Signing::TimestampHandler do
67
67
  assert_equal("1.2.3.4.2", policy_id)
68
68
  end
69
69
 
70
+ it "allows using basic authentication on the server" do
71
+ @handler.tsa_policy_id = '1.2.3.4.3'
72
+ @handler.tsa_username = 'hexatest'
73
+ @handler.tsa_password = 'invalid'
74
+ msg = assert_raises(HexaPDF::Error) { @handler.sign(@data, @range) }
75
+ assert_match(/Basic authentication/, msg.message)
76
+
77
+ @handler.tsa_password = 'hexapwd'
78
+ token = OpenSSL::PKCS7.new(@handler.sign(@data, @range))
79
+ assert_equal(CERTIFICATES.ca_certificate.subject, token.signers[0].issuer)
80
+ end
81
+
70
82
  it "returns the serialized timestamp token" do
71
83
  token = OpenSSL::PKCS7.new(@handler.sign(@data, @range))
72
84
  assert_equal(CERTIFICATES.ca_certificate.subject, token.signers[0].issuer)
@@ -17,6 +17,12 @@ describe HexaPDF::DigitalSignature::CMSHandler do
17
17
  @handler = HexaPDF::DigitalSignature::CMSHandler.new(@dict)
18
18
  end
19
19
 
20
+ it "fails with an appropriate error if the the signature contents is invalid" do
21
+ @dict.contents = :Unknown
22
+ msg = assert_raises(HexaPDF::Error) { HexaPDF::DigitalSignature::CMSHandler.new(@dict) }
23
+ assert_match(/contents is invalid/, msg.message)
24
+ end
25
+
20
26
  it "returns the signer name" do
21
27
  assert_equal("RSA signer", @handler.signer_name)
22
28
  end
@@ -251,8 +251,23 @@ describe HexaPDF::Dictionary do
251
251
  refute(@obj.validate(auto_correct: false))
252
252
  assert(@obj.validate(auto_correct: true))
253
253
  @obj.value[:NameField] = "string"
254
+ refute(@obj.validate(auto_correct: false))
254
255
  assert(@obj.validate(auto_correct: true))
256
+
257
+ @test_class.define_field(:RequiredDefault, type: String, required: true, default: 'str')
258
+ @obj.value[:RequiredDefault] = 20
259
+ refute(@obj.validate(auto_correct: false))
260
+ assert_equal(20, @obj.value[:RequiredDefault])
255
261
  assert(@obj.validate(auto_correct: true))
262
+ assert_equal("str", @obj.value[:RequiredDefault])
263
+
264
+ @obj.value[:AllowedValues] = '20'
265
+ assert(@obj.validate(auto_correct: true))
266
+ refute(@obj.key?(:AllowedValues))
267
+
268
+ @obj.value[:Inherited] = 20
269
+ refute(@obj.validate(auto_correct: true))
270
+ refute(@obj.key?(:Inherited))
256
271
  end
257
272
 
258
273
  it "checks whether the value is an allowed one" do
@@ -347,7 +347,7 @@ describe HexaPDF::Document do
347
347
 
348
348
  it "validates the trailer object" do
349
349
  @doc.trailer[:ID] = :Symbol
350
- refute(@doc.validate {|_, _, obj| assert_same(@doc.trailer, obj) })
350
+ assert(@doc.validate {|_a, _b, obj| assert_same(@doc.trailer, obj) })
351
351
  end
352
352
 
353
353
  it "validates only loaded objects" do
@@ -391,7 +391,7 @@ describe HexaPDF::Document do
391
391
  end
392
392
 
393
393
  it "fails if the document is not valid" do
394
- @doc.trailer[:Size] = :Symbol
394
+ @doc.catalog[:PageLayout] = :invalid_value
395
395
  assert_raises(HexaPDF::Error) { @doc.write(StringIO.new(''.b)) }
396
396
  end
397
397
 
@@ -67,6 +67,9 @@ describe HexaPDF::Type::Annotation do
67
67
  it "returns the appearance stream of the given type" do
68
68
  assert_nil(@annot.appearance)
69
69
 
70
+ @annot[:AP] = 'some invalid type'
71
+ assert_nil(@annot.appearance)
72
+
70
73
  @annot[:AP] = {N: {}}
71
74
  assert_nil(@annot.appearance)
72
75
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hexapdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.1
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-09-23 00:00:00.000000000 Z
10
+ date: 2025-12-08 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: cmdparse