Class: Omnizip::Formats::Ole::Dirent

Inherits:
Object
  • Object
show all
Includes:
Constants
Defined in:
lib/omnizip/formats/ole/dirent.rb

Overview

OLE directory entry (dirent)

Represents a file or directory entry in an OLE compound document. Each dirent is 128 bytes and contains metadata about the entry.

Constant Summary collapse

PACK =

Pack format for dirent structure

"a64 v C C V3 a16 V a8 a8 V2 a4"

Constants included from Constants

Constants::AVAIL, Constants::BAT, Constants::BYTE_ORDER_LE, Constants::DEFAULT_BIG_BLOCK_SHIFT, Constants::DEFAULT_SMALL_BLOCK_SHIFT, Constants::DEFAULT_THRESHOLD, Constants::DIRENT_COLORS, Constants::DIRENT_SIZE, Constants::DIRENT_TYPES, Constants::EOC, Constants::EOT, Constants::HEADER_BLOCK_SIZE, Constants::HEADER_SIZE, Constants::MAGIC, Constants::MAX_NAME_LENGTH, Constants::META_BAT

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ole) ⇒ Dirent

Initialize dirent

Parameters:

  • ole (Object)

    Parent OLE storage



112
113
114
115
116
117
118
119
# File 'lib/omnizip/formats/ole/dirent.rb', line 112

def initialize(ole)
  @ole = ole
  @children = []
  @name_lookup = {}
  @parent = nil
  @idx = nil
  apply_defaults
end

Instance Attribute Details

#childInteger

Returns First child index.

Returns:

  • (Integer)

    First child index



35
36
37
# File 'lib/omnizip/formats/ole/dirent.rb', line 35

def child
  @child
end

#clsidString

Returns 16-byte CLSID.

Returns:

  • (String)

    16-byte CLSID



38
39
40
# File 'lib/omnizip/formats/ole/dirent.rb', line 38

def clsid
  @clsid
end

#colourInteger

Returns Red-black tree color.

Returns:

  • (Integer)

    Red-black tree color



26
27
28
# File 'lib/omnizip/formats/ole/dirent.rb', line 26

def colour
  @colour
end

#create_timeTime? (readonly)

Returns Creation time.

Returns:

  • (Time, nil)

    Creation time



68
69
70
# File 'lib/omnizip/formats/ole/dirent.rb', line 68

def create_time
  @create_time
end

#create_time_strString

Returns 8-byte creation time.

Returns:

  • (String)

    8-byte creation time



44
45
46
# File 'lib/omnizip/formats/ole/dirent.rb', line 44

def create_time_str
  @create_time_str
end

#first_blockInteger

Returns First block of data.

Returns:

  • (Integer)

    First block of data



50
51
52
# File 'lib/omnizip/formats/ole/dirent.rb', line 50

def first_block
  @first_block
end

#flagsInteger

Returns Flags (for directories).

Returns:

  • (Integer)

    Flags (for directories)



41
42
43
# File 'lib/omnizip/formats/ole/dirent.rb', line 41

def flags
  @flags
end

#idxInteger?

Returns Index in dirent array (used during loading).

Returns:

  • (Integer, nil)

    Index in dirent array (used during loading)



83
84
85
# File 'lib/omnizip/formats/ole/dirent.rb', line 83

def idx
  @idx
end

#modify_timeTime? (readonly)

Returns Modification time.

Returns:

  • (Time, nil)

    Modification time



71
72
73
# File 'lib/omnizip/formats/ole/dirent.rb', line 71

def modify_time
  @modify_time
end

#modify_time_strString

Returns 8-byte modification time.

Returns:

  • (String)

    8-byte modification time



47
48
49
# File 'lib/omnizip/formats/ole/dirent.rb', line 47

def modify_time_str
  @modify_time_str
end

#nameString

Returns Decoded entry name.

Returns:

  • (String)

    Decoded entry name



65
66
67
# File 'lib/omnizip/formats/ole/dirent.rb', line 65

def name
  @name
end

#name_lenInteger

Returns Name length in bytes.

Returns:

  • (Integer)

    Name length in bytes



