origami 1.2.7 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -0
  3. data/README.md +112 -0
  4. data/bin/config/pdfcop.conf.yml +232 -233
  5. data/bin/gui/about.rb +27 -37
  6. data/bin/gui/config.rb +108 -117
  7. data/bin/gui/file.rb +416 -365
  8. data/bin/gui/gtkhex.rb +1138 -1153
  9. data/bin/gui/hexview.rb +55 -57
  10. data/bin/gui/imgview.rb +48 -51
  11. data/bin/gui/menu.rb +388 -386
  12. data/bin/gui/properties.rb +114 -130
  13. data/bin/gui/signing.rb +571 -617
  14. data/bin/gui/textview.rb +77 -95
  15. data/bin/gui/treeview.rb +382 -387
  16. data/bin/gui/walker.rb +227 -232
  17. data/bin/gui/xrefs.rb +56 -60
  18. data/bin/pdf2pdfa +53 -57
  19. data/bin/pdf2ruby +212 -228
  20. data/bin/pdfcop +338 -348
  21. data/bin/pdfdecompress +58 -65
  22. data/bin/pdfdecrypt +56 -60
  23. data/bin/pdfencrypt +75 -80
  24. data/bin/pdfexplode +185 -182
  25. data/bin/pdfextract +201 -218
  26. data/bin/pdfmetadata +83 -82
  27. data/bin/pdfsh +4 -5
  28. data/bin/pdfwalker +1 -2
  29. data/bin/shell/.irbrc +45 -82
  30. data/bin/shell/console.rb +105 -130
  31. data/bin/shell/hexdump.rb +40 -64
  32. data/examples/README.md +34 -0
  33. data/examples/attachments/attachment.rb +38 -0
  34. data/examples/attachments/nested_document.rb +51 -0
  35. data/examples/encryption/encryption.rb +28 -0
  36. data/{samples/actions/triggerevents/trigger.rb → examples/events/events.rb} +13 -16
  37. data/examples/flash/flash.rb +37 -0
  38. data/{samples → examples}/flash/helloworld.swf +0 -0
  39. data/examples/forms/javascript.rb +54 -0
  40. data/examples/forms/xfa.rb +115 -0
  41. data/examples/javascript/hello_world.rb +22 -0
  42. data/examples/javascript/js_emulation.rb +54 -0
  43. data/examples/loop/goto.rb +32 -0
  44. data/examples/loop/named.rb +33 -0
  45. data/examples/signature/signature.rb +65 -0
  46. data/examples/uri/javascript.rb +56 -0
  47. data/examples/uri/open-uri.rb +21 -0
  48. data/examples/uri/submitform.rb +47 -0
  49. data/lib/origami.rb +29 -42
  50. data/lib/origami/3d.rb +350 -225
  51. data/lib/origami/acroform.rb +262 -288
  52. data/lib/origami/actions.rb +268 -288
  53. data/lib/origami/annotations.rb +697 -722
  54. data/lib/origami/array.rb +258 -184
  55. data/lib/origami/boolean.rb +74 -84
  56. data/lib/origami/catalog.rb +397 -434
  57. data/lib/origami/collections.rb +144 -0
  58. data/lib/origami/destinations.rb +233 -194
  59. data/lib/origami/dictionary.rb +253 -232
  60. data/lib/origami/encryption.rb +1274 -1243
  61. data/lib/origami/export.rb +232 -268
  62. data/lib/origami/extensions/fdf.rb +307 -220
  63. data/lib/origami/extensions/ppklite.rb +368 -435
  64. data/lib/origami/filespec.rb +197 -0
  65. data/lib/origami/filters.rb +301 -295
  66. data/lib/origami/filters/ascii.rb +177 -180
  67. data/lib/origami/filters/ccitt.rb +528 -535
  68. data/lib/origami/filters/crypt.rb +26 -35
  69. data/lib/origami/filters/dct.rb +46 -52
  70. data/lib/origami/filters/flate.rb +95 -94
  71. data/lib/origami/filters/jbig2.rb +49 -55
  72. data/lib/origami/filters/jpx.rb +38 -44
  73. data/lib/origami/filters/lzw.rb +189 -183
  74. data/lib/origami/filters/predictors.rb +221 -235
  75. data/lib/origami/filters/runlength.rb +103 -104
  76. data/lib/origami/font.rb +173 -186
  77. data/lib/origami/functions.rb +67 -81
  78. data/lib/origami/graphics.rb +25 -21
  79. data/lib/origami/graphics/colors.rb +178 -187
  80. data/lib/origami/graphics/instruction.rb +79 -85
  81. data/lib/origami/graphics/path.rb +142 -148
  82. data/lib/origami/graphics/patterns.rb +160 -167
  83. data/lib/origami/graphics/render.rb +43 -50
  84. data/lib/origami/graphics/state.rb +138 -153
  85. data/lib/origami/graphics/text.rb +188 -205
  86. data/lib/origami/graphics/xobject.rb +819 -815
  87. data/lib/origami/header.rb +63 -78
  88. data/lib/origami/javascript.rb +596 -597
  89. data/lib/origami/linearization.rb +285 -290
  90. data/lib/origami/metadata.rb +139 -148
  91. data/lib/origami/name.rb +112 -148
  92. data/lib/origami/null.rb +53 -62
  93. data/lib/origami/numeric.rb +162 -175
  94. data/lib/origami/obfuscation.rb +186 -174
  95. data/lib/origami/object.rb +593 -573
  96. data/lib/origami/outline.rb +42 -47
  97. data/lib/origami/outputintents.rb +73 -82
  98. data/lib/origami/page.rb +703 -592
  99. data/lib/origami/parser.rb +238 -290
  100. data/lib/origami/parsers/fdf.rb +41 -33
  101. data/lib/origami/parsers/pdf.rb +75 -95
  102. data/lib/origami/parsers/pdf/lazy.rb +137 -0
  103. data/lib/origami/parsers/pdf/linear.rb +64 -66
  104. data/lib/origami/parsers/ppklite.rb +34 -70
  105. data/lib/origami/pdf.rb +1030 -1005
  106. data/lib/origami/reference.rb +102 -102
  107. data/lib/origami/signature.rb +591 -609
  108. data/lib/origami/stream.rb +668 -551
  109. data/lib/origami/string.rb +397 -373
  110. data/lib/origami/template/patterns.rb +56 -0
  111. data/lib/origami/template/widgets.rb +151 -0
  112. data/lib/origami/trailer.rb +144 -158
  113. data/lib/origami/tree.rb +62 -0
  114. data/lib/origami/version.rb +23 -0
  115. data/lib/origami/webcapture.rb +88 -79
  116. data/lib/origami/xfa.rb +2863 -2882
  117. data/lib/origami/xreftable.rb +472 -384
  118. data/test/dataset/calc.pdf +85 -0
  119. data/test/dataset/crypto.pdf +82 -0
  120. data/test/dataset/empty.pdf +49 -0
  121. data/test/test_actions.rb +27 -0
  122. data/test/test_annotations.rb +90 -0
  123. data/test/test_pages.rb +31 -0
  124. data/test/test_pdf.rb +16 -0
  125. data/test/test_pdf_attachment.rb +34 -0
  126. data/test/test_pdf_create.rb +24 -0
  127. data/test/test_pdf_encrypt.rb +95 -0
  128. data/test/test_pdf_parse.rb +96 -0
  129. data/test/test_pdf_sign.rb +58 -0
  130. data/test/test_streams.rb +182 -0
  131. data/test/test_xrefs.rb +67 -0
  132. metadata +88 -58
  133. data/README +0 -67
  134. data/bin/pdf2graph +0 -121
  135. data/bin/pdfcocoon +0 -104
  136. data/lib/origami/file.rb +0 -233
  137. data/samples/README.txt +0 -45
  138. data/samples/actions/launch/calc.rb +0 -87
  139. data/samples/actions/launch/winparams.rb +0 -22
  140. data/samples/actions/loop/loopgoto.rb +0 -24
  141. data/samples/actions/loop/loopnamed.rb +0 -21
  142. data/samples/actions/named/named.rb +0 -31
  143. data/samples/actions/samba/smbrelay.rb +0 -26
  144. data/samples/actions/webbug/submitform.js +0 -26
  145. data/samples/actions/webbug/webbug-browser.rb +0 -68
  146. data/samples/actions/webbug/webbug-js.rb +0 -67
  147. data/samples/actions/webbug/webbug-reader.rb +0 -90
  148. data/samples/attachments/attach.rb +0 -40
  149. data/samples/attachments/attached.txt +0 -1
  150. data/samples/crypto/crypto.rb +0 -28
  151. data/samples/digsig/signed.rb +0 -46
  152. data/samples/exploits/cve-2008-2992-utilprintf.rb +0 -87
  153. data/samples/exploits/cve-2009-0927-geticon.rb +0 -65
  154. data/samples/exploits/exploit_customdictopen.rb +0 -55
  155. data/samples/exploits/getannots.rb +0 -69
  156. data/samples/flash/flash.rb +0 -31
  157. data/samples/javascript/attached.txt +0 -1
  158. data/samples/javascript/js.rb +0 -52
  159. data/templates/patterns.rb +0 -66
  160. data/templates/widgets.rb +0 -173
  161. data/templates/xdp.rb +0 -92
  162. data/test/ts_pdf.rb +0 -50
