Class: FilesHunter::Decoders::TIFF

Inherits:
BeginPatternDecoder show all
Defined in:
lib/fileshunter/Decoders/TIFF.rb

Constant Summary collapse

BEGIN_PATTERN_TIFF_LE =
"II*\x00".force_encoding(Encoding::ASCII_8BIT)
BEGIN_PATTERN_TIFF_BE =
"MM\x00*".force_encoding(Encoding::ASCII_8BIT)
BEGIN_PATTERN_TIFF =
Regexp.new("(#{Regexp.escape(BEGIN_PATTERN_TIFF_LE)}|#{Regexp.escape(BEGIN_PATTERN_TIFF_BE)})", nil, 'n')
TYPE_SIZES =
{
  1 => 1,
  2 => 1,
  3 => 2,
  4 => 4,
  5 => 8,
  6 => 1,
  7 => 1,
  8 => 2,
  9 => 4,
  10 => 8,
  11 => 4,
  12 => 8
}
VALID_COMPRESSION_VALUES =
[ 1, 2, 3, 4, 5, 6, 32773 ]
VALID_PHOTOMETRIC_INTERPRETATIONS =
[ 0, 1, 2, 3, 4, 5, 6, 8 ]
TRAILING_00_REGEXP =
Regexp.new("\x00*$".force_encoding(Encoding::ASCII_8BIT), nil, 'n')
NULL_TERMINATING_CHAR =
"\x00".force_encoding(Encoding::ASCII_8BIT)

Instance Method Summary collapse

Methods inherited from BeginPatternDecoder

#find_segments

Methods inherited from FilesHunter::Decoder

#segments_found, #setup

Constructor Details

#initializeTIFF

Returns a new instance of TIFF.



35
36
37
38
# File 'lib/fileshunter/Decoders/TIFF.rb', line 35

def initialize
  super
  @accept_no_image_data = false
end

Instance Method Details

#accept_no_image_dataObject

Set this decoder to accept no image data. This is particularly useful for other decoders using it (for example with JPEG and its Exif info)



46
47
48
# File 'lib/fileshunter/Decoders/TIFF.rb', line 46

def accept_no_image_data
  @accept_no_image_data = true
end

#decode(offset) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/fileshunter/Decoders/TIFF.rb', line 50

