Class: Mapi::Pst

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/mapi/pst.rb

Overview

Read Outlook’s pst file

Defined Under Namespace

Classes: Attachment, AttachmentTable, BlockParser, BlockPtr, CompressibleEncryption, FormatError, Header, Item, NodePtr, RawPropertyStore, RawPropertyStoreTable, Recipient, RecipientTable, TablePtr

Constant Summary collapse

ToTree =
Module.new
ITEM_COUNT_OFFSET =

more constants from libpst.c these relate to the index block

0x1f0
LEVEL_INDICATOR_OFFSET =
0x1f3
0x1f8
ITEM_COUNT_OFFSET_64 =
0x1e8
LEVEL_INDICATOR_OFFSET_64 =
0x1eb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io, helper = nil) ⇒ Pst

Returns a new instance of Pst.

Parameters:

  • io (IO)
  • helper (Helper, nil) (defaults to: nil)

Raises:



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/mapi/pst.rb', line 311

def initialize io, helper=nil
	# corresponds to
	# * pst_open
	# * pst_load_index

	@io = io
	io.pos = 0
	@helper = helper || Helper.new
	@header = Header.new io.read(Header::SIZE)

	# would prefer this to be in Header#validate, but it doesn't have the io size.
	# should perhaps downgrade this to just be a warning...
	raise FormatError, "header size field invalid (#{header.size} != #{io.size}}" unless header.size == io.size

	load_block_btree
	load_node_btree
	load_xattrib

	@special_folder_ids = {}
end

Instance Attribute Details

#blocksArray<BlockPtr> (readonly)

Returns:



295
296
297
# File 'lib/mapi/pst.rb', line 295

def blocks
  @blocks
end

#headerHeader (readonly)

Returns:



291
292
293
# File 'lib/mapi/pst.rb', line 291

def header
  @header
end

#helperHelper (readonly)

Returns:



307
308
309
# File 'lib/mapi/pst.rb', line 307

def helper
  @helper
end

#ioIO (readonly)

Returns:

  • (IO)


287
288
289
# File 'lib/mapi/pst.rb', line 287

def io
  @io
end

#nodesArray<NodePtr> (readonly)

Returns:



299
300
301
# File 'lib/mapi/pst.rb', line 299

def nodes
  @nodes
end

#special_folder_idsHash<Integer, Symbol> (readonly)

Returns:

  • (Hash<Integer, Symbol>)


303
304
305
# File 'lib/mapi/pst.rb', line 303

def special_folder_ids
  @special_folder_ids
end

Class Method Details

.make_property_set(property_list) ⇒ PropertySet

Parameters:

  • property_list (Array<Array(Integer, Integer, Object)>)

Returns:



1637
1638
1639
1640
1641
1642
# File 'lib/mapi/pst.rb', line 1637

def self.make_property_set property_list
	hash = property_list.inject({}) do |hash, (key, type, value)|
		hash.update PropertySet::Key.new(key) => value
	end
	PropertySet.new hash
end

.split_per(str, size, count) ⇒ Array<String>

Parameters:

  • str (String)
  • size (Integer)
  • count (Integer)

Returns:

  • (Array<String>)


114
115
116
117
118
119
# File 'lib/mapi/pst.rb', line 114

def self.split_per str, size, count
	count = str.length / size if count < 0
	list = []
	count.times {|i| list << str[size * i, size]}
	list
end

.unpack(str, unpack_spec) ⇒ Array

unfortunately there is no Q analogue which is little endian only. this translates T as an unsigned quad word, little endian byte order, to not pollute the rest of the code.

didn’t want to override String#unpack, cause its too hacky, and incomplete.

Parameters:

  • str (String)
  • unpack_spec (String)

Returns:

  • (Array)


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
# File 'lib/mapi/pst.rb', line 81