@@ -1,25 +1,20 @@
1
1
  =begin
2
2
 
3
- = File
4
- stream.rb
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é.
5
+
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.
10
+
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.
15
+
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/>.
23
18
 
24
19
  =end
25
20
 
@@ -27,592 +22,714 @@ require 'strscan'
27
22
 
28
23
  module Origami
29
24
 
30
- class InvalidStreamObjectError < InvalidObjectError #:nodoc:
31
- end
32
-
33
- #
34
- # Class representing a PDF Stream Object.
35
- # Streams can be used to hold any kind of data, especially binary data.
36
- #
37
- class Stream
38
-
39
- include Origami::Object
40
- include StandardObject
41
-
42
- TOKENS = [ "stream" + WHITECHARS_NORET + "\\r?\\n", "endstream" ] #:nodoc:
43
-
44
- @@regexp_open = Regexp.new(WHITESPACES + TOKENS.first)
45
- @@regexp_close = Regexp.new(TOKENS.last)
46
-
47
- @@cast_fingerprints = {}
25
+ class InvalidStreamObjectError < InvalidObjectError #:nodoc:
26
+ end
48
27
 
49
- #
50
- # Actually only 5 first ones are implemented, other ones are mainly about image data processing (JPEG, JPEG2000 ... )
51
- #
52
- @@defined_filters =
53
- [
54
- :ASCIIHexDecode,
55
- :ASCII85Decode,
56
- :LZWDecode,
57
- :FlateDecode,
58
- :RunLengthDecode,
59
- # TODO
60
- :CCITTFaxDecode,
61
- :JBIG2Decode,
62
- :DCTDecode,
63
- :JPXDecode,
64
- # abbrev
65
- :AHx, # ASCIIHexDecode
66
- :A85, # ASCII85Decode
67
- :LZW, # LZWDecode
68
- :Fl, # FlateDecode
69
- :RL, # RunLengthDecode
70
- :CCF, # CCITTFaxDecode
71
- :DCT, # DCTDecode
72
- ]
73
-
74
- attr_accessor :dictionary
75
-
76
- field :Length, :Type => Integer, :Required => true
77
- field :Filter, :Type => [ Name, Array ]
78
- field :DecodeParms, :Type => [ Dictionary, Array ]
79
- field :F, :Type => Dictionary, :Version => "1.2"
80
- field :FFilter, :Type => [ Name, Array ], :Version => "1.2"
81
- field :FDecodeParms, :Type => [ Dictionary, Array ], :Version => "1.2"
82
- field :DL, :Type => Integer, :Version => "1.5"
28
+ # Forward declaration.
29
+ class FileSpec < Dictionary; end
83
30
 
84
31
  #
85
- # Creates a new PDF Stream.
86
- # _data_:: The Stream uncompressed data.
87
- # _dictionary_:: A hash representing the Stream attributes.
32
+ # Class representing a PDF Stream Object.
33
+ # Streams can be used to hold any kind of data, especially binary data.
88
34
  #