20
21
22
# File 'lib/omnizip/formats/ole/dirent.rb', line 20

def name_len
  @name_len
end

#name_utf16String

Returns 64-byte UTF-16LE name.

Returns:

  • (String)

    64-byte UTF-16LE name



17
18
19
# File 'lib/omnizip/formats/ole/dirent.rb', line 17

def name_utf16
  @name_utf16
end

#nextInteger

Returns Next sibling index.

Returns:

  • (Integer)

    Next sibling index



32
33
34
# File 'lib/omnizip/formats/ole/dirent.rb', line 32

def next
  @next
end

#oleObject (readonly)

Returns Parent OLE storage.

Returns:

  • (Object)

    Parent OLE storage



59
60
61
# File 'lib/omnizip/formats/ole/dirent.rb', line 59

def ole
  @ole
end

#parentArray<Dirent>

Returns Child entries (for directories).

Returns:

  • (Array<Dirent>)

    Child entries (for directories)



74
75
76
# File 'lib/omnizip/formats/ole/dirent.rb', line 74

def parent
  @parent
end

#prevInteger

Returns Previous sibling index.

Returns:

  • (Integer)

    Previous sibling index



29
30
31
# File 'lib/omnizip/formats/ole/dirent.rb', line 29

def prev
  @prev
end

#reservedString

Returns 4-byte reserved.

Returns:

  • (String)

    4-byte reserved



56
57
58
# File 'lib/omnizip/formats/ole/dirent.rb', line 56

def reserved
  @reserved
end

#sizeInteger

Returns Size in bytes.

Returns:

  • (Integer)

    Size in bytes



53
54
55
# File 'lib/omnizip/formats/ole/dirent.rb', line 53

def size
  @size
end

#typeSymbol

Returns Entry type (:empty, :dir, :file, :root).

Returns:

  • (Symbol)

    Entry type (:empty, :dir, :file, :root)



62
63
64
# File 'lib/omnizip/formats/ole/dirent.rb', line 62

def type
  @type
end

#type_idInteger

Returns Entry type (0=empty, 1=dir, 2=file, 5=root).

Returns:

  • (Integer)

    Entry type (0=empty, 1=dir, 2=file, 5=root)



23
24
25
# File 'lib/omnizip/formats/ole/dirent.rb', line 23

def type_id
  @type_id
end

Class Method Details

.create(ole, type:, name:) ⇒ Dirent

Create new dirent with specified type and name

Parameters:

  • ole (Object)

    Parent OLE storage

  • type (Symbol)

    Entry type (:file, :dir, :root)

  • name (String)

    Entry name

Returns:



102
103
104
105
106
107
# File 'lib/omnizip/formats/ole/dirent.rb', line 102

def self.create(ole, type:, name:)
  dirent = new(ole)
  dirent.type = type
  dirent.name = name
  dirent
end

.flatten_helper(children) ⇒ Integer

Helper to create balanced tree structure

Parameters:

Returns:

  • (Integer)

    Index of root of subtree



410
411
412
413
414
415
416
417
418
# File 'lib/omnizip/formats/ole/dirent.rb', line 410

def self.flatten_helper(children)
  return EOT if children.empty?

  i = children.length / 2
  this = children[i]
  this.prev = flatten_helper(children[0...i])
  this.next = flatten_helper(children[(i + 1)..])
  this.idx
end

.parse(ole, data) ⇒ Dirent

Parse dirent from binary data

Parameters:

  • ole (Object)

    Parent OLE storage

  • data (String)

    128-byte dirent data

Returns:



90
91
92
93
94
# File 'lib/omnizip/formats/ole/dirent.rb', line 90

def self.parse(ole, data)
  dirent = new(ole)
  dirent.unpack(data)
  dirent
end

Instance Method Details

#/(name) ⇒ Dirent? Also known as: []

Lookup child by name

Parameters:

  • name (String)

    Child name

Returns:

  • (Dirent, nil)

    Child dirent



339
340
341
# File 'lib/omnizip/formats/ole/dirent.rb', line 339

def /(name)
  @name_lookup&.[](name)