def self.unpack str, unpack_spec
	return str.unpack(unpack_spec) unless unpack_spec['T']
	@unpack_cache ||= {}
	t_offsets, new_spec = @unpack_cache[unpack_spec]
	unless t_offsets
		t_offsets = []
		offset = 0
		new_spec = ''
		unpack_spec.scan(/([^\d])_?(\*|\d+)?/o) do
			num_elems = $1.downcase == 'a' ? 1 : ($2 || 1).to_i
			if $1 == 'T'
				num_elems.times { |i| t_offsets << offset + i }
				new_spec << "V#{num_elems * 2}"
			else
				new_spec << $~[0]
			end
			offset += num_elems
		end
		@unpack_cache[unpack_spec] = [t_offsets, new_spec]
	end
	a = str.unpack(new_spec)
	t_offsets.each do |offset|
		low, high = a[offset, 2]
		a[offset, 2] = low && high ? low + (high << 32) : nil
	end
	a
end

Instance Method Details

#block_from_id(id) ⇒ BlockPtr

most access to idx objects will use this function

corresponds to

  • _pst_getID

Parameters:

  • id (Integer)

Returns:



611
612
613
# File 'lib/mapi/pst.rb', line 611

def block_from_id id
	@block_from_id[id & ~1]
end

#dump_debug_infoObject



1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
# File 'lib/mapi/pst.rb', line 1863

def dump_debug_info
	puts "* pst header"
	p header

=begin
Looking at the output of this, for blank-o1997.pst, i see this part:
...
- (26624,516) desc block data (overlap of 4 bytes)
- (27136,516) desc block data (gap of 508 bytes)
- (28160,516) desc block data (gap of 2620 bytes)
...

which confirms my belief that the block size for idx and desc is more likely 512
=end
	if 0 + 0 == 0
		puts '* file range usage'
		file_ranges =
			# these 3 things, should account for most of the data in the file.
			[[0, Header::SIZE, 'pst file header']] +
			@block_offsets.map { |offset| [offset, BlockPtr::BLOCK_SIZE, 'block data'] } +
			@node_offsets.map { |offset| [offset, NodePtr::BLOCK_SIZE, 'node data'] } +
			@blocks.map { |idx| [idx.offset, idx.size, 'idx id=0x%x (%s)' % [idx.id, idx.type]] }
		(file_ranges.sort_by { |idx| idx.first } + [nil]).to_enum(:each_cons, 2).each do |(offset, size, name), next_record|
			# i think there is a padding of the size out to 64 bytes
			# which is equivalent to padding out the final offset, because i think the offset is 
			# similarly oriented
			pad_amount = 64
			warn 'i am wrong about the offset padding' if offset % pad_amount != 0
			# so, assuming i'm not wrong about that, then we can calculate how much padding is needed.
			pad = pad_amount - (size % pad_amount)
			pad = 0 if pad == pad_amount
			gap = next_record ? next_record.first - (offset + size + pad) : 0
			extra = case gap <=> 0
				when -1; ["overlap of #{gap.abs} bytes)"]
				when  0; []
				when +1; ["gap of #{gap} bytes"]
			end
			# how about we check that padding
			@io.pos = offset + size
			pad_bytes = @io.read(pad)
			extra += ["padding not all zero"] unless pad_bytes == 0.chr * pad
			puts "- #{offset}:#{size}+#{pad} #{name.inspect}" + (extra.empty? ? '' : ' [' + extra * ', ' + ']')
		end
	end

	# i think the idea of the idx, and indeed the idx2, is just to be able to
	# refer to data indirectly, which means it can get moved around, and you just update
	# the idx table. it is simply a list of file offsets and sizes.
	# not sure i get how id2 plays into it though....
	# the sizes seem to be all even. is that a co-incidence? and the ids are all even. that
	# seems to be related to something else (see the (id & 2) == 1 stuff)
	puts '* idx entries'
	@blocks.each { |idx| puts "- #{idx.inspect}" }

	# if you look at the desc tree, you notice a few things:
	# 1. there is a desc that seems to be the parent of all the folders, messages etc.
	#    it is the one whose parent is itself.
	#    one of its children is referenced as the subtree_entryid of the first desc item,
	#    the root.
	# 2. typically only 2 types of desc records have idx2_id != 0. messages themselves,
	#    and the desc with id = 0x61 - the xattrib container. everything else uses the
	#    regular ids to find its data. i think it should be reframed as small blocks and
	#    big blocks, but i'll look into it more.
	#
	# idx_id and idx2_id are for getting to the data. desc_id and parent_desc_id just define
	# the parent <-> child relationship, and the desc_ids are how the items are referred to in
	# entryids.
	# note that these aren't unique! eg for 0, 4 etc. i expect these'd never change, as the ids
	# are stored in entryids. whereas the idx and idx2 could be a bit more volatile.
	puts '* node tree'
	# make a dummy root hold everything just for convenience
	root = NodePtr.new ''
	def root.inspect; "#<Pst::Root>"; end
	root.children.replace @orphans
	# this still loads the whole thing as a string for gsub. should use directo output io
	# version.
	puts root.to_tree.gsub(/, (parent_node_id|idx2_id)=0x0(?!\d)/, '')

	# this is fairly easy to understand, its just an attempt to display the pst items in a tree form
	# which resembles what you'd see in outlook.
	puts '* item tree'
	# now streams directly
	root_item.to_tree STDOUT
