Class: Omnizip::Formats::Ole::Dirent
- Inherits:
-
Object
- Object
- Omnizip::Formats::Ole::Dirent
- 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
-
#child ⇒ Integer
First child index.
-
#clsid ⇒ String
16-byte CLSID.
-
#colour ⇒ Integer
Red-black tree color.
-
#create_time ⇒ Time?
readonly
Creation time.
-
#create_time_str ⇒ String
8-byte creation time.
-
#first_block ⇒ Integer
First block of data.
-
#flags ⇒ Integer
Flags (for directories).
-
#idx ⇒ Integer?
Index in dirent array (used during loading).
-
#modify_time ⇒ Time?
readonly
Modification time.
-
#modify_time_str ⇒ String
8-byte modification time.
-
#name ⇒ String
Decoded entry name.
-
#name_len ⇒ Integer
Name length in bytes.
-
#name_utf16 ⇒ String
64-byte UTF-16LE name.
-
#next ⇒ Integer
Next sibling index.
-
#ole ⇒ Object
readonly
Parent OLE storage.
-
#parent ⇒ Array<Dirent>
Child entries (for directories).
-
#prev ⇒ Integer
Previous sibling index.
-
#reserved ⇒ String
4-byte reserved.
-
#size ⇒ Integer
Size in bytes.
-
#type ⇒ Symbol
Entry type (:empty, :dir, :file, :root).
-
#type_id ⇒ Integer
Entry type (0=empty, 1=dir, 2=file, 5=root).
Class Method Summary collapse
-
.create(ole, type:, name:) ⇒ Dirent
Create new dirent with specified type and name.
-
.flatten_helper(children) ⇒ Integer
Helper to create balanced tree structure.
-
.parse(ole, data) ⇒ Dirent
Parse dirent from binary data.
Instance Method Summary collapse
-
#/(name) ⇒ Dirent?
(also: #[])
Lookup child by name.
-
#<<(child) ⇒ Object
Add child entry.
-
#apply_defaults ⇒ Object
Apply default values.
-
#children ⇒ Array<Dirent>
Get all children.
-
#dir? ⇒ Boolean
Check if entry is a directory.
-
#each_child {|Dirent| ... } ⇒ Object
Iterate over children.
-
#empty? ⇒ Boolean
Check if entry is empty.
- #entry_directory? ⇒ Boolean
- #entry_mtime ⇒ Object
- #entry_name ⇒ Object
- #entry_size ⇒ Object
-
#file? ⇒ Boolean
Check if entry is a file.
-
#flatten(dirents = []) ⇒ Array<Dirent>
Flatten tree to array for serialization.
-
#free ⇒ void
Free resources to prevent memory leaks.
-
#initialize(ole) ⇒ Dirent
constructor
Initialize dirent.
-
#inspect ⇒ String
Inspect dirent.
-
#name_lookup ⇒ Object
Public read access to the per-dirent name lookup hash.
-
#open(mode = "r") {|RangesIOMigrateable| ... } ⇒ RangesIOMigrateable
Open stream for reading or writing.
-
#pack ⇒ String
Pack dirent to binary data.
-
#read ⇒ String
Read file content.
-
#root? ⇒ Boolean
Check if entry is root.
-
#unpack(data) ⇒ Object
Unpack dirent from binary data.
Constructor Details
#initialize(ole) ⇒ Dirent
Initialize dirent
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
#child ⇒ Integer
Returns First child index.
41 42 43 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 41 def child @child end |
#clsid ⇒ String
Returns 16-byte CLSID.
44 45 46 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 44 def clsid @clsid end |
#colour ⇒ Integer
Returns Red-black tree color.
32 33 34 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 32 def colour @colour end |
#create_time ⇒ Time? (readonly)
Returns Creation time.
74 75 76 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 74 def create_time @create_time end |
#create_time_str ⇒ String
Returns 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_block ⇒ Integer
Returns First block of data.
56 57 58 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 56 def first_block @first_block end |
#flags ⇒ Integer
Returns Flags (for directories).
47 48 49 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 47 def flags @flags end |
#idx ⇒ Integer?
Returns Index in dirent array (used during loading).
89 90 91 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 89 def idx @idx end |
#modify_time ⇒ Time? (readonly)
Returns Modification time.
77 78 79 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 77 def modify_time @modify_time end |
#modify_time_str ⇒ String
Returns 8-byte modification time.
53 54 55 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 53 def modify_time_str @modify_time_str end |
#name ⇒ String
Returns Decoded entry name.
71 72 73 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 71 def name @name end |
#name_len ⇒ Integer
Returns Name length in bytes.
26 27 28 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 26 def name_len @name_len end |
#name_utf16 ⇒ String
Returns 64-byte UTF-16LE name.
23 24 25 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 23 def name_utf16 @name_utf16 end |
#next ⇒ Integer
Returns Next sibling index.
38 39 40 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 38 def next @next end |
#ole ⇒ Object (readonly)
Returns Parent OLE storage.
65 66 67 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 65 def ole @ole end |
#parent ⇒ Array<Dirent>
Returns Child entries (for directories).
80 81 82 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 80 def parent @parent end |
#prev ⇒ Integer
Returns Previous sibling index.
35 36 37 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 35 def prev @prev end |
#reserved ⇒ String
Returns 4-byte reserved.
62 63 64 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 62 def reserved @reserved end |
#size ⇒ Integer
Returns Size in bytes.
59 60 61 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 59 def size @size end |
#type ⇒ Symbol
Returns Entry type (:empty, :dir, :file, :root).
68 69 70 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 68 def type @type end |
#type_id ⇒ Integer
Returns 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
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
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
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
345 346 347 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 345 def /(name) @name_lookup&.[](name) end |
#<<(child) ⇒ Object
Add child entry
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_defaults ⇒ Object
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 |
#children ⇒ Array<Dirent>
Get all children
369 370 371 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 369 def children @children || [] end |
#dir? ⇒ Boolean
Check if entry is a directory
185 186 187 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 185 def dir? !file? end |
#each_child {|Dirent| ... } ⇒ Object
Iterate over children
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
199 200 201 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 199 def empty? @type == :empty end |
#entry_directory? ⇒ Boolean
15 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 15 def entry_directory? = dir? |
#entry_mtime ⇒ Object
17 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 17 def entry_mtime = modify_time |
#entry_name ⇒ Object
14 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 14 def entry_name = name |
#entry_size ⇒ Object
16 |
# File 'lib/omnizip/formats/ole/dirent.rb', line 16 def entry_size = nil |
#file? ⇒ Boolean
Check if entry is a file
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
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 |
#free ⇒ void
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 |
#inspect ⇒ String
Inspect dirent
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_lookup ⇒ Object
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
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 |
#pack ⇒ String
Pack dirent to 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 |
#read ⇒ String
Read file content
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
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
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 |