end

#<<(child) ⇒ Object

Add child entry

Parameters:

  • child (Dirent)

    Child to add



347
348
349
350
351
# File 'lib/omnizip/formats/ole/dirent.rb', line 347

def <<(child)
  child.parent = self
  @name_lookup[child.name] = child if @name_lookup
  @children << child
end

#apply_defaultsObject

Apply default values



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/omnizip/formats/ole/dirent.rb', line 122

def apply_defaults
  @name_utf16 = "\x00".b * 64
  @name_len = 2
  @type_id = 0
  @colour = 1 # black
  @prev = EOT
  @next = EOT
  @child = EOT
  @clsid = "\x00".b * 16
  @flags = 0
  @create_time_str = "\x00".b * 8
  @modify_time_str = "\x00".b * 8
  @first_block = EOC
  @size = 0
  @reserved = "\x00".b * 4
  @type = :empty
  @name = ""
end

#childrenArray<Dirent>

Get all children

Returns:



363
364
365
# File 'lib/omnizip/formats/ole/dirent.rb', line 363

def children
  @children || []
end

#dir?Boolean

Check if entry is a directory

Returns:

  • (Boolean)


179
180
181
# File 'lib/omnizip/formats/ole/dirent.rb', line 179

def dir?
  !file?
end

#each_child {|Dirent| ... } ⇒ Object

Iterate over children

Yields:



370
371
372
# File 'lib/omnizip/formats/ole/dirent.rb', line 370

def each_child(&block)
  @children&.each(&block)
end

#empty?Boolean

Check if entry is empty

Returns:

  • (Boolean)


193
194
195
# File 'lib/omnizip/formats/ole/dirent.rb', line 193

def empty?
  @type == :empty
end

#file?Boolean

Check if entry is a file

Returns:

  • (Boolean)


172
173
174
# File 'lib/omnizip/formats/ole/dirent.rb', line 172

def file?
  @type == :file
end

#flatten(dirents = []) ⇒ Array<Dirent>

Flatten tree to array for serialization

Parameters:

  • dirents (Array<Dirent>) (defaults to: [])

    Output array

Returns:



390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# File 'lib/omnizip/formats/ole/dirent.rb', line 390

def flatten(dirents = [])
  @idx = dirents.length
  dirents << self

  if file?
    self.prev = EOT
    self.next = EOT
    self.child = EOT
  else
    children.each { |child| child.flatten(dirents) }
    self.child = self.class.flatten_helper(children)
  end

  dirents
end

#freevoid

This method returns an undefined value.

Free resources to prevent memory leaks

Call this when done with the dirent to release memory.



378
379
380
381
382
383
384
# File 'lib/omnizip/formats/ole/dirent.rb', line 378

def free
  @children&.each { |child| child.free if child.is_a?(Dirent) }
  @children = nil
  @parent = nil
  @ole = nil
  @data = nil
end

#inspectString

Inspect dirent

Returns:

  • (String)


423
424
425
426
427
428
429
# File 'lib/omnizip/formats/ole/dirent.rb', line 423

def inspect
  str = "#<Ole::Dirent:#{@name.inspect}"
  if file?
    str << " size=#{@size}"
  end
  "#{str}>"
end

#name_lookupObject

Public read access to the per-dirent name lookup hash. Used by children to update the parent's index when their name changes.



78
79
80
# File 'lib/omnizip/formats/ole/dirent.rb', line 78

def name_lookup
  @name_lookup ||= {}
end

#open(mode = "r") {|RangesIOMigrateable| ... } ⇒ RangesIOMigrateable

Open stream for reading or writing

Parameters:

  • mode (String) (defaults to: "r")

    Open mode ('r' for read, 'w' for write)

Yields:

Returns:

Raises:

  • (Errno::EISDIR)

    If entry is a directory



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/omnizip/formats/ole/dirent.rb', line 318

def open(mode = "r")
  raise Errno::EISDIR unless file?

  io = RangesIOMigrateable.new(self, mode)
  @modify_time = Time.now if io.is_a?(RangesIOMigrateable) && io.writeable?

  if block_given?
    begin
      yield io
    ensure
      io.close
    end
  else
    io
  end