89
- def initialize(data = "", dictionary = {})
90
- super()
91
-
92
- set_indirect(true)
93
-
94
- @dictionary, @data = Dictionary.new(dictionary), data
95
- @dictionary.parent = self
96
- end
97
-
98
- def pre_build
99
- encode!
100
-
101
- super
102
- end
35
+ class Stream
36
+ include Origami::Object
37
+ include StandardObject
38
+ using TypeConversion
103
39
 
104
- def post_build
105
- self.Length = @rawdata.length
106
-
107
- super
108
- end
40
+ TOKENS = [ "stream" + WHITECHARS_NORET + "\\r?\\n", "endstream" ] #:nodoc:
109
41
 
110
- def method_missing(field, *args) #:nodoc:
111
- if field.to_s[-1,1] == '='
112
- self[field.to_s[0..-2].to_sym] = args.first
113
- else
114
- obj = self[field];
115
- obj.is_a?(Reference) ? obj.solve : obj
116
- end
117
- end
118
-
119
- def self.parse(stream, parser = nil) #:nodoc:
120
-
121
- dictionary = Dictionary.parse(stream, parser)
122
- return dictionary if not stream.skip(@@regexp_open)
123
-
124
- length = dictionary[:Length]
125
- if not length.is_a?(Integer)
126
- rawdata = stream.scan_until(@@regexp_close)
127
- if rawdata.nil?
128
- raise InvalidStreamObjectError,
129
- "Stream shall end with a 'endstream' statement"
130
- end
131
- else
132
- length = length.value
133
- rawdata = stream.peek(length)
134
- stream.pos += length
135
-
136
- if not ( unmatched = stream.scan_until(@@regexp_close) )
137
- raise InvalidStreamObjectError,
138
- "Stream shall end with a 'endstream' statement"
139
- end
140
-
141
- rawdata << unmatched
142
- end
143
-
144
- stm =
145
- if Origami::OPTIONS[:enable_type_guessing]
146
- self.guess_type(dictionary).new('', dictionary.to_h)
147
- else
148
- Stream.new('', dictionary.to_h)
149
- end
150
-
151
- rawdata.chomp!(TOKENS.last)
152
-
153
- if rawdata[-1,1] == "\n"
154
- if rawdata[-2,1] == "\r"
155
- rawdata = rawdata[0, rawdata.size - 2]
156
- else
157
- rawdata = rawdata[0, rawdata.size - 1]
158
- end
159
- end
160
- #rawdata.chomp! if length.is_a?(Integer) and length < rawdata.length
161
-
162
- stm.rawdata = rawdata
163
- stm.file_offset = dictionary.file_offset
164
-
165
- stm
166
- end
167
-
168
- def self.add_type_info(typeclass, key, value) #:nodoc:
169
- if not @@cast_fingerprints.has_key?(typeclass) and typeclass.superclass != Stream and
170
- @@cast_fingerprints.has_key?(typeclass.superclass)
171
- @@cast_fingerprints[typeclass] = @@cast_fingerprints[typeclass.superclass].dup
172
- end
173
-
174
- @@cast_fingerprints[typeclass] ||= {}
175
- @@cast_fingerprints[typeclass][key.to_o] = value.to_o
176
- end
42
+ @@regexp_open = Regexp.new(WHITESPACES + TOKENS.first)
43
+ @@regexp_close = Regexp.new(TOKENS.last)
177
44
 
178
- def self.guess_type(hash) #:nodoc:
179
- best_type = Stream
45
+ @@cast_fingerprints = {}
180
46
 
181
- @@cast_fingerprints.each_pair do |typeclass, keys|
182
- best_type = typeclass if keys.all? { |k,v|
183
- hash.has_key?(k) and hash[k] == v
184
- } and typeclass < best_type
185
- end
47
+ #
48
+ # Actually only 5 first ones are implemented,
49
+ # other ones are mainly about image data processing (JPEG, JPEG2000 ...)
50
+ #
51
+ @@defined_filters = %i[
52
+ ASCIIHexDecode
53
+ ASCII85Decode
54
+ LZWDecode
55
+ FlateDecode
56
+ RunLengthDecode
186
57
 
187
- best_type
188
- end
58
+ CCITTFaxDecode
59
+ JBIG2Decode
60
+ DCTDecode
61
+ JPXDecode
189
62
 
190
- def set_predictor(predictor, colors = 1, bitspercomponent = 8, columns = 1)
191
-
192
- filters = self.Filter
193
- filters = [ filters ] unless filters.is_a?(::Array)
63
+ AHx
64
+ A85
65
+ LZW
66
+ Fl
67
+ RL
68
+ CCF
69
+ DCT
70
+ ]
194
71
 
195
- if not filters.include?(:FlateDecode) and not filters.include?(:LZWDecode)
196
- raise InvalidStreamObjectError, 'Predictor functions can only be used with Flate or LZW filters'
197
- end
72
+ attr_reader :dictionary
198
73
 
199
- layer = filters.index(:FlateDecode) or filters.index(:LZWDecode)
74
+ field :Length, :Type => Integer, :Required => true
75
+ field :Filter, :Type => [ Name, Array.of(Name) ]
76
+ field :DecodeParms, :Type => [ Dictionary, Array.of(Dictionary) ]
77
+ field :F, :Type => FileSpec, :Version => "1.2"
78
+ field :FFilter, :Type => [ Name, Array.of(Name) ], :Version => "1.2"
79
+ field :FDecodeParms, :Type => [ Dictionary, Array.of(Dictionary) ], :Version => "1.2"
80
+ field :DL, :Type => Integer, :Version => "1.5"
200
81
 
201
- params = Filter::LZW::DecodeParms.new
202
- params[:Predictor] = predictor
203
- params[:Colors] = colors if colors != 1
204
- params[:BitsPerComponent] = bitspercomponent if bitspercomponent != 8
205
- params[:Columns] = columns if columns != 1
82
+ #
83
+ # Creates a new PDF Stream.
84
+ # _data_:: The Stream uncompressed data.
85
+ # _dictionary_:: A hash representing the Stream attributes.
86
+ #
87
+ def initialize(data = "", dictionary = {})
88
+ super()
206
89
 
207
- set_decode_params(layer, params)
208
-
209
- self
210
- end
90
+ set_indirect(true)
211
91
 
