Class: Mapi::Pst
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
- BACKLINK_OFFSET =
0x1f8
- ITEM_COUNT_OFFSET_64 =
0x1e8
- LEVEL_INDICATOR_OFFSET_64 =
0x1eb
Instance Attribute Summary collapse
- #blocks ⇒ Array<BlockPtr> readonly
- #header ⇒ Header readonly
- #helper ⇒ Helper readonly
- #io ⇒ IO readonly
- #nodes ⇒ Array<NodePtr> readonly
- #special_folder_ids ⇒ Hash<Integer, Symbol> readonly
Class Method Summary collapse
- .make_property_set(property_list) ⇒ PropertySet
- .split_per(str, size, count) ⇒ Array<String>
-
.unpack(str, unpack_spec) ⇒ Array
unfortunately there is no Q analogue which is little endian only.
Instance Method Summary collapse
-
#block_from_id(id) ⇒ BlockPtr
most access to idx objects will use this function.
- #dump_debug_info ⇒ Object
-
#each {|message| ... } ⇒ void
Iterate all kind of items recursively stored in this MessageStore.
- #encrypted? ⇒ Boolean
-
#get_local_node_list_of_sub_block_to(sub_block_id, list) ⇒ Object
for debug.
-
#get_local_node_list_to(node_id, list) ⇒ Object
for debug.
-
#initialize(io, helper = nil) ⇒ Pst
constructor
A new instance of Pst.
- #inspect ⇒ Object
- #is64 ⇒ Boolean
-
#load_block_btree ⇒ Object
corresponds to * _pst_build_id_ptr.
-
#load_block_tree(offset, linku1, start_val) ⇒ Object
load the flat idx table, which maps ids to file ranges.
- #load_main_block_to(block_id, list) ⇒ Object
-
#load_node_btree ⇒ Object
corresponds to * _pst_build_desc_ptr * record_descriptor.
- #load_node_main_data_to(node_id, list) ⇒ Object
- #load_node_sub_data_to(node_id, local_node_id, list) ⇒ Object
-
#load_node_tree(offset, linku1, start_val) ⇒ Object
load the flat list of desc records recursively.
- #load_sub_block_to(sub_block_id, local_node_id, list) ⇒ Object
-
#load_xattrib ⇒ Object
corresponds to * pst_load_extended_attributes.
-
#name ⇒ String
Get this MessageStore’s display name.
-
#node_from_id(id) ⇒ NodePtr
as for idx.
-
#pst_parse_item(node) ⇒ Item
corresponds to * _pst_parse_item.
-
#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 ??.
-
#root ⇒ Item
Obtain a root item.
- #root_desc ⇒ NodePtr
- #root_item ⇒ Item
-
#warn(s) ⇒ Object
until i properly fix logging…
Constructor Details
#initialize(io, helper = nil) ⇒ Pst
Returns a new instance of Pst.
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
#blocks ⇒ Array<BlockPtr> (readonly)
295 296 297 |
# File 'lib/mapi/pst.rb', line 295 def blocks @blocks end |
#io ⇒ IO (readonly)
287 288 289 |
# File 'lib/mapi/pst.rb', line 287 def io @io end |
#nodes ⇒ Array<NodePtr> (readonly)
299 300 301 |
# File 'lib/mapi/pst.rb', line 299 def nodes @nodes end |
#special_folder_ids ⇒ Hash<Integer, Symbol> (readonly)
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
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>
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.
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
611 612 613 |
# File 'lib/mapi/pst.rb', line 611 def block_from_id id @block_from_id[id & ~1] end |
#dump_debug_info ⇒ Object
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.
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
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
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
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 |
#inspect ⇒ Object
1990 1991 1992 |
# File 'lib/mapi/pst.rb', line 1990 def inspect "#<Pst name=#{name.inspect} io=#{io.inspect}>" end |
#is64 ⇒ Boolean
658 659 660 |
# File 'lib/mapi/pst.rb', line 658 def is64 @header.version_2003? end |
#load_block_btree ⇒ Object
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
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_btree ⇒ Object
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
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
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
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_xattrib ⇒ Object
corresponds to
-
pst_load_extended_attributes
732 733 |
# File 'lib/mapi/pst.rb', line 732 def load_xattrib end |
#name ⇒ String
Get this MessageStore’s display name.
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
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
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 ??
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 |
#root ⇒ Item
Obtain a root item
1965 1966 1967 |
# File 'lib/mapi/pst.rb', line 1965 def root root_item end |
#root_desc ⇒ NodePtr
1950 1951 1952 |
# File 'lib/mapi/pst.rb', line 1950 def root_desc @nodes.first end |
#root_item ⇒ Item
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 |