end

#packString

Pack dirent to binary data

Returns:

  • (String)

    128-byte binary data



259
260
261
262
263
264
265
266
267
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
# File 'lib/omnizip/formats/ole/dirent.rb', line 259

def pack
  # Encode name to UTF-16LE with null terminator
  name_data = Types::Variant.dump(Types::Variant::VT_LPWSTR, @name)
  # Truncate to 62 bytes if needed (leaving room for null terminator)
  name_data = name_data[0, 62] + "\x00\x00".b if name_data.length > 62
  # Ensure null terminator exists
  name_data += "\x00\x00".b unless name_data.end_with?("\x00\x00".b)
  @name_len = name_data.length
  # Pad to 64 bytes total
  @name_utf16 = name_data + ("\x00".b * (64 - name_data.length))

  # Set type_id from type
  @type_id = DIRENT_TYPES.invert[@type] || 0

  # For directories, first_block should be EOT
  if dir? && !root?
    @first_block = EOT
  end

  # Encode timestamps for files
  if file?
    if @create_time
      @create_time_str = Types::Variant.dump(Types::Variant::VT_FILETIME,
                                             @create_time)
    end
    if @modify_time
      @modify_time_str = Types::Variant.dump(Types::Variant::VT_FILETIME,
                                             @modify_time)
    end
  else
    @create_time_str = "\x00".b * 8
    @modify_time_str = "\x00".b * 8
  end

  [
    @name_utf16, @name_len, @type_id, @colour,
    @prev, @next, @child, @clsid, @flags,
    @create_time_str, @modify_time_str,
    @first_block, @size, @reserved
  ].pack(PACK)
end

#readString

Read file content

Returns:

  • (String)

    File content

Raises:

  • (Errno::EISDIR)

    If entry is a directory



305
306
307
308
309
310
# File 'lib/omnizip/formats/ole/dirent.rb', line 305

def read
  raise Errno::EISDIR unless file?

  bat = @ole.bat_for_size(@size)
  bat.read(@first_block, @size)
end

#root?Boolean

Check if entry is root

Returns:

  • (Boolean)


186
187
188
# File 'lib/omnizip/formats/ole/dirent.rb', line 186

def root?
  @type == :root
end

#unpack(data) ⇒ Object

Unpack dirent from binary data

Parameters:

  • data (String)

    128-byte dirent data



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
# File 'lib/omnizip/formats/ole/dirent.rb', line 200

def unpack(data)
  values = data.unpack(PACK)
  @name_utf16 = values[0]
  @name_len = values[1]
  @type_id = values[2]
  @colour = values[3]
  @prev = values[4]
  @next = values[5]
  @child = values[6]
  @clsid = values[7]
  @flags = values[8]
  @create_time_str = values[9]
  @modify_time_str = values[10]
  @first_block = values[11]
  @size = values[12]
  @reserved = values[13]

  # Decode name from UTF-16LE
  # name_len includes the null terminator
  name_data = @name_utf16[0...@name_len] if @name_len.positive?
  @name = begin
    # Decode and strip null terminator
    decoded = name_data.dup.force_encoding(Encoding::UTF_16LE)
    # Remove trailing null character (UTF-16 null = 2 bytes)
    null_char = "\x00".encode(Encoding::UTF_16LE)
    decoded = decoded.chomp(null_char)
    decoded.encode(Encoding::UTF_8)
  rescue StandardError
    ""
  end

  # Decode type
  @type = DIRENT_TYPES[@type_id] || :empty

  # Decode timestamps for files
  if file?
    @create_time = begin
      Types::Variant.load(Types::Variant::VT_FILETIME, @create_time_str)
    rescue StandardError
      nil
    end
    @modify_time = begin
      Types::Variant.load(Types::Variant::VT_FILETIME, @modify_time_str)
    rescue StandardError
      nil
    end
    @children = nil
    @name_lookup = nil
  else
    @create_time = nil
    @modify_time = nil
    @children = []
    @name_lookup = {}
  end
end