212
- def cast_to(type)
213
- super(type)
92
+ @encoded_data = nil
93
+ @dictionary, @data = Dictionary.new(dictionary), data
94
+ @dictionary.parent = self
95
+ end
214
96
 
215
- cast = type.new("", self.dictionary.to_h)
216
- cast.rawdata = @rawdata.dup
217
- cast.no, cast.generation = self.no, self.generation
218
- cast.set_indirect(true)
219
- cast.set_pdf(self.pdf)
220
- cast.file_offset = self.file_offset
97
+ def dictionary=(dict)
98
+ @dictionary = dict
99
+ @dictionary.parent = self
221
100
 
222
- cast
223
- end
101
+ @dictionary
102
+ end
224
103
 
225
- def value #:nodoc:
226
- self
227
- end
228
-
229
- #
230
- # Returns the uncompressed stream content.
231
- #
232
- def data
233
- self.decode! if @data.nil?
104
+ def pre_build
105
+ encode!
234
106
 
235
- @data
236
- end
237
-
238
- #
239
- # Sets the uncompressed stream content.
240
- # _str_:: The new uncompressed data.
241
- #
242
- def data=(str)
243
- @rawdata = nil
244
- @data = str
245
- end
246
-
247
- #
248
- # Returns the raw compressed stream content.
249
- #
250
- def rawdata
251
- self.encode! if @rawdata.nil?
107
+ super
108
+ end
252
109
 
253
- @rawdata
254
- end
255
-
256
- #
257
- # Sets the raw compressed stream content.
258
- # _str_:: the new raw data.
259
- #
260
- def rawdata=(str)
261
- @rawdata = str
262
- @data = nil
263
- end
264
-
265
- #
266
- # Uncompress the stream data.
267
- #
268
- def decode!
269
- self.decrypt! if self.is_a?(Encryption::EncryptedStream)
270
-
271
- unless is_decoded?
272
- filters = self.Filter
273
-
274
- if filters.nil?
275
- @data = @rawdata.dup
276
- else
277
- case filters
278
- when Array, Name then
279
- dparams = self.DecodeParms || []
280
-
281
- dparams = [ dparams ] unless dparams.is_a?(::Array)
282
- filters = [ filters ] unless filters.is_a?(::Array)
283
-
284
- @data = @rawdata.dup
285
- @data.freeze
286
-
287
- filters.length.times do |layer|
110
+ def post_build
111
+ self.Length = @encoded_data.length
112
+
113
+ super
114
+ end
115
+
116
+ def self.parse(stream, parser = nil) #:nodoc:
117
+ dictionary = Dictionary.parse(stream, parser)
118
+ return dictionary if not stream.skip(@@regexp_open)
119
+
120
+ length = dictionary[:Length]
121
+ if not length.is_a?(Integer)
122
+ raw_data = stream.scan_until(@@regexp_close)
123
+ if raw_data.nil?
124
+ raise InvalidStreamObjectError,
125
+ "Stream shall end with a 'endstream' statement"
126
+ end
127
+ else
128
+ length = length.value
129
+ raw_data = stream.peek(length)
130
+ stream.pos += length
131
+
132
+ if not ( unmatched = stream.scan_until(@@regexp_close) )
133
+ raise InvalidStreamObjectError,
134
+ "Stream shall end with a 'endstream' statement"
135
+ end
136
+
137
+ raw_data << unmatched
138
+ end
139
+
140
+ stm =
141
+ if Origami::OPTIONS[:enable_type_guessing]
142
+ self.guess_type(dictionary).new('', dictionary.to_h)
143
+ else
144
+ Stream.new('', dictionary.to_h)
145
+ end
146
+
147
+ raw_data.chomp!(TOKENS.last)
148
+
149
+ if raw_data[-1,1] == "\n"
150
+ if raw_data[-2,1] == "\r"
151
+ raw_data = raw_data[0, raw_data.size - 2]
152
+ else
153
+ raw_data = raw_data[0, raw_data.size - 1]
154
+ end
155
+ end
156
+ #raw_data.chomp! if length.is_a?(Integer) and length < raw_data.length
157
+
158
+ stm.encoded_data = raw_data
159
+ stm.file_offset = dictionary.file_offset
160
+
161
+ stm
162
+ end
163
+
164
+ def self.add_type_info(typeclass, key, value) #:nodoc:
165
+ if not @@cast_fingerprints.has_key?(typeclass) and typeclass.superclass != Stream and
166
+ @@cast_fingerprints.has_key?(typeclass.superclass)
167
+ @@cast_fingerprints[typeclass] = @@cast_fingerprints[typeclass.superclass].dup
168
+ end
169
+
170
+ @@cast_fingerprints[typeclass] ||= {}
171
+ @@cast_fingerprints[typeclass][key.to_o] = value.to_o
172
+ end
173
+
174
+ def self.guess_type(hash) #:nodoc:
175
+ best_type = self
176
+
177
+ @@cast_fingerprints.each_pair do |klass, keys|
178
+ next unless klass < best_type
179
+
180
+ best_type = klass if keys.all? { |k,v| hash[k] == v }
181
+ end
182
+
183
+ best_type
184
+ end
185
+
186
+ #
187
+ # Iterates over each Filter in the Stream.
188
+ #
189
+ def each_filter
190
+ filters = self.Filter
191
+
192
+ return enum_for(__method__) do
193
+ case filters
194
+ when NilClass then 0
195
+ when Array then filters.length
196
+ else
197
+ 1
198
+ end
199
+ end unless block_given?
200
+
201
+ return if filters.nil?
202
+
203
+ if filters.is_a?(Array)
204
+ filters.each do |filter| yield(filter) end
205
+ else
206
+ yield(filters)
207
+ end
208
+
209
+ self
210
+ end
211
+
212
+ #
213
+ # Returns an Array of Filters for this Stream.
214
+ #
215
+ def filters
216
+ self.each_filter.to_a
217
+ end
218
+
219
+ #
220
+ # Set predictor type for the current Stream.
221
+ # Applies only for LZW and FlateDecode filters.
222
+ #
223
+ def set_predictor(predictor, colors: 1, bitspercomponent: 8, columns: 1)
224
+ filters = self.filters
225
+
226
+ unless filters.include?(:FlateDecode) or filters.include?(:LZWDecode)
227
+ raise InvalidStreamObjectError, 'Predictor functions can only be used with Flate or LZW filters'
228
+ end
229
+
230
+ layer = filters.index(:FlateDecode) or filters.index(:LZWDecode)
231
+
232
+ params = Filter::LZW::DecodeParms.new
233
+ params[:Predictor] = predictor
234
+ params[:Colors] = colors if colors != 1
235
+ params[:BitsPerComponent] = bitspercomponent if bitspercomponent != 8
236
+ params[:Columns] = columns if columns != 1
237
+
238
+ set_decode_params(layer, params)
239
+
240
+ self
241
+ end
242
+
243
+ def cast_to(type, _parser = nil)
244
+ super(type)
245
+
246
+ cast = type.new("", self.dictionary.to_h)
247
+ cast.encoded_data = self.encoded_data.dup
248
+ cast.no, cast.generation = self.no, self.generation
249
+ cast.set_indirect(true)
250
+ cast.set_document(self.document)
251
+ cast.file_offset = self.file_offset
252
+
253
+ cast
254
+ end
255
+
256
+ def value #:nodoc:
257
+ self
258
+ end
259
+
260
+ #
261
+ # Returns the uncompressed stream content.
262
+ #
263
+ def data
264
+ self.decode! unless self.decoded?
265
+
266
+ @data
267
+ end
268
+ alias decoded_data data
269
+
270
+ #
271
+ # Sets the uncompressed stream content.
272
+ # _str_:: The new uncompressed data.
273
+ #
274
+ def data=(str)
275
+ @encoded_data = nil
276
+ @data = str
277
+ end
278
+ alias decoded_data= data=
279
+
280
+ #
281
+ # Returns the raw compressed stream content.
282
+ #
283
+ def encoded_data
284
+ self.encode! unless self.encoded?
285
+
286
+ @encoded_data
287
+ end
288
+
289
+ #
290
+ # Sets the raw compressed stream content.
291
+ # _str_:: the new raw data.
292
+ #
293
+ def encoded_data=(str)
294
+ @encoded_data = str
295
+ @data = nil
296
+ end
297
+
298
+ #
299
+ # Uncompress the stream data.
300
+ #
301
+ def decode!
302
+ self.decrypt! if self.is_a?(Encryption::EncryptedStream)
303
+ return if decoded?
304
+
305
+ filters = self.filters
306
+
307
+ if filters.empty?
308
+ @data = @encoded_data.dup
309
+ return
310
+ end
311
+
312
+ unless filters.all?{|filter| filter.is_a?(Name)}
313
+ raise InvalidStreamObjectError, "Invalid Filter type parameter"
314
+ end
315
+
316
+ dparams = decode_params
317
+
318
+ @data = @encoded_data.dup
319
+ @data.freeze
320
+
321
+ filters.each_with_index do |filter, layer|
288
322
  params = dparams[layer].is_a?(Dictionary) ? dparams[layer] : {}
