Class: Origami::Stream

Inherits:
Object
  • Object
show all
Includes:
Object, StandardObject
Defined in:
lib/origami/stream.rb,
lib/origami/obfuscation.rb

Overview

Class representing a PDF Stream Object. Streams can be used to hold any kind of data, especially binary data.

Constant Summary collapse

TOKENS =

:nodoc:

[ "stream" + WHITECHARS_NORET  + "\\r?\\n", "endstream" ]
@@regexp_open =
Regexp.new(WHITESPACES + TOKENS.first)
@@regexp_close =
Regexp.new(TOKENS.last)
@@cast_fingerprints =
{}
@@defined_filters =

Actually only 5 first ones are implemented, other ones are mainly about image data processing (JPEG, JPEG2000 … )

[ 
  :ASCIIHexDecode, 
  :ASCII85Decode, 
  :LZWDecode, 
  :FlateDecode, 
  :RunLengthDecode,
  # TODO
  :CCITTFaxDecode,
  :JBIG2Decode,
  :DCTDecode,
  :JPXDecode,
  # abbrev
  :AHx, # ASCIIHexDecode
  :A85, # ASCII85Decode
  :LZW, # LZWDecode
  :Fl,  # FlateDecode
  :RL,  # RunLengthDecode
  :CCF, # CCITTFaxDecode
  :DCT, # DCTDecode
]

Constants included from StandardObject

Origami::StandardObject::DEFAULT_ATTRIBUTES

Instance Attribute Summary collapse

Attributes included from Object

#file_offset, #generation, #no, #objstm_offset, #parent

Class Method Summary collapse

Instance Method Summary collapse

Methods included from StandardObject

#do_type_check, #has_field?, included, #pdf_version_required, #set_default_value, #set_default_values

Methods included from Object

#<=>, #copy, #export, #indirect_parent, #is_indirect?, #logicalize, #logicalize!, #native_type, #pdf, #pdf_version_required, #reference, #resolve_all_references, #set_indirect, #set_pdf, #size, skip_until_next_obj, #solve, #to_o, #type, typeof, #xrefs

Constructor Details

#initialize(data = "", dictionary = {}) ⇒ Stream

Creates a new PDF Stream.

data

The Stream uncompressed data.

dictionary

A hash representing the Stream attributes.



89
90
91
92
93
94
95
96
# File 'lib/origami/stream.rb', line 89

def initialize(data = "", dictionary = {})
    super()
    
    set_indirect(true)
   
    @dictionary, @data = Dictionary.new(dictionary), data
    @dictionary.parent = self
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(field, *args) ⇒ Object

:nodoc:



110
111
112
113
114
115
116
117
# File 'lib/origami/stream.rb', line 110

def method_missing(field, *args) #:nodoc:
  if field.to_s[-1,1] == '='
    self[field.to_s[0..-2].to_sym] = args.first
  else
    obj = self[field]; 
    obj.is_a?(Reference) ? obj.solve : obj
  end
end

Instance Attribute Details

#dictionaryObject

Returns the value of attribute dictionary.



74
75
76
# File 'lib/origami/stream.rb', line 74

def dictionary
  @dictionary
end

Class Method Details

.add_type_info(typeclass, key, value) ⇒ Object

:nodoc:



168
169
170
171
172
173
174
175
176
# File 'lib/origami/stream.rb', line 168

def self.add_type_info(typeclass, key, value) #:nodoc:
  if not @@cast_fingerprints.has_key?(typeclass) and typeclass.superclass != Stream and
     @@cast_fingerprints.has_key?(typeclass.superclass)
    @@cast_fingerprints[typeclass] = @@cast_fingerprints[typeclass.superclass].dup
  end

  @@cast_fingerprints[typeclass] ||= {}
  @@cast_fingerprints[typeclass][key.to_o] = value.to_o
end

.guess_type(hash) ⇒ Object

:nodoc:



178
179
180
181
182
183
184
185
186
187
188
# File 'lib/origami/stream.rb', line 178

