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 +4 -4
- data/CHANGELOG.md +22 -0
- data/lib/hexapdf/cli/images.rb +13 -2
- data/lib/hexapdf/dictionary.rb +7 -1
- data/lib/hexapdf/digital_signature/cms_handler.rb +5 -1
- data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +24 -4
- data/lib/hexapdf/type/annotation.rb +1 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/digital_signature/common.rb +6 -1
- data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +12 -0
- data/test/hexapdf/digital_signature/test_cms_handler.rb +6 -0
- data/test/hexapdf/test_dictionary.rb +15 -0
- data/test/hexapdf/test_document.rb +2 -2
- data/test/hexapdf/type/test_annotation.rb +3 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: de7c1790b3c958a91f071b5c20063eafea93fed12a034b89890242fec25c3026
|
|
4
|
+
data.tar.gz: 0e201dc452930a2a81461be5bf9cd27d2749b92815498c06b37a1e2635a20d7d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/lib/hexapdf/cli/images.rb
CHANGED
|
@@ -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
|
-
|
|
159
|
+
count += 1
|
|
160
|
+
path = "#{@prefix}#{index}.#{image.info.extension}"
|
|
154
161
|
maybe_raise_on_existing_file(path)
|
|
155
|
-
|
|
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.
|
data/lib/hexapdf/dictionary.rb
CHANGED
|
@@ -301,7 +301,13 @@ module HexaPDF
|
|
|
301
301
|
yield(msg, true)
|
|
302
302
|
self[name] = obj.intern
|
|
303
303
|
else
|
|
304
|
-
yield(msg,
|
|
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
|
-
|
|
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
|
|
57
|
-
#
|
|
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
|
-
|
|
131
|
-
|
|
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
|
data/lib/hexapdf/version.rb
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
+
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-
|
|
10
|
+
date: 2025-12-08 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: cmdparse
|