289
- filter = filters[layer]
323
+
324
+ # Handle Crypt filters.
325
+ if filter == :Crypt
326
+ raise Filter::Error, "Crypt filter must be the first filter" unless layer.zero?
327
+
328
+ # Skip the Crypt filter.
329
+ next
330
+ end
290
331
 
291
332
  begin
292
- @data = decode_data(@data, filter, params)
293
- rescue Filter::InvalidFilterDataError => e
294
- @data = e.decoded_data if e.decoded_data
295
- raise InvalidStreamObjectError,
296
- "Error while decoding stream #{self.reference}\n\t-> [#{e.class}] #{e.message}"
333
+ @data = decode_data(@data, filter, params)
334
+ rescue Filter::Error => error
335
+ @data = error.decoded_data if error.decoded_data
336
+ raise
297
337
  end
298
- end
299
- else
300
- raise InvalidStreamObjectError, "Invalid Filter type parameter"
301
- end
338
+ end
339
+
340
+ self
302
341
  end
303
- end
304
-
305
- self
306
- end
307
-
308
- #
309
- # Compress the stream data.
310
- #
311
- def encode!
312
-
313
- unless is_encoded?
314
- filters = self.Filter
315
-
316
- if filters.nil?
317
- @rawdata = @data.dup
318
- else
319
- case filters
320
- when Array, Name then
321
- dparams = self.DecodeParms || []
322
-
323
- dparams = [ dparams ] unless dparams.is_a?(::Array)
324
- filters = [ filters ] unless filters.is_a?(::Array)
325
-
326
- @rawdata = @data.dup
327
- (filters.length - 1).downto(0) do |layer|
342
+
343
+ #
344
+ # Compress the stream data.
345
+ #
346
+ def encode!
347
+ return if encoded?
348
+ filters = self.filters
349
+
350
+ if filters.empty?
351
+ @encoded_data = @data.dup
352
+ return
353
+ end
354
+
355
+ unless filters.all?{|filter| filter.is_a?(Name)}
356
+ raise InvalidStreamObjectError, "Invalid Filter type parameter"
357
+ end
358
+
359
+ dparams = decode_params
360
+
361
+ @encoded_data = @data.dup
362
+ (filters.length - 1).downto(0) do |layer|
328
363
  params = dparams[layer].is_a?(Dictionary) ? dparams[layer] : {}
329
364
  filter = filters[layer]
330
365
 
331
- @rawdata = encode_data(@rawdata, filter, params)
332
- end
333
- else
334
- raise InvalidStreamObjectError, "Invalid filter type parameter"
335
- end
336
- end
337
-
338
- self.Length = @rawdata.length
339
- end
340
-
341
- self
342
- end
343
-
344
- def to_s(indent = 1) #:nodoc:
345
-
346
- content = ""
347
-
348
- content << @dictionary.to_s(indent)
349
- content << "stream" + EOL
350
- content << self.rawdata
351
- content << EOL << TOKENS.last
352
-
353
- super(content)
354
- end
355
-
356
- def [](key) #:nodoc:
357
- @dictionary[key]
358
- end
359
-
360
- def []=(key,val) #:nodoc:
361
- @dictionary[key] = val
362
- end
363
-
364
- def each_key(&b) #:nodoc:
365
- @dictionary.each_key(&b)
366
- end
367
-
368
- def self.native_type ; Stream end
366
+ # Handle Crypt filters.
367
+ if filter == :Crypt
368
+ raise Filter::Error, "Crypt filter must be the first filter" unless layer.zero?
369
369
 