end

#each {|message| ... } ⇒ void

This method returns an undefined value.

Iterate all kind of items recursively stored in this MessageStore.

Yields:

  • (message)

Yield Parameters:



1977
1978
1979
1980
1981
# File 'lib/mapi/pst.rb', line 1977

def each(&block)
	root = self.root
	block[root]
	root.each_recursive(&block)
end

#encrypted?Boolean

Returns:

  • (Boolean)


334
335
336
# File 'lib/mapi/pst.rb', line 334

def encrypted?
	@header.encrypted?
end

#get_local_node_list_of_sub_block_to(sub_block_id, list) ⇒ Object

for debug

Parameters:

  • sub_block_id (String)
  • list (Array<String>)


788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
# File 'lib/mapi/pst.rb', line 788

def get_local_node_list_of_sub_block_to sub_block_id, list
	return if sub_block_id == 0

	sub_block = block_from_id sub_block_id
	p ["WALK",sub_block_id,sub_block]
	raise 'must not be data' if sub_block.data?

	# SLBLOCK or SIBLOCK
	data = sub_block.read

	btype = data[0].ord
	raise 'btype != 2' if btype != 2

	level = data[1].ord
	case level
	when 0 # SLBLOCK
		count = data[2, 2].unpack("v").first
		count.times do |i|
			sl_node_id, sl_block_id, sl_sub_block_id = (
				is64 ? Pst.unpack(data[(is64 ? 8 : 4) + 24 * i, 24], "T3") : data[(is64 ? 8 : 4) + 12 * i, 12].unpack("V3")
			)

			list << (sl_node_id & 0xffffffff)
			
			get_local_node_list_of_sub_block_to sl_sub_block_id, list
		end
	when 1 # SIBLOCK
		count = data[2, 2].unpack("v").first
		count.times do |i|
			si_node_id, si_block_id = (
				is64 ? Pst.unpack(data[(is64 ? 8 : 4) + 16 * i, 16], "T2") : data[(is64 ? 8 : 4) + 8 * i, 8].unpack("V2")
			)

			list << (si_node_id & 0xffffffff)
		end
	else
		raise 'level unk'
	end
end

#get_local_node_list_to(node_id, list) ⇒ Object

for debug

Parameters:

  • node_id (String)
  • list (Array<String>)


778
779
780
781
# File 'lib/mapi/pst.rb', line 778

def get_local_node_list_to node_id, list
	node = node_from_id node_id
	get_local_node_list_of_sub_block_to node.sub_block_id, list
end

#inspectObject



1990
1991
1992
# File 'lib/mapi/pst.rb', line 1990

def inspect
	"#<Pst name=#{name.inspect} io=#{io.inspect}>"
end

#is64Boolean

Returns:

  • (Boolean)


658
659
660
# File 'lib/mapi/pst.rb', line 658

def is64
	@header.version_2003?
end

#load_block_btreeObject

corresponds to

  • _pst_build_id_ptr



540
541
542
543
544
545
546
547
548
549
550
551
552
# File 'lib/mapi/pst.rb', line 540

