hexapdf 1.4.0 → 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 +47 -2
- data/lib/hexapdf/cli/form.rb +1 -1
- data/lib/hexapdf/cli/images.rb +18 -3
- data/lib/hexapdf/cli.rb +3 -0
- 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/document.rb +1 -1
- data/lib/hexapdf/font/encoding/base.rb +27 -0
- data/lib/hexapdf/font/type1_wrapper.rb +1 -3
- data/lib/hexapdf/layout/table_box.rb +2 -2
- data/lib/hexapdf/serializer.rb +7 -7
- data/lib/hexapdf/type/annotation.rb +1 -1
- data/lib/hexapdf/type/font_type1.rb +12 -1
- data/lib/hexapdf/utils/sorted_tree_node.rb +4 -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/font/encoding/test_base.rb +20 -0
- data/test/hexapdf/test_dictionary.rb +15 -0
- data/test/hexapdf/test_document.rb +3 -2
- data/test/hexapdf/test_serializer.rb +2 -1
- data/test/hexapdf/type/annotations/test_widget.rb +8 -0
- data/test/hexapdf/type/test_annotation.rb +3 -0
- data/test/hexapdf/type/test_font_type1.rb +14 -0
- data/test/hexapdf/utils/test_sorted_tree_node.rb +11 -1
- 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,48 @@
|
|
|
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
|
+
|
|
23
|
+
## 1.4.1 - 2025-09-23
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
* [HexaPDF::Font::Encoding::Base#to_compact_array] for creating a compact array
|
|
28
|
+
representation of the encoding
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- CLI to handle missing file errors better
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
* Serialization of strings that need to be UTF-16 encoded when using encryption
|
|
37
|
+
* [HexaPDF::Document#write_to_string] to pass on arguments to `#write`
|
|
38
|
+
* [HexaPDF::Type::FontType1] validation to handle PDFs with an invalid value of
|
|
39
|
+
/SymbolEncoding for the /Encoding key
|
|
40
|
+
* [HexaPDF::Type::FontType1] validation to handle PDFs with an invalid value of
|
|
41
|
+
/StandardEncoding for the /Encoding key
|
|
42
|
+
* CLI command `hexapdf form` to ignore widgets that don't belong to any field
|
|
43
|
+
* Validation of invalid sorted tree root nodes with odd number of direct entries
|
|
44
|
+
|
|
45
|
+
|
|
1
46
|
## 1.4.0 - 2025-08-03
|
|
2
47
|
|
|
3
48
|
### Added
|
|
@@ -24,8 +69,8 @@
|
|
|
24
69
|
of a table cell
|
|
25
70
|
* [HexaPDF::Layout::Style::Quad#set] to allow setting a subset of values using a
|
|
26
71
|
hash
|
|
27
|
-
* CLI command `
|
|
28
|
-
* CLI command `
|
|
72
|
+
* CLI command `hexapdf form` to show the names of radio button widgets
|
|
73
|
+
* CLI command `hexapdf form` to show position and size of widgets in easier to
|
|
29
74
|
understand form
|
|
30
75
|
* Default signing handler to not set /DigestMethod entry on signature reference
|
|
31
76
|
dictionary anymore
|
data/lib/hexapdf/cli/form.rb
CHANGED
|
@@ -290,7 +290,7 @@ module HexaPDF
|
|
|
290
290
|
page.each_annotation do |annotation|
|
|
291
291
|
next unless annotation[:Subtype] == :Widget
|
|
292
292
|
field = annotation.form_field
|
|
293
|
-
next if field.concrete_field_type == :push_button
|
|
293
|
+
next if !field.concrete_field_type || field.concrete_field_type == :push_button
|
|
294
294
|
if with_seen || !seen[field.full_field_name]
|
|
295
295
|
yield(page, page_index, field, annotation)
|
|
296
296
|
seen[field.full_field_name] = true
|
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
|
|
@@ -132,7 +133,7 @@ module HexaPDF
|
|
|
132
133
|
printf("%5s %5s %9s %6s %6s %5s %4s %3s %5s %5s %6s %5s %8s\n",
|
|
133
134
|
"index", "page", "oid", "width", "height", "color", "comp", "bpc",
|
|
134
135
|
"x-ppi", "y-ppi", "size", "type", "writable")
|
|
135
|
-
puts("-" *
|
|
136
|
+
puts("-" * 84)
|
|
136
137
|
each_image(doc) do |image, index, pindex, (x_ppi, y_ppi)|
|
|
137
138
|
info = image.info
|
|
138
139
|
size = human_readable_file_size(image[:Length] + image[:SMask]&.[](:Length).to_i)
|
|
@@ -145,20 +146,34 @@ 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
|
|
168
|
+
if info.color_space == :cmyk && info.type == :jpeg
|
|
169
|
+
$stderr.puts "Note (image #{path}): JPEG uses CMYK colorspace and may " \
|
|
170
|
+
"need color post-processing"
|
|
171
|
+
end
|
|
158
172
|
elsif command_parser.verbosity_warning?
|
|
159
173
|
$stderr.puts "Warning (image #{index}): PDF image format not supported for writing"
|
|
160
174
|
end
|
|
161
175
|
end
|
|
176
|
+
puts "Created #{count} image files (out of #{total} selected)" if command_parser.verbosity_info?
|
|
162
177
|
end
|
|
163
178
|
|
|
164
179
|
# Iterates over all images.
|
data/lib/hexapdf/cli.rb
CHANGED
|
@@ -61,6 +61,9 @@ module HexaPDF
|
|
|
61
61
|
# Runs the CLI application.
|
|
62
62
|
def self.run(args = ARGV)
|
|
63
63
|
Application.new.parse(args)
|
|
64
|
+
rescue Errno::ENOENT => e
|
|
65
|
+
path = e.message.scan(/(?<= - ).*?$/).first
|
|
66
|
+
$stderr.puts "Problem encountered: No such file - #{path}"
|
|
64
67
|
rescue StandardError => e
|
|
65
68
|
$stderr.puts "Problem encountered: #{e.message}"
|
|
66
69
|
unless e.kind_of?(HexaPDF::Error)
|
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
|
data/lib/hexapdf/document.rb
CHANGED
|
@@ -81,6 +81,33 @@ module HexaPDF
|
|
|
81
81
|
@code_to_name.key(name)
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
+
# Returns the encoding in a compact array form.
|
|
85
|
+
#
|
|
86
|
+
# If the optional +base_encoding+ argument is specified, all codes that have the same value
|
|
87
|
+
# in the base encoding are ignored.
|
|
88
|
+
#
|
|
89
|
+
# The returned array is of the form:
|
|
90
|
+
#
|
|
91
|
+
# code1 name1 name2 ... code2 name3 name4 ...
|
|
92
|
+
#
|
|
93
|
+
# This means that name1 is associated with code1, name2 with code1 + 1 and so on.
|
|
94
|
+
#
|
|
95
|
+
# See: PDF 2.0 s9.6.5.1
|
|
96
|
+
def to_compact_array(base_encoding: nil)
|
|
97
|
+
result = []
|
|
98
|
+
last_code = -3
|
|
99
|
+
@code_to_name.sort.each do |code, name|
|
|
100
|
+
next if base_encoding&.name(code) == name
|
|
101
|
+
if last_code + 1 == code
|
|
102
|
+
result << name
|
|
103
|
+
else
|
|
104
|
+
result << code << name
|
|
105
|
+
end
|
|
106
|
+
last_code = code
|
|
107
|
+
end
|
|
108
|
+
result
|
|
109
|
+
end
|
|
110
|
+
|
|
84
111
|
end
|
|
85
112
|
|
|
86
113
|
end
|
|
@@ -279,9 +279,7 @@ module HexaPDF
|
|
|
279
279
|
if VALID_ENCODING_NAMES.include?(@encoding.encoding_name)
|
|
280
280
|
dict[:Encoding] = @encoding.encoding_name
|
|
281
281
|
elsif @encoding != @wrapped_font.encoding
|
|
282
|
-
|
|
283
|
-
(min..max).each {|code| differences << @encoding.name(code) }
|
|
284
|
-
dict[:Encoding] = {Differences: differences}
|
|
282
|
+
dict[:Encoding] = {Differences: @encoding.to_compact_array}
|
|
285
283
|
end
|
|
286
284
|
end
|
|
287
285
|
|
|
@@ -131,8 +131,8 @@ module HexaPDF
|
|
|
131
131
|
# fixed height (only if the actual content is smaller or equal than it):
|
|
132
132
|
#
|
|
133
133
|
# #>pdf-composer
|
|
134
|
-
# cells = [[{content: layout.text('A'),
|
|
135
|
-
# [{content: layout.text('C'),
|
|
134
|
+
# cells = [[{content: layout.text('A'), min_height: 5}, layout.text('B')],
|
|
135
|
+
# [{content: layout.text('C'), min_height: 40}, layout.text('D')]]
|
|
136
136
|
# composer.table(cells)
|
|
137
137
|
#
|
|
138
138
|
# The cells can be styled using a callable object for more complex styling:
|
data/lib/hexapdf/serializer.rb
CHANGED
|
@@ -276,16 +276,16 @@ module HexaPDF
|
|
|
276
276
|
#
|
|
277
277
|
# See: PDF2.0 s7.3.4
|
|
278
278
|
def serialize_string(obj)
|
|
279
|
+
if obj.encoding != Encoding::BINARY && obj.match?(/[^ -~\t\r\n]/)
|
|
280
|
+
utf16_encoded = true
|
|
281
|
+
obj = "\xFE\xFF".b << obj.encode(Encoding::UTF_16BE).force_encoding(Encoding::BINARY)
|
|
282
|
+
end
|
|
279
283
|
obj = if @encrypter && @object.kind_of?(HexaPDF::Object) && @object.indirect?
|
|
280
284
|
encrypter.encrypt_string(obj, @object)
|
|
281
|
-
elsif
|
|
282
|
-
|
|
283
|
-
"\xFE\xFF".b << obj.encode(Encoding::UTF_16BE).force_encoding(Encoding::BINARY)
|
|
284
|
-
else
|
|
285
|
-
obj.b
|
|
286
|
-
end
|
|
285
|
+
elsif utf16_encoded
|
|
286
|
+
obj
|
|
287
287
|
else
|
|
288
|
-
obj.
|
|
288
|
+
obj.b
|
|
289
289
|
end
|
|
290
290
|
obj.gsub!(/[()\\\r]/n, STRING_ESCAPE_MAP)
|
|
291
291
|
"(#{obj})"
|
|
@@ -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
|
|
@@ -183,7 +183,18 @@ module HexaPDF
|
|
|
183
183
|
|
|
184
184
|
encoding = self[:Encoding]
|
|
185
185
|
if encoding.kind_of?(Symbol) && !PREDEFINED_ENCODING.include?(encoding)
|
|
186
|
-
|
|
186
|
+
correctable = (self[:BaseFont] == :Symbol && encoding == :SymbolEncoding) ||
|
|
187
|
+
(!symbolic? && encoding == :StandardEncoding)
|
|
188
|
+
yield("The /Encoding value '#{encoding}' is invalid", correctable)
|
|
189
|
+
if correctable
|
|
190
|
+
if encoding == :SymbolEncoding
|
|
191
|
+
delete(:Encoding)
|
|
192
|
+
else
|
|
193
|
+
diffs = HexaPDF::Font::Encoding.for_name(:StandardEncoding).
|
|
194
|
+
to_compact_array(base_encoding: HexaPDF::Font::Encoding.for_name(:WinAnsiEncoding))
|
|
195
|
+
self[:Encoding] = {BaseEncoding: :WinAnsiEncoding, Differences: diffs}
|
|
196
|
+
end
|
|
197
|
+
end
|
|
187
198
|
end
|
|
188
199
|
end
|
|
189
200
|
|
|
@@ -322,7 +322,10 @@ module HexaPDF
|
|
|
322
322
|
if key?(container_name)
|
|
323
323
|
container = self[container_name]
|
|
324
324
|
if container.length.odd?
|
|
325
|
-
|
|
325
|
+
root_node = !key?(:Limits)
|
|
326
|
+
yield("Sorted tree #{root_node ? 'root' : 'leaf'} node contains odd number of entries",
|
|
327
|
+
root_node)
|
|
328
|
+
container.value.clear if root_node
|
|
326
329
|
return
|
|
327
330
|
end
|
|
328
331
|
index = 0
|
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
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
2
|
|
|
3
3
|
require 'test_helper'
|
|
4
|
+
require 'hexapdf/font/encoding'
|
|
4
5
|
require 'hexapdf/font/encoding/base'
|
|
5
6
|
|
|
6
7
|
describe HexaPDF::Font::Encoding::Base do
|
|
@@ -42,4 +43,23 @@ describe HexaPDF::Font::Encoding::Base do
|
|
|
42
43
|
assert_nil(@base.code(:Unknown))
|
|
43
44
|
end
|
|
44
45
|
end
|
|
46
|
+
|
|
47
|
+
describe "to_compact_array" do
|
|
48
|
+
before do
|
|
49
|
+
@base.code_to_name[66] = :B
|
|
50
|
+
@base.code_to_name[67] = :C
|
|
51
|
+
@base.code_to_name[20] = :space
|
|
52
|
+
@base.code_to_name[28] = :D
|
|
53
|
+
@base.code_to_name[29] = :E
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "returns the difference array" do
|
|
57
|
+
assert_equal([20, :space, 28, :D, :E, 65, :A, :B, :C], @base.to_compact_array)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "ignores the codes that are the same in the base encoding" do
|
|
61
|
+
std_encoding = HexaPDF::Font::Encoding.for_name(:StandardEncoding)
|
|
62
|
+
assert_equal([20, :space, 28, :D, :E, ], @base.to_compact_array(base_encoding: std_encoding))
|
|
63
|
+
end
|
|
64
|
+
end
|
|
45
65
|
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
|
|
|
@@ -611,5 +611,6 @@ describe HexaPDF::Document do
|
|
|
611
611
|
assert_equal(Encoding::ASCII_8BIT, str.encoding)
|
|
612
612
|
doc = HexaPDF::Document.new(io: StringIO.new(str))
|
|
613
613
|
assert_equal(:test, doc.trailer.info[:test])
|
|
614
|
+
assert_nil(doc.trailer.info[:ModDate])
|
|
614
615
|
end
|
|
615
616
|
end
|
|
@@ -181,7 +181,8 @@ describe HexaPDF::Serializer do
|
|
|
181
181
|
|
|
182
182
|
it "encrypts strings in indirect PDF objects" do
|
|
183
183
|
assert_serialized("(enc:1:test)", HexaPDF::Object.new("test", oid: 1))
|
|
184
|
-
assert_serialized("<</x[(enc:1
|
|
184
|
+
assert_serialized("<</x[(enc:1:\xFE\xFF\x00t\x00e\x00s\x00t\x00\xF6)]>>".b,
|
|
185
|
+
HexaPDF::Object.new({x: ["testö"]}, oid: 1))
|
|
185
186
|
end
|
|
186
187
|
|
|
187
188
|
it "doesn't encrypt strings in direct PDF objects" do
|
|
@@ -52,6 +52,14 @@ describe HexaPDF::Type::Annotations::Widget do
|
|
|
52
52
|
assert_kind_of(HexaPDF::Type::AcroForm::TextField, result)
|
|
53
53
|
refute_same(@widget.data, result.data)
|
|
54
54
|
end
|
|
55
|
+
|
|
56
|
+
it "works when the type of the field is defined higher up in the field hierarchy" do
|
|
57
|
+
@widget[:Parent] = {T: 'parent', Kids: [@widget]}
|
|
58
|
+
@widget[:Parent][:Parent] = {FT: :Tx, Kids: [@widget[:Parent]]}
|
|
59
|
+
result = @widget.form_field
|
|
60
|
+
assert_kind_of(HexaPDF::Type::AcroForm::TextField, result)
|
|
61
|
+
refute_same(@widget.data, result.data)
|
|
62
|
+
end
|
|
55
63
|
end
|
|
56
64
|
|
|
57
65
|
describe "background_color" do
|
|
@@ -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
|
|
|
@@ -143,5 +143,19 @@ describe HexaPDF::Type::FontType1 do
|
|
|
143
143
|
@font[:Encoding] = :Other
|
|
144
144
|
refute(@font.validate)
|
|
145
145
|
end
|
|
146
|
+
|
|
147
|
+
it "works around certain invalid PDFs with a /SymbolEncoding value for /Encoding" do
|
|
148
|
+
@font[:Encoding] = :SymbolEncoding
|
|
149
|
+
@font[:BaseFont] = :Symbol
|
|
150
|
+
assert(@font.validate)
|
|
151
|
+
refute(@font.key?(:Encoding))
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it "works around certain invalid PDFs with a /StandardEncoding value for /Encoding" do
|
|
155
|
+
@font[:Encoding] = :StandardEncoding
|
|
156
|
+
assert(@font.validate)
|
|
157
|
+
assert(:WinAnsiEncoding, @font[:Encoding][:BaseEncoding])
|
|
158
|
+
assert_equal([39, :quoteright, 96, :quoteleft], @font[:Encoding][:Differences][0, 4])
|
|
159
|
+
end
|
|
146
160
|
end
|
|
147
161
|
end
|
|
@@ -219,11 +219,21 @@ describe HexaPDF::Utils::SortedTreeNode do
|
|
|
219
219
|
it "checks that leaf node containers have an even number of entries" do
|
|
220
220
|
@kid11[:Names].delete_at(0)
|
|
221
221
|
refute(@kid11.validate do |message, c|
|
|
222
|
-
assert_match(/odd number/, message)
|
|
222
|
+
assert_match(/leaf.*odd number/, message)
|
|
223
223
|
refute(c)
|
|
224
224
|
end)
|
|
225
225
|
end
|
|
226
226
|
|
|
227
|
+
it "corrects a root node container with an odd number of entries" do
|
|
228
|
+
@root.value.clear
|
|
229
|
+
@root[:Names] = ['Test']
|
|
230
|
+
assert(@root.validate do |message, c|
|
|
231
|
+
assert_match(/root.*odd number/, message)
|
|
232
|
+
assert(c)
|
|
233
|
+
end)
|
|
234
|
+
assert(@root[:Names].empty?)
|
|
235
|
+
end
|
|
236
|
+
|
|
227
237
|
it "checks that the keys are of the correct type" do
|
|
228
238
|
@kid11[:Names][2] = 5
|
|
229
239
|
refute(@kid11.validate do |message, c|
|
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-08
|
|
10
|
+
date: 2025-12-08 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: cmdparse
|