370
- private
370
+ # Skip the Crypt filter.
371
+ next
372
+ end
371
373
 
372
- def is_decoded? #:nodoc:
373
- not @data.nil?
374
- end
374
+ @encoded_data = encode_data(@encoded_data, filter, params)
375
+ end
375
376
 
376
- def is_encoded? #:nodoc:
377
- not @rawdata.nil?
378
- end
377
+ self.Length = @encoded_data.length
379
378
 
380
- def set_decode_params(layer, params) #:nodoc:
381
- dparms = self.DecodeParms
382
- unless dparms.is_a? ::Array
383
- @dictionary[:DecodeParms] = dparms = []
384
- end
379
+ self
380
+ end
385
381
 
386
- if layer > dparms.length - 1
387
- dparms.concat(::Array.new(layer - dparms.length + 1, Null.new))
388
- end
382
+ def to_s(indent: 1, tab: "\t") #:nodoc:
383
+ content = ""
389
384
 
390
- dparms[layer] = params
391
- @dictionary[:DecodeParms] = dparms.first if dparms.length == 1
385
+ content << @dictionary.to_s(indent: indent, tab: tab)
386
+ content << "stream" + EOL
387
+ content << self.encoded_data
388
+ content << EOL << TOKENS.last
392
389
 
393
- self
394
- end
395
-
396
- def decode_data(data, filter, params) #:nodoc:
397
- unless @@defined_filters.include?(filter.value)
398
- raise InvalidStreamObjectError, "Unknown filter : #{filter}"
399
- end
390
+ super(content)
391
+ end
400
392
 
401
- Origami::Filter.const_get(filter.value.to_s.sub(/Decode$/,"")).decode(data, params)
402
- end
403
-
404
- def encode_data(data, filter, params) #:nodoc:
405
- unless @@defined_filters.include?(filter.value)
406
- raise InvalidStreamObjectError, "Unknown filter : #{filter}"
407
- end
408
-
409
- encoded = Origami::Filter.const_get(filter.value.to_s.sub(/Decode$/,"")).encode(data, params)
410
-
411
- if filter.value == :ASCIIHexDecode or filter.value == :ASCII85Decode
412
- encoded << Origami::Filter.const_get(filter.value.to_s.sub(/Decode$/,""))::EOD
413
- end
414
-
415
- encoded
416
- end
417
-
418
- end
393
+ def [](key) #:nodoc:
394
+ @dictionary[key]
395
+ end
419
396
 
420
- #
421
- # Class representing an external Stream.
422
- #
423
- class ExternalStream < Stream
397
+ def []=(key,val) #:nodoc:
398
+ @dictionary[key] = val
399
+ end
424
400
 
425
- def initialize(filespec, hash = {})
401
+ def each_key(&b) #:nodoc:
402
+ @dictionary.each_key(&b)
403
+ end
426
404
 
427
- hash[:F] = filespec
428
- super('', hash)
429
- end
405
+ def key?(name)
406
+ @dictionary.key?(name)
407
+ end
408
+ alias has_key? key?
430
409
 
431
- end
432
-
433
- class InvalidObjectStreamObjectError < InvalidStreamObjectError #:nodoc:
434
- end
435
-
436
- #
437
- # Class representing a Stream containing other Objects.
438
- #
439
- class ObjectStream < Stream
440
-
441
- include Enumerable
442
-
443
- NUM = 0 #:nodoc:
444
- OBJ = 1 #:nodoc:
445
-
446
- field :Type, :Type => Name, :Default => :ObjStm, :Required => true, :Version => "1.5"
447
- field :N, :Type => Integer, :Required => true
448
- field :First, :Type => Integer, :Required => true
449
- field :Extends, :Type => Stream
410
+ def self.native_type ; Stream end
450
411
 
451
- #
452
- # Creates a new Object Stream.
453
- # _dictionary_:: A hash of attributes to set to the Stream.
454
- # _rawdata_:: The Stream data.
455
- #
456
- def initialize(rawdata = "", dictionary = {})
457
- @objects = nil
458
-
459
- super(rawdata, dictionary)
460
- end
461
-
462
- def pre_build #:nodoc:
463
- load! if @objects.nil?
464
-
465
- prolog = ""
466
- data = ""
467
- objoff = 0
468
- @objects.to_a.sort.each do |num,obj|
469
-
470
- obj.set_indirect(false)
471
- obj.objstm_offset = objoff
472
-
473
- prolog << "#{num} #{objoff} "
474
- objdata = "#{obj.to_s} "
475
-
476
- objoff += objdata.size
477
- data << objdata
478
- obj.set_indirect(true)
479
- obj.no = num
480
- end
481
-
482
- self.data = prolog + data
483
-
484
- @dictionary[:N] = @objects.size
485
- @dictionary[:First] = prolog.size
486
-
487
- super
488
- end
489
-
490
- #
491
- # Adds a new Object to this Stream.
492
- # _object_:: The Object to append.
493
- #
494
- def <<(object)
495
- unless object.generation == 0
496
- raise InvalidObjectError, "Cannot store an object with generation > 0 in an ObjectStream"
497
- end
498
-
499
- if object.is_a?(Stream)
500
- raise InvalidObjectError, "Cannot store a Stream in an ObjectStream"
501
- end
502
-
503
- load! if @objects.nil?
504
-
505
- object.no, object.generation = @pdf.alloc_new_object_number if object.no == 0
506
-
507
- object.set_indirect(true) # object is indirect
508
- object.parent = self # set this stream as the parent
509
- object.set_pdf(@pdf) # indirect objects need pdf information
510
- @objects[object.no] = object
511
-
512
- Reference.new(object.no, 0)
513
- end
514
- alias :insert :<<
412
+ private
515
413
 
516
- #
517
- # Deletes Object _no_.
518
- #
519
- def delete(no)
520
- load! if @objects.nil?
414
+ def method_missing(field, *args) #:nodoc:
415
+ if field.to_s[-1,1] == '='
416
+ self[field.to_s[0..-2].to_sym] = args.first
417
+ else
418
+ obj = self[field];
419
+ obj.is_a?(Reference) ? obj.solve : obj
420
+ end
421
+ end
521
422
 