def load_block_btree
	@blocks = []
	@block_offsets = []
	load_block_tree header.block_btree, header.block_btree_count, 0

	# we'll typically be accessing by id, so create a hash as a lookup cache
	@block_from_id = {}
		@blocks.each do |idx|
		id = idx.id & ~1
		warn "there are duplicate idx records with id #{id}" if @block_from_id[id]
		@block_from_id[id] = idx
	end
end

#load_block_tree(offset, linku1, start_val) ⇒ Object

load the flat idx table, which maps ids to file ranges. this is the recursive helper

corresponds to

  • _pst_build_id_ptr



560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
# File 'lib/mapi/pst.rb', line 560

def load_block_tree offset, linku1, start_val
	@block_offsets << offset

	#_pst_read_block_size(pf, offset, BLOCK_SIZE, &buf, 0, 0) < BLOCK_SIZE)
	buf = pst_read_block_size offset, BlockPtr::BLOCK_SIZE, false

	item_count = buf[is64 ? ITEM_COUNT_OFFSET_64 : ITEM_COUNT_OFFSET].ord
	level = buf[is64 ? LEVEL_INDICATOR_OFFSET_64 : LEVEL_INDICATOR_OFFSET].ord
	count_max = is64 ? BlockPtr::COUNT_MAX64 : BlockPtr::COUNT_MAX32
	raise "have too many active items in index (#{item_count})" if item_count > count_max

	this_node_id = is64 ? Pst.unpack(buf[BACKLINK_OFFSET, 8], "T").first : buf[BACKLINK_OFFSET, 4].unpack("V").first
	raise 'blah 1' unless this_node_id == linku1

	if level == 0
		# leaf pointers
		size = is64 ? BlockPtr::SIZE64 : BlockPtr::SIZE32

		# split the data into item_count index objects
		Pst.split_per(buf, size, item_count).each_with_index do |data, i|
			idx = BlockPtr.new data, is64
			# first entry
			raise 'blah 3' if i == 0 and start_val != 0 and idx.id != start_val
			idx.pst = self
			# this shouldn't really happen i'd imagine
			raise "OHNO" if idx.id == 0
			@blocks << idx
		end
	else
		# node pointers
		size = is64 ? TablePtr::SIZE64 : TablePtr::SIZE32
		# split the data into item_count table pointers
		Pst.split_per(buf, size, item_count).each_with_index do |data, i|
			table = TablePtr.new data, is64
			# for the first value, we expect the start to be equal
			raise 'blah 3' if i == 0 and start_val != 0 and table.start != start_val
			# this shouldn't really happen i'd imagine
			raise "OHNO" if table.start == 0
			load_block_tree table.offset, table.u1, table.start
		end
	end
end

#load_main_block_to(block_id, list) ⇒ Object

Parameters:

  • block_id (Integer)
  • list (Array<String>)


885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
# File 'lib/mapi/pst.rb', line 885

def load_main_block_to block_id, list
	return if block_id == 0

	block = block_from_id block_id

	if block.data?
		# this is real data we want
		list << block.read.force_encoding("BINARY")
		return
	end

	# XBLOCK or XXBLOCK
	data = block.read

	btype = data[0].ord
	raise 'btype must be 1' if btype != 1

	level = data[1].ord
	case level
	when 1, 2
		count, num_bytes = data[2, 6].unpack("vV")

		items = (
			is64 ? Pst.unpack(data[8, 8 * count], "T#{count}") : data[8, 4 * count].unpack("V#{count}")
		)
		items.each { |block_id|
			load_main_block_to block_id, list
		}
	else
		raise 'level unk'
	end
end

#load_node_btreeObject

corresponds to

  • _pst_build_desc_ptr

  • record_descriptor



620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
# File 'lib/mapi/pst.rb', line 620

