origami 1.2.7 → 2.0.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 +66 -0
- data/README.md +112 -0
- data/bin/config/pdfcop.conf.yml +232 -233
- data/bin/gui/about.rb +27 -37
- data/bin/gui/config.rb +108 -117
- data/bin/gui/file.rb +416 -365
- data/bin/gui/gtkhex.rb +1138 -1153
- data/bin/gui/hexview.rb +55 -57
- data/bin/gui/imgview.rb +48 -51
- data/bin/gui/menu.rb +388 -386
- data/bin/gui/properties.rb +114 -130
- data/bin/gui/signing.rb +571 -617
- data/bin/gui/textview.rb +77 -95
- data/bin/gui/treeview.rb +382 -387
- data/bin/gui/walker.rb +227 -232
- data/bin/gui/xrefs.rb +56 -60
- data/bin/pdf2pdfa +53 -57
- data/bin/pdf2ruby +212 -228
- data/bin/pdfcop +338 -348
- data/bin/pdfdecompress +58 -65
- data/bin/pdfdecrypt +56 -60
- data/bin/pdfencrypt +75 -80
- data/bin/pdfexplode +185 -182
- data/bin/pdfextract +201 -218
- data/bin/pdfmetadata +83 -82
- data/bin/pdfsh +4 -5
- data/bin/pdfwalker +1 -2
- data/bin/shell/.irbrc +45 -82
- data/bin/shell/console.rb +105 -130
- data/bin/shell/hexdump.rb +40 -64
- data/examples/README.md +34 -0
- data/examples/attachments/attachment.rb +38 -0
- data/examples/attachments/nested_document.rb +51 -0
- data/examples/encryption/encryption.rb +28 -0
- data/{samples/actions/triggerevents/trigger.rb → examples/events/events.rb} +13 -16
- data/examples/flash/flash.rb +37 -0
- data/{samples → examples}/flash/helloworld.swf +0 -0
- data/examples/forms/javascript.rb +54 -0
- data/examples/forms/xfa.rb +115 -0
- data/examples/javascript/hello_world.rb +22 -0
- data/examples/javascript/js_emulation.rb +54 -0
- data/examples/loop/goto.rb +32 -0
- data/examples/loop/named.rb +33 -0
- data/examples/signature/signature.rb +65 -0
- data/examples/uri/javascript.rb +56 -0
- data/examples/uri/open-uri.rb +21 -0
- data/examples/uri/submitform.rb +47 -0
- data/lib/origami.rb +29 -42
- data/lib/origami/3d.rb +350 -225
- data/lib/origami/acroform.rb +262 -288
- data/lib/origami/actions.rb +268 -288
- data/lib/origami/annotations.rb +697 -722
- data/lib/origami/array.rb +258 -184
- data/lib/origami/boolean.rb +74 -84
- data/lib/origami/catalog.rb +397 -434
- data/lib/origami/collections.rb +144 -0
- data/lib/origami/destinations.rb +233 -194
- data/lib/origami/dictionary.rb +253 -232
- data/lib/origami/encryption.rb +1274 -1243
- data/lib/origami/export.rb +232 -268
- data/lib/origami/extensions/fdf.rb +307 -220
- data/lib/origami/extensions/ppklite.rb +368 -435
- data/lib/origami/filespec.rb +197 -0
- data/lib/origami/filters.rb +301 -295
- data/lib/origami/filters/ascii.rb +177 -180
- data/lib/origami/filters/ccitt.rb +528 -535
- data/lib/origami/filters/crypt.rb +26 -35
- data/lib/origami/filters/dct.rb +46 -52
- data/lib/origami/filters/flate.rb +95 -94
- data/lib/origami/filters/jbig2.rb +49 -55
- data/lib/origami/filters/jpx.rb +38 -44
- data/lib/origami/filters/lzw.rb +189 -183
- data/lib/origami/filters/predictors.rb +221 -235
- data/lib/origami/filters/runlength.rb +103 -104
- data/lib/origami/font.rb +173 -186
- data/lib/origami/functions.rb +67 -81
- data/lib/origami/graphics.rb +25 -21
- data/lib/origami/graphics/colors.rb +178 -187
- data/lib/origami/graphics/instruction.rb +79 -85
- data/lib/origami/graphics/path.rb +142 -148
- data/lib/origami/graphics/patterns.rb +160 -167
- data/lib/origami/graphics/render.rb +43 -50
- data/lib/origami/graphics/state.rb +138 -153
- data/lib/origami/graphics/text.rb +188 -205
- data/lib/origami/graphics/xobject.rb +819 -815
- data/lib/origami/header.rb +63 -78
- data/lib/origami/javascript.rb +596 -597
- data/lib/origami/linearization.rb +285 -290
- data/lib/origami/metadata.rb +139 -148
- data/lib/origami/name.rb +112 -148
- data/lib/origami/null.rb +53 -62
- data/lib/origami/numeric.rb +162 -175
- data/lib/origami/obfuscation.rb +186 -174
- data/lib/origami/object.rb +593 -573
- data/lib/origami/outline.rb +42 -47
- data/lib/origami/outputintents.rb +73 -82
- data/lib/origami/page.rb +703 -592
- data/lib/origami/parser.rb +238 -290
- data/lib/origami/parsers/fdf.rb +41 -33
- data/lib/origami/parsers/pdf.rb +75 -95
- data/lib/origami/parsers/pdf/lazy.rb +137 -0
- data/lib/origami/parsers/pdf/linear.rb +64 -66
- data/lib/origami/parsers/ppklite.rb +34 -70
- data/lib/origami/pdf.rb +1030 -1005
- data/lib/origami/reference.rb +102 -102
- data/lib/origami/signature.rb +591 -609
- data/lib/origami/stream.rb +668 -551
- data/lib/origami/string.rb +397 -373
- data/lib/origami/template/patterns.rb +56 -0
- data/lib/origami/template/widgets.rb +151 -0
- data/lib/origami/trailer.rb +144 -158
- data/lib/origami/tree.rb +62 -0
- data/lib/origami/version.rb +23 -0
- data/lib/origami/webcapture.rb +88 -79
- data/lib/origami/xfa.rb +2863 -2882
- data/lib/origami/xreftable.rb +472 -384
- data/test/dataset/calc.pdf +85 -0
- data/test/dataset/crypto.pdf +82 -0
- data/test/dataset/empty.pdf +49 -0
- data/test/test_actions.rb +27 -0
- data/test/test_annotations.rb +90 -0
- data/test/test_pages.rb +31 -0
- data/test/test_pdf.rb +16 -0
- data/test/test_pdf_attachment.rb +34 -0
- data/test/test_pdf_create.rb +24 -0
- data/test/test_pdf_encrypt.rb +95 -0
- data/test/test_pdf_parse.rb +96 -0
- data/test/test_pdf_sign.rb +58 -0
- data/test/test_streams.rb +182 -0
- data/test/test_xrefs.rb +67 -0
- metadata +88 -58
- data/README +0 -67
- data/bin/pdf2graph +0 -121
- data/bin/pdfcocoon +0 -104
- data/lib/origami/file.rb +0 -233
- data/samples/README.txt +0 -45
- data/samples/actions/launch/calc.rb +0 -87
- data/samples/actions/launch/winparams.rb +0 -22
- data/samples/actions/loop/loopgoto.rb +0 -24
- data/samples/actions/loop/loopnamed.rb +0 -21
- data/samples/actions/named/named.rb +0 -31
- data/samples/actions/samba/smbrelay.rb +0 -26
- data/samples/actions/webbug/submitform.js +0 -26
- data/samples/actions/webbug/webbug-browser.rb +0 -68
- data/samples/actions/webbug/webbug-js.rb +0 -67
- data/samples/actions/webbug/webbug-reader.rb +0 -90
- data/samples/attachments/attach.rb +0 -40
- data/samples/attachments/attached.txt +0 -1
- data/samples/crypto/crypto.rb +0 -28
- data/samples/digsig/signed.rb +0 -46
- data/samples/exploits/cve-2008-2992-utilprintf.rb +0 -87
- data/samples/exploits/cve-2009-0927-geticon.rb +0 -65
- data/samples/exploits/exploit_customdictopen.rb +0 -55
- data/samples/exploits/getannots.rb +0 -69
- data/samples/flash/flash.rb +0 -31
- data/samples/javascript/attached.txt +0 -1
- data/samples/javascript/js.rb +0 -52
- data/templates/patterns.rb +0 -66
- data/templates/widgets.rb +0 -173
- data/templates/xdp.rb +0 -92
- data/test/ts_pdf.rb +0 -50
data/lib/origami/xreftable.rb
CHANGED
|
@@ -1,456 +1,544 @@
|
|
|
1
1
|
=begin
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
= Info
|
|
7
|
-
This file is part of Origami, PDF manipulation framework for Ruby
|
|
8
|
-
Copyright (C) 2010 Guillaume Delugré <guillaume AT security-labs DOT org>
|
|
9
|
-
All right reserved.
|
|
10
|
-
|
|
11
|
-
Origami is free software: you can redistribute it and/or modify
|
|
12
|
-
it under the terms of the GNU Lesser General Public License as published by
|
|
13
|
-
the Free Software Foundation, either version 3 of the License, or
|
|
14
|
-
(at your option) any later version.
|
|
15
|
-
|
|
16
|
-
Origami is distributed in the hope that it will be useful,
|
|
17
|
-
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
18
|
-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19
|
-
GNU Lesser General Public License for more details.
|
|
20
|
-
|
|
21
|
-
You should have received a copy of the GNU Lesser General Public License
|
|
22
|
-
along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
|
3
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
|
4
|
+
Copyright (C) 2016 Guillaume Delugré.
|
|
23
5
|
|
|
24
|
-
|
|
6
|
+
Origami is free software: you can redistribute it and/or modify
|
|
7
|
+
it under the terms of the GNU Lesser General Public License as published by
|
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
(at your option) any later version.
|
|
25
10
|
|
|
26
|
-
|
|
11
|
+
Origami is distributed in the hope that it will be useful,
|
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
GNU Lesser General Public License for more details.
|
|
27
15
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
#
|
|
31
|
-
# Tries to strip any xrefs information off the document.
|
|
32
|
-
#
|
|
33
|
-
def remove_xrefs
|
|
34
|
-
def delete_xrefstm(xrefstm)
|
|
35
|
-
prev = xrefstm.Prev
|
|
36
|
-
delete_object(xrefstm.reference)
|
|
16
|
+
You should have received a copy of the GNU Lesser General Public License
|
|
17
|
+
along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
|
37
18
|
|
|
38
|
-
|
|
39
|
-
delete_xrefstm(prev_stm)
|
|
40
|
-
end
|
|
41
|
-
end
|
|
19
|
+
=end
|
|
42
20
|
|
|
43
|
-
|
|
44
|
-
if rev.has_xrefstm?
|
|
45
|
-
delete_xrefstm(rev.xrefstm)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
if rev.trailer.has_dictionary? and rev.trailer.XRefStm.is_a?(Integer)
|
|
49
|
-
xrefstm = get_object_by_offset(rev.trailer.XRefStm)
|
|
21
|
+
module Origami
|
|
50
22
|
|
|
51
|
-
|
|
52
|
-
|
|
23
|
+
class PDF
|
|
24
|
+
#
|
|
25
|
+
# Tries to strip any xrefs information off the document.
|
|
26
|
+
#
|
|
27
|
+
def remove_xrefs
|
|
53
28
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
29
|
+
# Delete a XRefStream and its ancestors.
|
|
30
|
+
delete_xrefstm = -> (xrefstm) do
|
|
31
|
+
prev = xrefstm.Prev
|
|
32
|
+
delete_object(xrefstm.reference)
|
|
57
33
|
|
|
58
|
-
|
|
34
|
+
if prev.is_a?(Integer) and (prev_stm = get_object_by_offset(prev)).is_a?(XRefStream)
|
|
35
|
+
delete_xrefstm.call(prev_stm)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
59
38
|
|
|
60
|
-
|
|
61
|
-
|
|
39
|
+
@revisions.reverse_each do |rev|
|
|
40
|
+
if rev.has_xrefstm?
|
|
41
|
+
delete_xrefstm.call(rev.xrefstm)
|
|
42
|
+
end
|
|
62
43
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
#
|
|
66
|
-
class XRef
|
|
67
|
-
|
|
68
|
-
FREE = "f"
|
|
69
|
-
USED = "n"
|
|
70
|
-
FIRSTFREE = 65535
|
|
44
|
+
if rev.trailer.has_dictionary? and rev.trailer.XRefStm.is_a?(Integer)
|
|
45
|
+
xrefstm = get_object_by_offset(rev.trailer.XRefStm)
|
|
71
46
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
# _offset_:: The file _offset_ of the referenced Object.
|
|
79
|
-
# _generation_:: The generation number of the referenced Object.
|
|
80
|
-
# _state_:: The state of the referenced Object (FREE or USED).
|
|
81
|
-
#
|
|
82
|
-
def initialize(offset, generation, state)
|
|
83
|
-
@offset, @generation, @state = offset, generation, state
|
|
47
|
+
delete_xrefstm.call(xrefstm) if xrefstm.is_a?(XRefStream)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
rev.xrefstm = rev.xreftable = nil
|
|
51
|
+
end
|
|
52
|
+
end
|
|
84
53
|
end
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if stream.scan(@@regexp).nil?
|
|
89
|
-
raise InvalidXRefError, "Invalid XRef format"
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
offset = stream[1].to_i
|
|
93
|
-
generation = stream[2].to_i
|
|
94
|
-
state = stream[3]
|
|
95
|
-
|
|
96
|
-
XRef.new(offset, generation, state)
|
|
54
|
+
|
|
55
|
+
class InvalidXRefError < Error #:nodoc:
|
|
97
56
|
end
|
|
98
|
-
|
|
57
|
+
|
|
99
58
|
#
|
|
100
|
-
#
|
|
59
|
+
# Class representing a Cross-reference information.
|
|
101
60
|
#
|
|
102
|
-
|
|
103
|
-
off = ("0" * (10 - @offset.to_s.length)) + @offset.to_s
|
|
104
|
-
gen = ("0" * (5 - @generation.to_s.length)) + @generation.to_s
|
|
105
|
-
|
|
106
|
-
"#{off} #{gen} #{@state}" + EOL
|
|
107
|
-
end
|
|
61
|
+
class XRef
|
|
108
62
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
field2_w <<= 3
|
|
63
|
+
FREE = "f"
|
|
64
|
+
USED = "n"
|
|
65
|
+
FIRSTFREE = 65535
|
|
113
66
|
|
|
114
|
-
|
|
67
|
+
@@regexp = /(?<offset>\d{10}) (?<gen>\d{5}) (?<state>n|f)(\r\n| \r| \n)/
|
|
115
68
|
|
|
116
|
-
|
|
117
|
-
offset = '0' * (field1_w - offset.size) + offset
|
|
118
|
-
generation = @generation.to_s(2)
|
|
69
|
+
attr_accessor :offset, :generation, :state
|
|
119
70
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
#
|
|
129
|
-
# Class representing a cross-reference subsection.
|
|
130
|
-
# A subsection contains a continute set of XRef.
|
|
131
|
-
#
|
|
132
|
-
class Subsection
|
|
133
|
-
|
|
134
|
-
@@regexp = Regexp.new("(\\d+) (\\d+)" + WHITESPACES + "(\\r?\\n|\\r\\n?)")
|
|
135
|
-
|
|
136
|
-
attr_reader :range
|
|
137
|
-
|
|
138
|
-
#
|
|
139
|
-
# Creates a new XRef subsection.
|
|
140
|
-
# _start_:: The number of the first object referenced in the subsection.
|
|
141
|
-
# _entries_:: An array of XRef.
|
|
142
|
-
#
|
|
143
|
-
def initialize(start, entries = [])
|
|
144
|
-
|
|
145
|
-
@entries = entries.dup
|
|
146
|
-
@range = Range.new(start, start + entries.size - 1)
|
|
147
|
-
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def self.parse(stream) #:nodoc:
|
|
151
|
-
|
|
152
|
-
if stream.scan(@@regexp).nil?
|
|
153
|
-
raise InvalidXRefSubsectionError, "Bad subsection format"
|
|
71
|
+
#
|
|
72
|
+
# Creates a new XRef.
|
|
73
|
+
# _offset_:: The file _offset_ of the referenced Object.
|
|
74
|
+
# _generation_:: The generation number of the referenced Object.
|
|
75
|
+
# _state_:: The state of the referenced Object (FREE or USED).
|
|
76
|
+
#
|
|
77
|
+
def initialize(offset, generation, state)
|
|
78
|
+
@offset, @generation, @state = offset, generation, state
|
|
154
79
|
end
|
|
155
|
-
|
|
156
|
-
start = stream[1].to_i
|
|
157
|
-
size = stream[2].to_i
|
|
158
|
-
|
|
159
|
-
xrefs = []
|
|
160
|
-
size.times do
|
|
161
|
-
xrefs << XRef.parse(stream)
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
XRef::Subsection.new(start, xrefs)
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
#
|
|
168
|
-
# Returns whether this subsection contains information about a particular object.
|
|
169
|
-
# _no_:: The Object number.
|
|
170
|
-
#
|
|
171
|
-
def has_object?(no)
|
|
172
|
-
@range.include?(no)
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
#
|
|
176
|
-
# Returns XRef associated with a given object.
|
|
177
|
-
# _no_:: The Object number.
|
|
178
|
-
#
|
|
179
|
-
def [](no)
|
|
180
|
-
@entries[no - @range.begin]
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
#
|
|
184
|
-
# Processes each XRef in the subsection.
|
|
185
|
-
#
|
|
186
|
-
def each(&b)
|
|
187
|
-
@entries.each(&b)
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
#
|
|
191
|
-
# Outputs self into PDF code.
|
|
192
|
-
#
|
|
193
|
-
def to_s
|
|
194
|
-
section = "#{@range.begin} #{@range.end - @range.begin + 1}" + EOL
|
|
195
|
-
@entries.each { |xref|
|
|
196
|
-
section << xref.to_s
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
section
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
end
|
|
203
80
|
|
|
204
|
-
|
|
205
|
-
|
|
81
|
+
def self.parse(stream) #:nodoc:
|
|
82
|
+
if stream.scan(@@regexp).nil?
|
|
83
|
+
raise InvalidXRefError, "Invalid XRef format"
|
|
84
|
+
end
|
|
206
85
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
@@regexp_open = Regexp.new(WHITESPACES + "xref" + WHITESPACES + "(\\r?\\n|\\r\\n?)")
|
|
214
|
-
@@regexp_sub = Regexp.new("(\\d+) (\\d+)" + WHITESPACES + "(\\r?\\n|\\r\\n?)")
|
|
215
|
-
|
|
216
|
-
#
|
|
217
|
-
# Creates a new XRef section.
|
|
218
|
-
# _subsections_:: An array of XRefSubsection.
|
|
219
|
-
#
|
|
220
|
-
def initialize(subsections = [])
|
|
221
|
-
@subsections = subsections
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def self.parse(stream) #:nodoc:
|
|
225
|
-
|
|
226
|
-
if stream.skip(@@regexp_open).nil?
|
|
227
|
-
raise InvalidXRefSectionError, "No xref token found"
|
|
86
|
+
offset = stream['offset'].to_i
|
|
87
|
+
generation = stream['gen'].to_i
|
|
88
|
+
state = stream['state']
|
|
89
|
+
|
|
90
|
+
XRef.new(offset, generation, state)
|
|
228
91
|
end
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
92
|
+
|
|
93
|
+
#
|
|
94
|
+
# Returns true if the associated object is used.
|
|
95
|
+
#
|
|
96
|
+
def used?
|
|
97
|
+
@state == USED
|
|
233
98
|
end
|
|
234
99
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
#
|
|
242
|
-
def <<(subsection)
|
|
243
|
-
@subsections << subsection
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
#
|
|
247
|
-
# Returns a XRef associated with a given object.
|
|
248
|
-
# _no_:: The Object number.
|
|
249
|
-
#
|
|
250
|
-
def [](no)
|
|
251
|
-
@subsections.each { |s|
|
|
252
|
-
return s[no] if s.has_object?(no)
|
|
253
|
-
}
|
|
254
|
-
nil
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
alias :find :[]
|
|
258
|
-
|
|
259
|
-
#
|
|
260
|
-
# Processes each XRefSubsection.
|
|
261
|
-
#
|
|
262
|
-
def each(&b)
|
|
263
|
-
@subsections.each(&b)
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
#
|
|
267
|
-
# Outputs self into PDF code.
|
|
268
|
-
#
|
|
269
|
-
def to_s
|
|
270
|
-
"xref" << EOL << @subsections.join
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
end
|
|
100
|
+
#
|
|
101
|
+
# Returns true if the associated object is freed.
|
|
102
|
+
#
|
|
103
|
+
def free?
|
|
104
|
+
@state == FREE
|
|
105
|
+
end
|
|
276
106
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
107
|
+
#
|
|
108
|
+
# Outputs self into PDF code.
|
|
109
|
+
#
|
|
110
|
+
def to_s
|
|
111
|
+
off = @offset.to_s.rjust(10, '0')
|
|
112
|
+
gen = @generation.to_s.rjust(5, '0')
|
|
281
113
|
|
|
282
|
-
|
|
114
|
+
"#{off} #{gen} #{@state}" + EOL
|
|
115
|
+
end
|
|
283
116
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
117
|
+
def to_xrefstm_data(type_w, field1_w, field2_w)
|
|
118
|
+
type_w <<= 3
|
|
119
|
+
field1_w <<= 3
|
|
120
|
+
field2_w <<= 3
|
|
288
121
|
|
|
289
|
-
|
|
122
|
+
type = ((@state == FREE) ? "\000" : "\001").unpack("B#{type_w}")[0]
|
|
290
123
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
field2_w <<= 3
|
|
124
|
+
offset = @offset.to_s(2).rjust(field1_w, '0')
|
|
125
|
+
generation = @generation.to_s(2).rjust(field2_w, '0')
|
|
294
126
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
objstmno = '0' * (field1_w - objstmno.size) + objstmno
|
|
298
|
-
index = @index.to_s(2)
|
|
299
|
-
index = '0' * (field2_w - index.size) + index
|
|
127
|
+
[ type , offset, generation ].pack("B#{type_w}B#{field1_w}B#{field2_w}")
|
|
128
|
+
end
|
|
300
129
|
|
|
301
|
-
|
|
302
|
-
|
|
130
|
+
class InvalidXRefSubsectionError < Error #:nodoc:
|
|
131
|
+
end
|
|
303
132
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
133
|
+
#
|
|
134
|
+
# Class representing a cross-reference subsection.
|
|
135
|
+
# A subsection contains a continute set of XRef.
|
|
136
|
+
#
|
|
137
|
+
class Subsection
|
|
138
|
+
@@regexp = Regexp.new("(?<start>\\d+) (?<size>\\d+)" + WHITESPACES + "(\\r?\\n|\\r\\n?)")
|
|
139
|
+
|
|
140
|
+
attr_reader :range
|
|
141
|
+
|
|
142
|
+
#
|
|
143
|
+
# Creates a new XRef subsection.
|
|
144
|
+
# _start_:: The number of the first object referenced in the subsection.
|
|
145
|
+
# _entries_:: An array of XRef.
|
|
146
|
+
#
|
|
147
|
+
def initialize(start, entries = [])
|
|
148
|
+
@entries = entries.dup
|
|
149
|
+
@range = Range.new(start, start + entries.size - 1)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def self.parse(stream) #:nodoc:
|
|
153
|
+
if stream.scan(@@regexp).nil?
|
|
154
|
+
raise InvalidXRefSubsectionError, "Bad subsection format"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
start = stream['start'].to_i
|
|
158
|
+
size = stream['size'].to_i
|
|
159
|
+
|
|
160
|
+
xrefs = []
|
|
161
|
+
size.times do
|
|
162
|
+
xrefs << XRef.parse(stream)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
XRef::Subsection.new(start, xrefs)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
#
|
|
169
|
+
# Returns whether this subsection contains information about a particular object.
|
|
170
|
+
# _no_:: The Object number.
|
|
171
|
+
#
|
|
172
|
+
def has_object?(no)
|
|
173
|
+
@range.include?(no)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
#
|
|
177
|
+
# Returns XRef associated with a given object.
|
|
178
|
+
# _no_:: The Object number.
|
|
179
|
+
#
|
|
180
|
+
def [](no)
|
|
181
|
+
@entries[no - @range.begin]
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
#
|
|
185
|
+
# Processes each XRef in the subsection.
|
|
186
|
+
#
|
|
187
|
+
def each(&b)
|
|
188
|
+
@entries.each(&b)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
#
|
|
192
|
+
# Processes each XRef in the subsection, passing the XRef and the object number to the block.
|
|
193
|
+
#
|
|
194
|
+
def each_with_number
|
|
195
|
+
return enum_for(__method__) { self.size } unless block_given?
|
|
196
|
+
|
|
197
|
+
counter = @range.to_enum
|
|
198
|
+
@entries.each do |entry|
|
|
199
|
+
yield(entry, counter.next)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
#
|
|
204
|
+
# The number of entries in the subsection.
|
|
205
|
+
#
|
|
206
|
+
def size
|
|
207
|
+
@entries.size
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
#
|
|
211
|
+
# Outputs self into PDF code.
|
|
212
|
+
#
|
|
213
|
+
def to_s
|
|
214
|
+
section = "#{@range.begin} #{@range.end - @range.begin + 1}" + EOL
|
|
215
|
+
@entries.each do |xref|
|
|
216
|
+
section << xref.to_s
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
section
|
|
220
|
+
end
|
|
221
|
+
end
|
|
307
222
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
#
|
|
311
|
-
class XRefStream < Stream
|
|
223
|
+
class InvalidXRefSectionError < Error #:nodoc:
|
|
224
|
+
end
|
|
312
225
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
226
|
+
#
|
|
227
|
+
# Class representing a Cross-reference table.
|
|
228
|
+
# A section contains a set of XRefSubsection.
|
|
229
|
+
#
|
|
230
|
+
class Section
|
|
231
|
+
TOKEN = "xref"
|
|
232
|
+
|
|
233
|
+
@@regexp_open = Regexp.new(WHITESPACES + TOKEN + WHITESPACES + "(\\r?\\n|\\r\\n?)")
|
|
234
|
+
@@regexp_sub = Regexp.new("(\\d+) (\\d+)" + WHITESPACES + "(\\r?\\n|\\r\\n?)")
|
|
235
|
+
|
|
236
|
+
#
|
|
237
|
+
# Creates a new XRef section.
|
|
238
|
+
# _subsections_:: An array of XRefSubsection.
|
|
239
|
+
#
|
|
240
|
+
def initialize(subsections = [])
|
|
241
|
+
@subsections = subsections
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def self.parse(stream) #:nodoc:
|
|
245
|
+
if stream.skip(@@regexp_open).nil?
|
|
246
|
+
raise InvalidXRefSectionError, "No xref token found"
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
subsections = []
|
|
250
|
+
while stream.match?(@@regexp_sub) do
|
|
251
|
+
subsections << XRef::Subsection.parse(stream)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
XRef::Section.new(subsections)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
#
|
|
258
|
+
# Appends a new subsection.
|
|
259
|
+
# _subsection_:: A XRefSubsection.
|
|
260
|
+
#
|
|
261
|
+
def <<(subsection)
|
|
262
|
+
@subsections << subsection
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
#
|
|
266
|
+
# Returns a XRef associated with a given object.
|
|
267
|
+
# _no_:: The Object number.
|
|
268
|
+
#
|
|
269
|
+
def [](no)
|
|
270
|
+
@subsections.each do |s|
|
|
271
|
+
return s[no] if s.has_object?(no)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
nil
|
|
275
|
+
end
|
|
276
|
+
alias find []
|
|
277
|
+
|
|
278
|
+
#
|
|
279
|
+
# Processes each XRef in each Subsection.
|
|
280
|
+
#
|
|
281
|
+
def each(&b)
|
|
282
|
+
return enum_for(__method__) { self.size } unless block_given?
|
|
283
|
+
|
|
284
|
+
@subsections.each do |subsection|
|
|
285
|
+
subsection.each(&b)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
#
|
|
290
|
+
# Processes each XRef in each Subsection, passing the XRef and the object number.
|
|
291
|
+
#
|
|
292
|
+
def each_with_number(&b)
|
|
293
|
+
return enum_for(__method__) { self.size } unless block_given?
|
|
294
|
+
|
|
295
|
+
@subsections.each do |subsection|
|
|
296
|
+
subsection.each_with_number(&b)
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
#
|
|
301
|
+
# Processes each Subsection in this table.
|
|
302
|
+
#
|
|
303
|
+
def each_subsection(&b)
|
|
304
|
+
@subsections.each(&b)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
#
|
|
308
|
+
# Returns an Array of Subsection.
|
|
309
|
+
#
|
|
310
|
+
def subsections
|
|
311
|
+
@subsections
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
#
|
|
315
|
+
# Clear all the entries.
|
|
316
|
+
#
|
|
317
|
+
def clear
|
|
318
|
+
@subsections.clear
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
#
|
|
322
|
+
# The number of XRef entries in the Section.
|
|
323
|
+
#
|
|
324
|
+
def size
|
|
325
|
+
@subsections.reduce(0) { |total, subsection| total + subsection.size }
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
#
|
|
329
|
+
# Outputs self into PDF code.
|
|
330
|
+
#
|
|
331
|
+
def to_s
|
|
332
|
+
"xref" << EOL << @subsections.join
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|
|
319
336
|
|
|
320
337
|
#
|
|
321
|
-
#
|
|
338
|
+
# An xref poiting to an Object embedded in an ObjectStream.
|
|
322
339
|
#
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
field :Index, :Type => Array
|
|
326
|
-
field :Prev, :Type => Integer
|
|
327
|
-
field :W, :Type => Array, :Required => true
|
|
340
|
+
class XRefToCompressedObj
|
|
341
|
+
attr_accessor :objstmno, :index
|
|
328
342
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
super(data, dictionary)
|
|
339
|
-
|
|
340
|
-
@xrefs = nil
|
|
341
|
-
end
|
|
343
|
+
def initialize(objstmno, index)
|
|
344
|
+
@objstmno = objstmno
|
|
345
|
+
@index = index
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def to_xrefstm_data(type_w, field1_w, field2_w)
|
|
349
|
+
type_w <<= 3
|
|
350
|
+
field1_w <<= 3
|
|
351
|
+
field2_w <<= 3
|
|
342
352
|
|
|
343
|
-
|
|
344
|
-
|
|
353
|
+
type = "\002".unpack("B#{type_w}")[0]
|
|
354
|
+
objstmno = @objstmno.to_s(2).rjust(field1_w, '0')
|
|
355
|
+
index = @index.to_s(2).rjust(field2_w, '0')
|
|
345
356
|
|
|
346
|
-
|
|
357
|
+
[ type , objstmno, index ].pack("B#{type_w}B#{field1_w}B#{field2_w}")
|
|
358
|
+
end
|
|
347
359
|
end
|
|
348
360
|
|
|
361
|
+
class InvalidXRefStreamObjectError < InvalidStreamObjectError ; end
|
|
362
|
+
|
|
349
363
|
#
|
|
350
|
-
#
|
|
364
|
+
# Class representing a XRef Stream.
|
|
351
365
|
#
|
|
352
|
-
|
|
353
|
-
|
|
366
|
+
class XRefStream < Stream
|
|
367
|
+
include Enumerable
|
|
368
|
+
include StandardObject
|
|
369
|
+
|
|
370
|
+
XREF_FREE = 0
|
|
371
|
+
XREF_USED = 1
|
|
372
|
+
XREF_COMPRESSED = 2
|
|
373
|
+
|
|
374
|
+
#
|
|
375
|
+
# Xref fields
|
|
376
|
+
#
|
|
377
|
+
field :Type, :Type => Name, :Default => :XRef, :Required => true, :Version => "1.5"
|
|
378
|
+
field :Size, :Type => Integer, :Required => true
|
|
379
|
+
field :Index, :Type => Array.of(Integer, Integer)
|
|
380
|
+
field :Prev, :Type => Integer
|
|
381
|
+
field :W, :Type => Array.of(Integer, length: 3), :Required => true
|
|
382
|
+
|
|
383
|
+
#
|
|
384
|
+
# Trailer fields
|
|
385
|
+
#
|
|
386
|
+
field :Root, :Type => Catalog, :Required => true
|
|
387
|
+
field :Encrypt, :Type => Encryption::Standard::Dictionary
|
|
388
|
+
field :Info, :Type => Metadata
|
|
389
|
+
field :ID, :Type => Array.of(String, length: 2)
|
|
390
|
+
|
|
391
|
+
def initialize(data = "", dictionary = {})
|
|
392
|
+
super(data, dictionary)
|
|
393
|
+
|
|
394
|
+
@xrefs = nil
|
|
395
|
+
end
|
|
354
396
|
|
|
355
|
-
|
|
356
|
-
|
|
397
|
+
def entries
|
|
398
|
+
load! if @xrefs.nil?
|
|
357
399
|
|
|
358
|
-
|
|
400
|
+
@xrefs
|
|
401
|
+
end
|
|
359
402
|
|
|
360
|
-
|
|
361
|
-
|
|
403
|
+
#
|
|
404
|
+
# Returns XRef entries present in this stream.
|
|
405
|
+
#
|
|
406
|
+
def pre_build #:nodoc:
|
|
407
|
+
load! if @xrefs.nil?
|
|
362
408
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
#
|
|
366
|
-
def <<(xref)
|
|
367
|
-
load! if @xrefs.nil?
|
|
409
|
+
self.W = [ 1, 2, 2 ] unless has_field?(:W)
|
|
410
|
+
self.Size = @xrefs.length + 1
|
|
368
411
|
|
|
369
|
-
|
|
370
|
-
end
|
|
412
|
+
save!
|
|
371
413
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
#
|
|
375
|
-
def each(&b)
|
|
376
|
-
load! if @xrefs.nil?
|
|
414
|
+
super
|
|
415
|
+
end
|
|
377
416
|
|
|
378
|
-
|
|
379
|
-
|
|
417
|
+
#
|
|
418
|
+
# Adds an XRef to this Stream.
|
|
419
|
+
#
|
|
420
|
+
def <<(xref)
|
|
421
|
+
load! if @xrefs.nil?
|
|
380
422
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
#
|
|
384
|
-
def find(no)
|
|
385
|
-
load! if @xrefs.nil?
|
|
423
|
+
@xrefs << xref
|
|
424
|
+
end
|
|
386
425
|
|
|
387
|
-
|
|
426
|
+
#
|
|
427
|
+
# Iterates over each XRef present in the stream.
|
|
428
|
+
#
|
|
429
|
+
def each(&b)
|
|
430
|
+
load! if @xrefs.nil?
|
|
388
431
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
brange = ranges[i*2].to_i
|
|
392
|
-
size = ranges[i*2+1].to_i
|
|
393
|
-
return @xrefs[index + no - brange] if Range.new(brange, brange + size - 1) === no
|
|
432
|
+
@xrefs.each(&b)
|
|
433
|
+
end
|
|
394
434
|
|
|
395
|
-
|
|
396
|
-
|
|
435
|
+
#
|
|
436
|
+
# Iterates over each XRef present in the stream, passing the XRef and its object number.
|
|
437
|
+
#
|
|
438
|
+
def each_with_number
|
|
439
|
+
return enum_for(__method__) unless block_given?
|
|
440
|
+
|
|
441
|
+
load! if @xrefs.nil?
|
|
442
|
+
|
|
443
|
+
ranges = object_ranges
|
|
444
|
+
xrefs = @xrefs.to_enum
|
|
445
|
+
|
|
446
|
+
ranges.each do |range|
|
|
447
|
+
range.each do |no|
|
|
448
|
+
begin
|
|
449
|
+
yield(xrefs.next, no)
|
|
450
|
+
rescue StopIteration
|
|
451
|
+
raise InvalidXRefStreamObjectError, "Range is bigger than number of entries"
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
end
|
|
397
456
|
|
|
398
|
-
|
|
399
|
-
|
|
457
|
+
#
|
|
458
|
+
# Returns an XRef matching this object number.
|
|
459
|
+
#
|
|
460
|
+
def find(no)
|
|
461
|
+
load! if @xrefs.nil?
|
|
400
462
|
|
|
401
|
-
|
|
402
|
-
self.data = ''
|
|
403
|
-
@xrefs = []
|
|
404
|
-
self.Index = []
|
|
405
|
-
end
|
|
463
|
+
ranges = object_ranges
|
|
406
464
|
|
|
407
|
-
|
|
465
|
+
index = 0
|
|
466
|
+
ranges.each do |range|
|
|
467
|
+
return @xrefs[index + no - range.begin] if range.cover?(no)
|
|
408
468
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
widths = self.W
|
|
469
|
+
index += range.size
|
|
470
|
+
end
|
|
412
471
|
|
|
413
|
-
|
|
414
|
-
raise InvalidXRefStreamObjectError, "W field must be an array of 3 integers"
|
|
472
|
+
nil
|
|
415
473
|
end
|
|
416
474
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
field2_w = self.W[2]
|
|
422
|
-
|
|
423
|
-
entrymask = "B#{type_w << 3}B#{field1_w << 3}B#{field2_w << 3}"
|
|
424
|
-
size = @data.size / (type_w + field1_w + field2_w)
|
|
425
|
-
|
|
426
|
-
xentries = @data.unpack(entrymask * size).map!{|field| field.to_i(2) }
|
|
427
|
-
|
|
428
|
-
@xrefs = []
|
|
429
|
-
size.times do |i|
|
|
430
|
-
type,field1,field2 = xentries[i*3].ord,xentries[i*3+1].ord,xentries[i*3+2].ord
|
|
431
|
-
case type
|
|
432
|
-
when XREF_FREE
|
|
433
|
-
@xrefs << XRef.new(field1, field2, XRef::FREE)
|
|
434
|
-
when XREF_USED
|
|
435
|
-
@xrefs << XRef.new(field1, field2, XRef::USED)
|
|
436
|
-
when XREF_COMPRESSED
|
|
437
|
-
@xrefs << XRefToCompressedObj.new(field1, field2)
|
|
438
|
-
end
|
|
475
|
+
def clear
|
|
476
|
+
self.data = ''
|
|
477
|
+
@xrefs = []
|
|
478
|
+
self.Index = []
|
|
439
479
|
end
|
|
440
|
-
else
|
|
441
|
-
@xrefs = []
|
|
442
|
-
end
|
|
443
|
-
end
|
|
444
480
|
|
|
445
|
-
|
|
446
|
-
self.data = ""
|
|
481
|
+
private
|
|
447
482
|
|
|
448
|
-
|
|
449
|
-
|
|
483
|
+
def object_ranges
|
|
484
|
+
load! if @xrefs.nil?
|
|
450
485
|
|
|
451
|
-
|
|
452
|
-
|
|
486
|
+
if self.key?(:Index)
|
|
487
|
+
ranges = self.Index
|
|
488
|
+
unless ranges.is_a?(Array) and ranges.length.even? and ranges.all?{|i| i.is_a?(Integer)}
|
|
489
|
+
raise InvalidXRefStreamObjectError, "Index must be an even Array of integers"
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
ranges.each_slice(2).map { |start, length| Range.new(start.to_i, start.to_i + length.to_i - 1) }
|
|
493
|
+
else
|
|
494
|
+
[ 0...@xrefs.size ]
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
def load! #:nodoc:
|
|
499
|
+
if @xrefs.nil? and has_field?(:W)
|
|
500
|
+
widths = self.W
|
|
501
|
+
|
|
502
|
+
if not widths.is_a?(Array) or widths.length != 3 or widths.any?{|width| not width.is_a?(Integer) }
|
|
503
|
+
raise InvalidXRefStreamObjectError, "W field must be an array of 3 integers"
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
decode!
|
|
507
|
+
|
|
508
|
+
type_w = self.W[0]
|
|
509
|
+
field1_w = self.W[1]
|
|
510
|
+
field2_w = self.W[2]
|
|
511
|
+
|
|
512
|
+
entrymask = "B#{type_w << 3}B#{field1_w << 3}B#{field2_w << 3}"
|
|
513
|
+
size = @data.size / (type_w + field1_w + field2_w)
|
|
514
|
+
|
|
515
|
+
xentries = @data.unpack(entrymask * size).map!{|field| field.to_i(2) }
|
|
516
|
+
|
|
517
|
+
@xrefs = []
|
|
518
|
+
size.times do |i|
|
|
519
|
+
type,field1,field2 = xentries[i*3].ord,xentries[i*3+1].ord,xentries[i*3+2].ord
|
|
520
|
+
case type
|
|
521
|
+
when XREF_FREE
|
|
522
|
+
@xrefs << XRef.new(field1, field2, XRef::FREE)
|
|
523
|
+
when XREF_USED
|
|
524
|
+
@xrefs << XRef.new(field1, field2, XRef::USED)
|
|
525
|
+
when XREF_COMPRESSED
|
|
526
|
+
@xrefs << XRefToCompressedObj.new(field1, field2)
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
else
|
|
530
|
+
@xrefs = []
|
|
531
|
+
end
|
|
532
|
+
end
|
|
453
533
|
|
|
454
|
-
|
|
534
|
+
def save! #:nodoc:
|
|
535
|
+
self.data = ""
|
|
536
|
+
|
|
537
|
+
type_w, field1_w, field2_w = self.W
|
|
538
|
+
@xrefs.each do |xref| @data << xref.to_xrefstm_data(type_w, field1_w, field2_w) end
|
|
539
|
+
|
|
540
|
+
encode!
|
|
541
|
+
end
|
|
542
|
+
end
|
|
455
543
|
|
|
456
544
|
end
|