522
- @objects.delete(no)
523
- end
423
+ def decoded? #:nodoc:
424
+ not @data.nil?
425
+ end
524
426
 
525
- #
526
- # Returns the index of Object _no_.
527
- #
528
- def index(no)
529
- ind = 0
530
- @objects.to_a.sort.each { |num, obj|
531
- return ind if num == no
427
+ def encoded? #:nodoc:
428
+ not @encoded_data.nil?
429
+ end
532
430
 
533
- ind = ind + 1
534
- }
431
+ def each_decode_params
432
+ params = self.DecodeParms
535
433
 
536
- nil
537
- end
434
+ return enum_for(__method__) do
435
+ case params
436
+ when NilClass then 0
437
+ when Array then params.length
438
+ else
439
+ 1
440
+ end
441
+ end unless block_given?
538
442
 
539
- #
540
- # Returns a given decompressed object contained in the Stream.
541
- # _no_:: The Object number.
542
- #
543
- def extract(no)
544
- load! if @objects.nil?
545
-
546
- @objects[no]
547
- end
443
+ return if params.nil?
548
444
 
549
- #
550
- # Returns a given decompressed object by index.
551
- # _index_:: The Object index in the ObjectStream.
552
- #
553
- def extract_by_index(index)
554
- load! if @objects.nil?
445
+ if params.is_a?(Array)
446
+ params.each do |param| yield(param) end
447
+ else
448
+ yield(params)
449
+ end
450
+
451
+ self
452
+ end
453
+
454
+ def decode_params
455
+ each_decode_params.to_a
456
+ end
457
+
458
+ def set_decode_params(layer, params) #:nodoc:
459
+ dparms = self.DecodeParms
460
+ unless dparms.is_a? ::Array
461
+ @dictionary[:DecodeParms] = dparms = []
462
+ end
463
+
464
+ if layer > dparms.length - 1
465
+ dparms.concat(::Array.new(layer - dparms.length + 1, Null.new))
466
+ end
467
+
468
+ dparms[layer] = params
469
+ @dictionary[:DecodeParms] = dparms.first if dparms.length == 1
470
+
471
+ self
472
+ end
473
+
474
+ def decode_data(data, filter, params) #:nodoc:
475
+ unless @@defined_filters.include?(filter.value)
476
+ raise InvalidStreamObjectError, "Unknown filter : #{filter}"
477
+ end
478
+
479
+ Origami::Filter.const_get(filter.value.to_s.sub(/Decode$/,"")).decode(data, params)
480
+ end
555
481
 
556
- @objects.to_a.sort[index]
482
+ def encode_data(data, filter, params) #:nodoc:
483
+ unless @@defined_filters.include?(filter.value)
484
+ raise InvalidStreamObjectError, "Unknown filter : #{filter}"
485
+ end
486
+
487
+ encoded = Origami::Filter.const_get(filter.value.to_s.sub(/Decode$/,"")).encode(data, params)
488
+
489
+ if filter.value == :ASCIIHexDecode or filter.value == :ASCII85Decode
490
+ encoded << Origami::Filter.const_get(filter.value.to_s.sub(/Decode$/,""))::EOD
491
+ end
492
+
493
+ encoded
494
+ end
557
495
  end
558
-
496
+
559
497
  #
560
- # Returns whether a specific object is contained in this stream.
561
- # _no_:: The Object number.
498
+ # Class representing an external Stream.
562
499
  #
563
- def include?(no)
564
- load! if @objects.nil?
565
-
566
- @objects.include?(no)
500
+ class ExternalStream < Stream
501
+
502
+ def initialize(filespec, hash = {})
503
+ hash[:F] = filespec
504
+ super('', hash)
505
+ end
567
506
  end
568
-
569
- #
570
- # Iterates over each object in the stream.
571
- #
572
- def each(&b)
573
- load! if @objects.nil?
574
-
575
- @objects.values.each(&b)
507
+
508
+ class InvalidObjectStreamObjectError < InvalidStreamObjectError #:nodoc:
576
509
  end
577
-
510
+
578
511
  #
579
- # Returns the array of inner objects.
512
+ # Class representing a Stream containing other Objects.
580
513
  #