def load_node_btree
	@nodes = []
	@node_offsets = []
	load_node_tree header.node_btree, header.node_btree_count, 0x21

	# first create a lookup cache
	@node_from_id = {}
		@nodes.each do |node|
		node.pst = self
		warn "there are duplicate desc records with id #{node.node_id}" if @node_from_id[node.node_id]
		@node_from_id[node.node_id] = node
	end

	# now turn the flat list of loaded desc records into a tree

	# well, they have no parent, so they're more like, the toplevel descs.
	@orphans = []
	# now assign each node to the parents child array, putting the orphans in the above
	@nodes.each do |node|
		parent = @node_from_id[node.parent_node_id]
		# note, besides this, its possible to create other circular structures.
		if parent == node
			# this actually happens usually, for the root_item it appears.
			#warn "desc record's parent is itself (#{desc.inspect})"
		# maybe add some more checks in here for circular structures
		elsif parent
			parent.children << node
			next
		end
		@orphans << node
	end

	# maybe change this to some sort of sane-ness check. orphans are expected
#		warn "have #{@orphans.length} orphan desc record(s)." unless @orphans.empty?
end

#load_node_main_data_to(node_id, list) ⇒ Object

Parameters:

  • node_id (Integer)
  • list (Array<String>)


756
757
758
759
760
# File 'lib/mapi/pst.rb', line 756

def load_node_main_data_to node_id, list
	raise 'node_is must be Integer' unless Integer === node_id
	node = node_from_id node_id
	load_main_block_to node.block_id, list
end

#load_node_sub_data_to(node_id, local_node_id, list) ⇒ Object

Parameters:

  • node_id (Integer)
  • local_node_id (Integer)
  • list (Array<String>)


766
767
768
769
770
771
# File 'lib/mapi/pst.rb', line 766

def load_node_sub_data_to node_id, local_node_id, list
	raise 'node_is must be Integer' unless Integer === node_id
	raise 'local_node_id must be Integer' unless Integer === local_node_id
	node = node_from_id node_id
	load_sub_block_to node.sub_block_id, local_node_id, list
end

#load_node_tree(offset, linku1, start_val) ⇒ Object

load the flat list of desc records recursively

corresponds to

  • _pst_build_desc_ptr

  • record_descriptor



669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
# File 'lib/mapi/pst.rb', line 669

def load_node_tree offset, linku1, start_val
	@node_offsets << offset
	
	buf = pst_read_block_size offset, NodePtr::BLOCK_SIZE, false
	item_count = buf[is64 ? ITEM_COUNT_OFFSET_64 : ITEM_COUNT_OFFSET].ord
	level = buf[is64 ? LEVEL_INDICATOR_OFFSET_64 : LEVEL_INDICATOR_OFFSET].ord

	# not real desc
	this_node_id = is64 ? Pst.unpack(buf[BACKLINK_OFFSET, 8], "T").first : buf[BACKLINK_OFFSET, 4].unpack("V").first
	raise 'blah 1' unless this_node_id == linku1

	if level == 0
		# leaf pointers
		size = is64 ? NodePtr::SIZE64 : NodePtr::SIZE32
		count_max = is64 ? NodePtr::COUNT_MAX64 : NodePtr::COUNT_MAX32

		raise "have too many active items in index (#{item_count})" if item_count > count_max
		# split the data into item_count desc objects
		Pst.split_per(buf, size, item_count).each_with_index do |data, i|
			node = NodePtr.new data, is64
			# first entry
			raise 'blah 3' if i == 0 and start_val != 0 and node.node_id != start_val
			# this shouldn't really happen i'd imagine
			break if node.node_id == 0
			@nodes << node
		end
	else
		# node pointers
		size = is64 ? TablePtr::SIZE64 : TablePtr::SIZE32
		count_max = is64 ? BlockPtr::COUNT_MAX64 : BlockPtr::COUNT_MAX32

		raise "have too many active items in index (#{item_count})" if item_count > count_max
		# split the data into item_count table pointers
		Pst.split_per(buf, size, item_count).each_with_index do |data, i|
			table = TablePtr.new data, is64
			# for the first value, we expect the start to be equal note that ids -1, so even for the
			# first we expect it to be equal. thats the 0x21 (dec 33) desc record. this means we assert
			# that the first desc record is always 33...
			raise 'blah 3' if i == 0 and start_val != -1 and table.start != start_val
			# this shouldn't really happen i'd imagine
			break if table.start == 0
			load_node_tree table.offset, table.u1, table.start
		end
	end
end

#load_sub_block_to(sub_block_id, local_node_id, list) ⇒ Object