def decode(offset)
  @file_offset = offset
  @bindata_reader_16 = nil
  @bindata_reader_32 = nil
  if (@data[offset..offset+3] == BEGIN_PATTERN_TIFF_LE)
    @bindata_reader_16 = BinData::Uint16le
    @bindata_reader_32 = BinData::Uint32le
  else
    @bindata_reader_16 = BinData::Uint16be
    @bindata_reader_32 = BinData::Uint32be
  end
  ifd_offset = @bindata_reader_32.read(@data[offset+4..offset+7])
  extensions = [:tif, :tiff] # By default
  @max_end_offset = ifd_offset
  @strip_offsets = []
  @strip_byte_counts = []
  @tile_offsets = []
  @tile_byte_counts = []
  @compression = 1
  @lst_bits_per_sample = [1]
  @image_width = nil
  @image_length = nil
  @tag_parser = Proc.new do |tag, type, nbr, size, cursor|
    case tag.to_i
    when 2
      ( :gps_latitude => @data[cursor..cursor+size-1] )
    when 4
      ( :gps_longitude => @data[cursor..cursor+size-1] )
    when 6
      ( :gps_altitude => @data[cursor..cursor+size-1] )
    when 7
      ( :gps_timestamp => [
        read_ratio(cursor),
        read_ratio(cursor+8),
        read_ratio(cursor+16),
      ] )
    when 256
      @image_width = ((type == 3) ? @bindata_reader_16.read(@data[cursor..cursor+1]) : @bindata_reader_32.read(@data[cursor..cursor+3]))
      invalid_data("@#{cursor} - Invalid image width #{@image_width}") if (@image_width == 0)
      ( :image_width => @image_width )
    when 257
      @image_length = ((type == 3) ? @bindata_reader_16.read(@data[cursor..cursor+1]) : @bindata_reader_32.read(@data[cursor..cursor+3]))
      invalid_data("@#{cursor} - Invalid image length #{@image_length}") if (@image_length == 0)
      ( :image_length => @image_length )
    when 258
      @lst_bits_per_sample = []
      nbr.times do |idx_sample|
        @lst_bits_per_sample << @bindata_reader_16.read(@data[cursor+2*idx_sample..cursor+2*idx_sample+1])
      end
      ( :lst_bits_per_sample => @lst_bits_per_sample )
    when 259
      @compression = @bindata_reader_16.read(@data[cursor..cursor+1])
      invalid_data("@#{cursor} - Invalid compression #{@compression}") if (!VALID_COMPRESSION_VALUES.include?(@compression))
      ( :compression => @compression )
    when 262
      photometric_interpretation = @bindata_reader_16.read(@data[cursor..cursor+1])
      invalid_data("@#{cursor} - Invalid photometric interpretation #{photometric_interpretation}") if (!VALID_PHOTOMETRIC_INTERPRETATIONS.include?(photometric_interpretation))
      ( :photometric_interpretation => photometric_interpretation )
    when 264
      cell_width = @bindata_reader_16.read(@data[cursor..cursor+1])
      invalid_data("@#{cursor} - Invalid cell width #{cell_width}") if (cell_width == 0)
      ( :cell_width => cell_width )
    when 265
      cell_length = @bindata_reader_16.read(@data[cursor..cursor+1])
      invalid_data("@#{cursor} - Invalid cell length #{cell_length}") if (cell_length == 0)
      ( :cell_length => cell_length )
    when 266
      fill_order = @bindata_reader_16.read(@data[cursor..cursor+1])
      invalid_data("@#{cursor} - Invalid fill order #{fill_order}") if ((fill_order == 0) or (fill_order > 2))
      ( :fill_order => fill_order )
    when 269
      ( :document_name => read_ascii(cursor, size) )
    when 270
      ( :image_description => read_ascii(cursor, size) )
    when 271
      ( :make => read_ascii(cursor, size) )
    when 272
      ( :model => read_ascii(cursor, size) )
    when 273
      value_size = ((type == 3) ? 2 : 4)
      nbr.times do |idx|
        @strip_offsets << ((type == 3) ? @bindata_reader_16.read(@data[cursor+idx*value_size..cursor+idx*value_size+1]) : @bindata_reader_32.read(@data[cursor+idx*value_size..cursor+idx*value_size+3]))
      end
      found_relevant_data(extensions)
    when 274
      orientation = @bindata_reader_16.read(@data[cursor..cursor+1])
      invalid_data("@#{cursor} - Invalid orientation #{orientation}") if ((orientation == 0) or (orientation > 8))
      ( :orientation => orientation )
    when 277
      samples_per_pixel = @bindata_reader_16.read(@data[cursor..cursor+1])
      invalid_data("@#{cursor} - Invalid samples per pixel #{samples_per_pixel}") if (samples_per_pixel == 0)
      ( :samples_per_pixel => samples_per_pixel )
    when 278
      rows_per_strip = ((type == 3) ? @bindata_reader_16.read(@data[cursor..cursor+1]) : @bindata_reader_32.read(@data[cursor..cursor+3]))
      invalid_data("@#{cursor} - Invalid rows per strip #{rows_per_strip}") if (rows_per_strip == 0)
      ( :rows_per_strip => rows_per_strip )
    when 279
      value_size = ((type == 3) ? 2 : 4)
      nbr.times do |idx|
        @strip_byte_counts << ((type == 3) ? @bindata_reader_16.read(@data[cursor+idx*value_size..cursor+idx*value_size+1]) : @bindata_reader_32.read(@data[cursor+idx*value_size..cursor+idx*value_size+3]))
      end
    when 282
      ratio = read_ratio(cursor)
      invalid_data("@#{cursor} - Invalid x resolution #{ratio}") if (ratio == 0)
      ( :x_resolution => ratio )
    when 283
      ratio = read_ratio(cursor)
      invalid_data("@#{cursor} - Invalid y resolution #{ratio}") if (ratio == 0)
      ( :y_resolution => ratio )
    when 285
      ( :page_name => read_ascii(cursor, size) )
    when 296
      resolution_unit = @bindata_reader_16.read(@data[cursor..cursor+1])
      invalid_data("@#{cursor} - Invalid resolution unit #{resolution_unit}") if ((resolution_unit == 0) or (resolution_unit > 3))
      ( :resolution_unit => resolution_unit )
    when 297
      page_number = @bindata_reader_16.read(@data[cursor..cursor+1])
      page_total = @bindata_reader_16.read(@data[cursor+2..cursor+3])
      invalid_data("@#{cursor} - Invalid page total #{page_total}") if (page_total == 0)
      ( :page_number => page_number, :page_total => page_total )
    when 305
      ( :software => read_ascii(cursor, size) )
    when 306
      ( :date_time => read_ascii(cursor, size) )
    when 315
      ( :artist => read_ascii(cursor, size) )
    when 316
      ( :host_computer => read_ascii(cursor, size) )
    when 324
      nbr.times do |idx|
        @tile_offsets << @bindata_reader_32.read(@data[cursor+idx*4..cursor+idx*4+3])
      end
      found_relevant_data(extensions)
    when 325
      nbr.times do |idx|
        @tile_byte_counts << @bindata_reader_32.read(@data[cursor+idx*4..cursor+idx*4+3])
      end
    when 337
      ( :target_printer => read_ascii(cursor, size) )
    when 33432
      ( :copyright => read_ascii(cursor, size) )
    when 33434
      ( :exposure_time => read_ratio(cursor) )
    when 33437
      ( :f_number => read_ratio(cursor) )
    when 34665
      exif_ifd_offset = @bindata_reader_32.read(@data[cursor..cursor+3])
      ( :exif_ifd => true )
      parse_ifd(exif_ifd_offset, &@tag_parser)
    when 34853
      gps_ifd_offset = @bindata_reader_32.read(@data[cursor..cursor+3])
      ( :gps_ifd => true )
      parse_ifd(gps_ifd_offset, &@tag_parser)
    when 36864
      ( :exif_version => read_ascii(cursor, size) )
    when 36867
      ( :date_time_original => read_ascii(cursor, size) )
    when 36868
      ( :date_time_digitized => read_ascii(cursor, size) )
    when 37386
      ( :focal_length => read_ratio(cursor) )
    when 37510
      ( :user_comment => read_ascii(cursor, size) )
    when 37520
      ( :subsec_time => read_ascii(cursor, size) )
    when 37521
      ( :subsec_time_original => read_ascii(cursor, size) )
    when 37522
      ( :subsec_time_digitized => read_ascii(cursor, size) )
    when 40960
      ( :flashpix_version => read_ascii(cursor, size) )
    when 40962
      ( :pixel_x_dimension => ((type == 3) ? @bindata_reader_16.read(@data[cursor..cursor+1]) : @bindata_reader_32.read(@data[cursor..cursor+3])) )
    when 40963
      ( :pixel_y_dimension => ((type == 3) ? @bindata_reader_16.read(@data[cursor..cursor+1]) : @bindata_reader_32.read(@data[cursor..cursor+3])) )
    when 40965
      interoperability_ifd_offset = @bindata_reader_32.read(@data[cursor..cursor+3])
      ( :interoperability_ifd => true )
      parse_ifd(interoperability_ifd_offset, &@tag_parser)
    end
  end
  parse_ifd(ifd_offset, &@tag_parser)
  log_debug "@#{@file_offset + @max_end_offset} - Found #{@strip_offsets.size} strips and #{@tile_offsets.size} tiles."
  found_relevant_data(extensions)
  invalid_data("@#{@file_offset + @max_end_offset} - No strips nor tiles defined.") if ((!@accept_no_image_data) and (@strip_offsets.empty?) and (@tile_offsets.empty?))
  # Special case:
  if ((@strip_offsets.size == 1) and
      (@strip_byte_counts.empty?))
    # Compute the strip size: this is the total image size
    invalid_data("@#{@file_offset + @max_end_offset} - Missing strip byte counts and image is compressed") if (@compression != 1)
    invalid_data("@#{@file_offset + @max_end_offset} - Missing image width") if (@image_width == nil)
    invalid_data("@#{@file_offset + @max_end_offset} - Missing image length") if (@image_length == nil)
    # Compute a single row size
    nbr_bits_per_pixel = 0
    all_samples_16 = true
    all_samples_32 = true
    @lst_bits_per_sample.each do |nbr_bits|
      nbr_bits_per_pixel += nbr_bits
      all_samples_16 = false if (nbr_bits != 16)
      all_samples_32 = false if (nbr_bits != 32)
    end
    row_size_bits = @image_width * nbr_bits_per_pixel
    # Compute the padding in bits
    bits_padding = (all_samples_16 ? 16 : (all_samples_32 ? 32 : 8))
    bits_rest = row_size_bits % bits_padding
    row_size_bits += bits_padding - bits_rest if (bits_rest != 0)
    # We have the real row size
    image_end_offset = @strip_offsets[0] + @image_length * (row_size_bits / 8)
    @max_end_offset = image_end_offset if (@max_end_offset < image_end_offset)
  else
    invalid_data("@#{@file_offset + @max_end_offset} - Found #{@strip_offsets.size} strip offsets but #{@strip_byte_counts.size} strip bytes count") if (@strip_offsets.size != @strip_byte_counts.size)
    invalid_data("@#{@file_offset + @max_end_offset} - Found #{@tile_offsets.size} tile offsets but #{@tile_byte_counts.size} tile bytes count") if (@tile_offsets.size != @tile_byte_counts.size)
    # Read all strips
    @strip_offsets.each_with_index do |strip_offset, idx_strip|
       @max_end_offset = strip_offset + @strip_byte_counts[idx_strip] if (@max_end_offset < strip_offset + @strip_byte_counts[idx_strip])
    end
    # Read all tiles
    @tile_offsets.each_with_index do |tile_offset, idx_tile|
       @max_end_offset = tile_offset + @tile_byte_counts[idx_tile] if (@max_end_offset < tile_offset + @tile_byte_counts[idx_tile])
    end
  end

  return @file_offset + @max_end_offset
end

#get_begin_patternObject



40
41
42
# File 'lib/fileshunter/Decoders/TIFF.rb', line 40

def get_begin_pattern
  return BEGIN_PATTERN_TIFF, { :offset_inc => 4, :max_regexp_size => 4 }
end