def self.guess_type(hash) #:nodoc:
  best_type = Stream

  @@cast_fingerprints.each_pair do |typeclass, keys|
    best_type = typeclass if keys.all? { |k,v| 
      hash.has_key?(k) and hash[k] == v 
    } and typeclass < best_type
  end

  best_type
end

.native_typeObject



368
# File 'lib/origami/stream.rb', line 368

def self.native_type ; Stream end

.parse(stream, parser = nil) ⇒ Object

:nodoc:



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/origami/stream.rb', line 119

def self.parse(stream, parser = nil) #:nodoc:
  
  dictionary = Dictionary.parse(stream, parser)
  return dictionary if not stream.skip(@@regexp_open)

  length = dictionary[:Length]
  if not length.is_a?(Integer)
    rawdata = stream.scan_until(@@regexp_close)
    if rawdata.nil?
      raise InvalidStreamObjectError, 
        "Stream shall end with a 'endstream' statement"
    end
  else
    length = length.value
    rawdata = stream.peek(length)
    stream.pos += length

    if not ( unmatched = stream.scan_until(@@regexp_close) )
      raise InvalidStreamObjectError, 
        "Stream shall end with a 'endstream' statement"
    end
    
    rawdata << unmatched
  end
   
  stm = 
    if Origami::OPTIONS[:enable_type_guessing]
      self.guess_type(dictionary).new('', dictionary.to_h)
    else
      Stream.new('', dictionary.to_h)
    end
  
  rawdata.chomp!(TOKENS.last)

  if rawdata[-1,1] == "\n"
    if rawdata[-2,1] == "\r"
      rawdata = rawdata[0, rawdata.size - 2]
    else
      rawdata = rawdata[0, rawdata.size - 1]
    end
  end
  #rawdata.chomp! if length.is_a?(Integer) and length < rawdata.length
  
  stm.rawdata = rawdata
  stm.file_offset = dictionary.file_offset

  stm
end

Instance Method Details

#[](key) ⇒ Object

:nodoc:



356
357
358
# File 'lib/origami/stream.rb', line 356

def [](key) #:nodoc:
  @dictionary[key]
end

#[]=(key, val) ⇒ Object

:nodoc:



360
361
362
# File 'lib/origami/stream.rb', line 360

def []=(key,val) #:nodoc:
  @dictionary[key] = val
end

#cast_to(type) ⇒ Object



212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/origami/stream.rb', line 212

def cast_to(type)
  super(type)

  cast = type.new("", self.dictionary.to_h)
  cast.rawdata = @rawdata.dup
  cast.no, cast.generation = self.no, self.generation
  cast.set_indirect(true) 
  cast.set_pdf(self.pdf) 
  cast.file_offset = self.file_offset

  cast
end

#dataObject

Returns the uncompressed stream content.



232
233
234
235
236
# File 'lib/origami/stream.rb', line 232

def data
  self.decode! if @data.nil?

  @data 
end

#data=(str) ⇒ Object

Sets the uncompressed stream content.

str

The new uncompressed data.



242
243
244
245
# File 'lib/origami/stream.rb', line 242

def data=(str)      
  @rawdata = nil
  @data = str
end

#decode!Object

Uncompress the stream data.



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/origami/stream.rb', line 268

def decode!
  self.decrypt! if self.is_a?(Encryption::EncryptedStream)
  
  unless is_decoded?
    filters = self.Filter
    
    if filters.nil?
      @data = @rawdata.dup
    else
      case filters
        when Array, Name then
          dparams = self.DecodeParms || []

          dparams = [ dparams ] unless dparams.is_a?(::Array)
          filters = [ filters ] unless filters.is_a?(::Array)
      
          @data = @rawdata.dup
          @data.freeze

          filters.length.times do |layer|
            params = dparams[layer].is_a?(Dictionary) ? dparams[layer] : {}
            filter = filters[layer]

            begin
              @data = decode_data(@data, filter, params)
            rescue Filter::InvalidFilterDataError => e
              @data = e.decoded_data if e.decoded_data
              raise InvalidStreamObjectError, 
                "Error while decoding stream #{self.reference}\n\t-> [#{e.class}] #{e.message}"
            end
          end
        else
          raise InvalidStreamObjectError, "Invalid Filter type parameter"
      end
    end
  end
  
  self
