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

Inherits:
Object
  • Object
show all
Includes:
Entry, 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



118
119
120
121
122
123
124
125
# File 'lib/omnizip/formats/ole/dirent.rb', line 118

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



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

def child
  @child
end

#clsidString

Returns 16-byte CLSID.

Returns:

  • (String)

    16-byte CLSID



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

def clsid
  @clsid
end

#colourInteger

Returns Red-black tree color.

Returns:

  • (Integer)

    Red-black tree color



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

def colour
  @colour
end

#create_timeTime? (readonly)

Returns Creation time.

Returns:

  • (Time, nil)

    Creation time



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

def create_time
  @create_time
end

#create_time_strString

Returns 8-byte creation time.

Returns:

  • (String)

    8-byte creation time



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

def create_time_str
  @create_time_str
end

#first_blockInteger

Returns First block of data.

Returns:

  • (Integer)

    First block of data



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

def first_block
  @first_block
end

#flagsInteger

Returns Flags (for directories).

Returns:

  • (Integer)

    Flags (for directories)



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

def flags
  @flags
end

#idxInteger?

Returns Index in dirent array (used during loading).

Returns:

  • (Integer, nil)

    Index in dirent array (used during loading)



89
90
91
# File 'lib/omnizip/formats/ole/dirent.rb', line 89

def idx
  @idx
end

#modify_timeTime? (readonly)

Returns Modification time.

Returns:

  • (Time, nil)

    Modification time



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

def modify_time
  @modify_time
end

#modify_time_strString

Returns 8-byte modification time.

Returns:

  • (String)

    8-byte modification time



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

def modify_time_str
  @modify_time_str
end

#nameString

Returns Decoded entry name.

Returns:

  • (String)

    Decoded entry name



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

def name
  @name
end

#name_lenInteger

Returns Name length in bytes.

Returns:

  • (Integer)

    Name length in bytes



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

def name_len
  @name_len
end

#name_utf16String

Returns 64-byte UTF-16LE name.

Returns:

  • (String)

    64-byte UTF-16LE name



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

def name_utf16
  @name_utf16
end

#nextInteger

Returns Next sibling index.

Returns:

  • (Integer)

    Next sibling index



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

def next
  @next
end

#oleObject (readonly)

Returns Parent OLE storage.

Returns:

  • (Object)

    Parent OLE storage



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

def ole
  @ole
end

#parentArray<Dirent>

Returns Child entries (for directories).

Returns:

  • (Array<Dirent>)

    Child entries (for directories)



80
81
82
# File 'lib/omnizip/formats/ole/dirent.rb', line 80

def parent
  @parent
end

#prevInteger

Returns Previous sibling index.

Returns:

  • (Integer)

    Previous sibling index



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

def prev
  @prev
end

#reservedString

Returns 4-byte reserved.

Returns:

  • (String)

    4-byte reserved



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

def reserved
  @reserved
end

#sizeInteger

Returns Size in bytes.

Returns:

  • (Integer)

    Size in bytes



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

def size
  @size
end

#typeSymbol

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

Returns:

  • (Symbol)

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



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

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)



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

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:



108
109
110
111
112
113
# File 'lib/omnizip/formats/ole/dirent.rb', line 108

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



416
417
418
419
420
421
422
423
424
# File 'lib/omnizip/formats/ole/dirent.rb', line 416

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:



96
97
98
99
100
# File 'lib/omnizip/formats/ole/dirent.rb', line 96

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



345
346
347
# File 'lib/omnizip/formats/ole/dirent.rb', line 345

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

#<<(child) ⇒ Object

Add child entry

Parameters:

  • child (Dirent)

    Child to add



353
354
355
356
357
# File 'lib/omnizip/formats/ole/dirent.rb', line 353

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

#apply_defaultsObject

Apply default values



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/omnizip/formats/ole/dirent.rb', line 128

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:



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

def children
  @children || []
end

#dir?Boolean

Check if entry is a directory

Returns:

  • (Boolean)


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

def dir?
  !file?
end

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

Iterate over children

Yields:



376
377
378
# File 'lib/omnizip/formats/ole/dirent.rb', line 376

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

#empty?Boolean

Check if entry is empty

Returns:

  • (Boolean)


199
200
201
# File 'lib/omnizip/formats/ole/dirent.rb', line 199

def empty?
  @type == :empty
end

#entry_directory?Boolean

Returns:

  • (Boolean)


15
# File 'lib/omnizip/formats/ole/dirent.rb', line 15

def entry_directory? = dir?

#entry_mtimeObject



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

def entry_mtime = modify_time

#entry_nameObject



14
# File 'lib/omnizip/formats/ole/dirent.rb', line 14

def entry_name = name

#entry_sizeObject



16
# File 'lib/omnizip/formats/ole/dirent.rb', line 16

def entry_size = nil

#file?Boolean

Check if entry is a file

Returns:

  • (Boolean)


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

def file?
  @type == :file
end

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

Flatten tree to array for serialization

Parameters:

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

    Output array

Returns:



396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
# File 'lib/omnizip/formats/ole/dirent.rb', line 396

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.



384
385
386
387
388
389
390
# File 'lib/omnizip/formats/ole/dirent.rb', line 384

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)


429
430
431
432
433
434
435
# File 'lib/omnizip/formats/ole/dirent.rb', line 429

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.



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

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



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/omnizip/formats/ole/dirent.rb', line 324

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



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
300
301
302
303
304
305
# File 'lib/omnizip/formats/ole/dirent.rb', line 265

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



311
312
313
314
315
316
# File 'lib/omnizip/formats/ole/dirent.rb', line 311

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)


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

def root?
  @type == :root
end

#unpack(data) ⇒ Object

Unpack dirent from binary data

Parameters:

  • data (String)

    128-byte dirent data



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

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