Parameters:

  • sub_block_id (Integer)
  • local_node_id (Integer)
  • list (Array<String>)


832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
# File 'lib/mapi/pst.rb', line 832

def load_sub_block_to sub_block_id, local_node_id, list
	raise 'sub_block_id must be Integer' unless Integer === sub_block_id
	return if sub_block_id == 0

	sub_block = block_from_id sub_block_id
	raise 'must not be data' if sub_block.data?

	# SLBLOCK or SIBLOCK
	data = sub_block.read

	btype = data[0].ord
	raise 'btype != 2' if btype != 2

	level = data[1].ord
	case level
	when 0 # SLBLOCK
		count = data[2, 2].unpack("v").first
		count.times do |i|
			sl_node_id, sl_block_id, sl_sub_block_id = (
				is64 ? Pst.unpack(data[(is64 ? 8 : 4) + 24 * i, 24], "T3") : data[(is64 ? 8 : 4) + 12 * i, 12].unpack("V3")
			)

			sl_node_id &= 0xffffffff
			
			if sl_node_id == local_node_id
				load_main_block_to sl_block_id, list
			end

			load_sub_block_to sl_sub_block_id, local_node_id, list
		end
	when 1 # SIBLOCK
		count = data[2, 2].unpack("v").first
		count.times do |i|
			si_node_id, si_block_id = (
				is64 ? Pst.unpack(data[(is64 ? 8 : 4) + 16 * i, 16], "T2") : data[(is64 ? 8 : 4) + 8 * i, 8].unpack("V2")
			)

			si_node_id &= 0xffffffff

			if si_node_id == local_node_id
				si_block = block_from_id si_block_id
				raise 'must be data' unless si_block.data?
				list << si_block.read.force_encoding("BINARY")
			end
		end
	else
		raise 'level unk'
	end
end

#load_xattribObject

corresponds to

  • pst_load_extended_attributes



732
733
# File 'lib/mapi/pst.rb', line 732

def load_xattrib
end

#nameString

Get this MessageStore’s display name.

Returns:

  • (String)


1986
1987
1988
# File 'lib/mapi/pst.rb', line 1986

def name
	@name ||= root_item.props.display_name
end

#node_from_id(id) ⇒ NodePtr

as for idx

corresponds to:

  • _pst_getDptr

Parameters:

  • id (Integer)

Returns:



724
725
726
# File 'lib/mapi/pst.rb', line 724

def node_from_id id
	@node_from_id[id]
end

#pst_parse_item(node) ⇒ Item

corresponds to

  • _pst_parse_item

Parameters:

Returns:



1853
1854
1855
# File 'lib/mapi/pst.rb', line 1853

def pst_parse_item node
	Item.new node, RawPropertyStore.new(node).to_a
end

#pst_read_block_size(offset, size, decrypt = true) ⇒ String

corresponds to:

  • _pst_read_block_size

  • _pst_read_block ??

  • _pst_ff_getIDblock_dec ??

  • _pst_ff_getIDblock ??

Parameters:

  • offset (Integer)
  • size (Integer)
  • decrypt (Boolean) (defaults to: true)

Returns:

  • (String)


746
747
748
749
750
751
# File 'lib/mapi/pst.rb', line 746

def pst_read_block_size offset, size, decrypt=true
	io.seek offset
	buf = io.read size
	warn "tried to read #{size} bytes but only got #{buf.length}" if buf.length != size
	encrypted? && decrypt ? CompressibleEncryption.decrypt(buf) : buf
end

#rootItem

Obtain a root item

Returns:



1965
1966
1967
# File 'lib/mapi/pst.rb', line 1965

def root
	root_item
end

#root_descNodePtr

Returns:



1950
1951
1952
# File 'lib/mapi/pst.rb', line 1950

def root_desc
	@nodes.first
end

#root_itemItem

Returns:



1956
1957
1958
1959
1960
# File 'lib/mapi/pst.rb', line 1956

def root_item
	item = pst_parse_item root_desc
	item.type = :root
	item
end

#warn(s) ⇒ Object

until i properly fix logging…



341
342
343
# File 'lib/mapi/pst.rb', line 341

def warn s
	Mapi::Log.warn s
end