end

#each_key(&b) ⇒ Object

:nodoc:



364
365
366
# File 'lib/origami/stream.rb', line 364

def each_key(&b) #:nodoc:
  @dictionary.each_key(&b)
end

#encode!Object

Compress the stream data.



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/origami/stream.rb', line 311

def encode!

  unless is_encoded?
    filters = self.Filter
    
    if filters.nil?
      @rawdata = @data.dup
    else
      case filters
        when Array, Name then
          dparams = self.DecodeParms || []

          dparams = [ dparams ] unless dparams.is_a?(::Array)
          filters = [ filters ] unless filters.is_a?(::Array)
      
          @rawdata = @data.dup
          (filters.length - 1).downto(0) do |layer|
            params = dparams[layer].is_a?(Dictionary) ? dparams[layer] : {}
            filter = filters[layer]

            @rawdata = encode_data(@rawdata, filter, params)
          end
        else
          raise InvalidStreamObjectError, "Invalid filter type parameter"
      end
    end
    
    self.Length = @rawdata.length
  end
  
  self
end

#post_buildObject



104
105
106
107
108
# File 'lib/origami/stream.rb', line 104

def post_build
  self.Length = @rawdata.length
 
  super
end

#pre_buildObject



98
99
100
101
102
# File 'lib/origami/stream.rb', line 98

def pre_build
  encode!
 
  super
end

#rawdataObject

Returns the raw compressed stream content.



250
251
252
253
254
# File 'lib/origami/stream.rb', line 250

def rawdata
  self.encode! if @rawdata.nil? 

  @rawdata
end

#rawdata=(str) ⇒ Object

Sets the raw compressed stream content.

str

the new raw data.



260
261
262
263
# File 'lib/origami/stream.rb', line 260

def rawdata=(str)
  @rawdata = str
  @data = nil
end

#set_predictor(predictor, colors = 1, bitspercomponent = 8, columns = 1) ⇒ Object



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/origami/stream.rb', line 190

def set_predictor(predictor, colors = 1, bitspercomponent = 8, columns = 1)
  
  filters = self.Filter
  filters = [ filters ] unless filters.is_a?(::Array)

  if not filters.include?(:FlateDecode) and not filters.include?(:LZWDecode)
    raise InvalidStreamObjectError, 'Predictor functions can only be used with Flate or LZW filters'
  end

  layer = filters.index(:FlateDecode) or filters.index(:LZWDecode)

  params = Filter::LZW::DecodeParms.new
  params[:Predictor] = predictor
  params[:Colors] = colors if colors != 1
  params[:BitsPerComponent] = bitspercomponent if bitspercomponent != 8
  params[:Columns] = columns if columns != 1

  set_decode_params(layer, params)
  
  self
end

#to_obfuscated_strObject



206
207
208
209
210
211
212
213
214
215
# File 'lib/origami/obfuscation.rb', line 206

def to_obfuscated_str
  content = ""
  
  content << @dictionary.to_obfuscated_str
  content << "stream" + EOL
  content << self.rawdata
  content << EOL << TOKENS.last
  
  super(content)
end

#to_s(indent = 1) ⇒ Object

:nodoc:



344
345
346
347
348
349
350
351
352
353
354
# File 'lib/origami/stream.rb', line 344

def to_s(indent = 1) #:nodoc:
  
  content = ""
  
  content << @dictionary.to_s(indent)
  content << "stream" + EOL
  content << self.rawdata
  content << EOL << TOKENS.last
  
  super(content)
end

#valueObject

:nodoc:



225
226
227
# File 'lib/origami/stream.rb', line 225

def value #:nodoc:
  self
end