581
- def objects
582
- load! if @objects.nil?
583
-
584
- @objects.values
585
- end
586
-
587
- private
588
-
589
- def load! #:nodoc:
590
- decode!
591
-
592
- data = StringScanner.new(@data)
593
- nums = []
594
- offsets = []
595
-
596
- @dictionary[:N].to_i.times do
597
- nums << Integer.parse(data).to_i
598
- offsets << Integer.parse(data)
599
- end
600
-
601
- @objects = {}
602
- nums.size.times do |i|
603
- type = Object.typeof(data)
604
- raise InvalidObjectStreamObjectError,
605
- "Bad embedded object format in object stream" if type.nil?
606
-
607
- embeddedobj = type.parse(data)
608
- embeddedobj.set_indirect(true) # object is indirect
609
- embeddedobj.no = nums[i] # object number
610
- embeddedobj.parent = self # set this stream as the parent
611
- embeddedobj.set_pdf(@pdf) # indirect objects need pdf information
612
- embeddedobj.objstm_offset = offsets[i]
613
- @objects[nums[i]] = embeddedobj
614
- end
615
-
514
+ class ObjectStream < Stream
515
+ include Enumerable
516
+
517
+ NUM = 0 #:nodoc:
518
+ OBJ = 1 #:nodoc:
519
+
520
+ field :Type, :Type => Name, :Default => :ObjStm, :Required => true, :Version => "1.5"
521
+ field :N, :Type => Integer, :Required => true
522
+ field :First, :Type => Integer, :Required => true
523
+ field :Extends, :Type => ObjectStream
524
+
525
+ #
526
+ # Creates a new Object Stream.
527
+ # _dictionary_:: A hash of attributes to set to the Stream.
528
+ # _raw_data_:: The Stream data.
529
+ #
530
+ def initialize(raw_data = "", dictionary = {})
531
+ super
532
+
533
+ @objects = nil
534
+ end
535
+
536
+ def pre_build #:nodoc:
537
+ load! if @objects.nil?
538
+
539
+ prolog = ""
540
+ data = ""
541
+ objoff = 0
542
+ @objects.to_a.sort.each do |num,obj|
543
+
544
+ obj.set_indirect(false)
545
+ obj.objstm_offset = objoff
546
+
547
+ prolog << "#{num} #{objoff} "
548
+ objdata = "#{obj.to_s} "
549
+
550
+ objoff += objdata.size
551
+ data << objdata
552
+ obj.set_indirect(true)
553
+ obj.no = num
554
+ end
555
+
556
+ self.data = prolog + data
557
+
558
+ @dictionary[:N] = @objects.size
559
+ @dictionary[:First] = prolog.size
560
+
561
+ super
562
+ end
563
+
564
+ #
565
+ # Adds a new Object to this Stream.
566
+ # _object_:: The Object to append.
567
+ #
568
+ def <<(object)
569
+ unless object.generation == 0
570
+ raise InvalidObjectError, "Cannot store an object with generation > 0 in an ObjectStream"
571
+ end
572
+
573
+ if object.is_a?(Stream)
574
+ raise InvalidObjectError, "Cannot store a Stream in an ObjectStream"
575
+ end
576
+
577
+ # We must have an associated document to generate new object numbers.
578
+ if @document.nil?
579
+ raise InvalidObjectError, "The ObjectStream must be added to a document before inserting objects"
580
+ end
581
+
582
+ # The object already belongs to a document.
583
+ unless (obj_doc = object.document).nil?
584
+ # Remove the previous instance if the object is indirect to avoid duplicates.
585
+ if obj_doc.equal?(@document)
586
+ @document.delete_object(object.reference) if object.indirect?
587
+ else
588
+ object = object.export
589
+ end
590
+ end
591
+
592
+ load! if @objects.nil?
593
+
594
+ object.no, object.generation = @document.allocate_new_object_number if object.no == 0
595
+
596
+ object.set_indirect(true) # object is indirect
597
+ object.parent = self # set this stream as the parent
598
+ object.set_document(@document) # indirect objects need pdf information
599
+ @objects[object.no] = object
600
+
601
+ Reference.new(object.no, 0)
602
+ end
603
+ alias insert <<
604
+
605
+ #
606
+ # Deletes Object _no_.
607
+ #
608
+ def delete(no)
609
+ load! if @objects.nil?
610
+
611
+ @objects.delete(no)
612
+ end
613
+
614
+ #
615
+ # Returns the index of Object _no_.
616
+ #
617
+ def index(no)
618
+ ind = 0
619
+ @objects.to_a.sort.each do |num, obj|
620
+ return ind if num == no
621
+
622
+ ind = ind + 1
623
+ end
624
+
625
+ nil
626
+ end
627
+
628
+ #
629
+ # Returns a given decompressed object contained in the Stream.
630
+ # _no_:: The Object number.
631
+ #
632
+ def extract(no)
633
+ load! if @objects.nil?
634
+
635
+ @objects[no]
636
+ end
637
+
638
+ #
639
+ # Returns a given decompressed object by index.
640
+ # _index_:: The Object index in the ObjectStream.
641
+ #
642
+ def extract_by_index(index)
643
+ load! if @objects.nil?
644
+
645
+ raise TypeError, "index must be an integer" unless index.is_a?(::Integer)
646
+ raise IndexError, "index #{index} out of range" if index < 0 or index >= @objects.size
647
+
648
+ @objects.to_a.sort[index][1]
649
+ end
650
+
651
+ #
652
+ # Returns whether a specific object is contained in this stream.
653
+ # _no_:: The Object number.
654
+ #
655
+ def include?(no)
656
+ load! if @objects.nil?
657
+
658
+ @objects.include?(no)
659
+ end
660
+
661
+ #
662
+ # Iterates over each object in the stream.
663
+ #
664
+ def each(&b)
665
+ load! if @objects.nil?
666
+
667
+ @objects.values.each(&b)
668
+ end
669
+ alias each_object each
670
+
671
+ #
672
+ # Returns the number of objects contained in the stream.
673
+ #
674
+ def length
675
+ raise InvalidObjectStreamObjectError, "Invalid number of objects" unless self.N.is_a?(Integer)
676
+
677
+ self.N.to_i
678
+ end
679
+
680
+ #
681
+ # Returns the array of inner objects.
682
+ #
683
+ def objects
684
+ load! if @objects.nil?
685
+
686
+ @objects.values
687
+ end
688
+
689
+ private
690
+
691
+ def load! #:nodoc:
692
+ decode!
693
+
694
+ @objects = {}
695
+ return if @data.empty?
696
+
697
+ data = StringScanner.new(@data)
698
+ nums = []
699
+ offsets = []
700
+ first_offset = first_object_offset
701
+
702
+ self.length.times do
703
+ nums << Integer.parse(data).to_i
704
+ offsets << Integer.parse(data).to_i
705
+ end
706
+
707
+ self.length.times do |i|
708
+ unless (0...@data.size).cover? (first_object_offset + offsets[i]) and offsets[i] >= 0
709
+ raise InvalidObjectStreamObjectError, "Invalid offset '#{offsets[i]} for object #{nums[i]}"
710
+ end
711
+
712
+ data.pos = first_offset + offsets[i]
713
+ type = Object.typeof(data)
714
+ raise InvalidObjectStreamObjectError,
715
+ "Bad embedded object format in object stream" if type.nil?
716
+
717
+ embeddedobj = type.parse(data)
718
+ embeddedobj.set_indirect(true) # object is indirect
719
+ embeddedobj.no = nums[i] # object number
720
+ embeddedobj.parent = self # set this stream as the parent
721
+ embeddedobj.set_document(@document) # indirect objects need pdf information
722
+ embeddedobj.objstm_offset = offsets[i]
723
+ @objects[nums[i]] = embeddedobj
724
+ end
725
+ end
726
+
727
+ def first_object_offset #:nodoc:
728
+ raise InvalidObjectStreamObjectError, "Invalid First offset" unless self.First.is_a?(Integer)
729
+ raise InvalidObjectStreamObjectError, "Negative object offset" if self.First < 0
730
+
731
+ return self.First.to_i
732
+ end
616
733
  end
617
- end
734
+
618
735
  end