├── .gitignore
├── .vscode
├── launch.json
└── tasks.json
├── LICENSE.txt
├── PSTParse
├── ListsTablesPropertiesLayer
│ ├── BTH.cs
│ ├── BTHDataEntry.cs
│ ├── BTHDataNode.cs
│ ├── BTHHEADER.cs
│ ├── BTHIndexEntry.cs
│ ├── BTHIndexNode.cs
│ ├── EntryID.cs
│ ├── ExchangeProperty.cs
│ ├── HID.cs
│ ├── HN.cs
│ ├── HNBITMAPHDR.cs
│ ├── HNBlock.cs
│ ├── HNDataDTO.cs
│ ├── HNHDR.cs
│ ├── HNPAGEHDR.cs
│ ├── HNPAGEMAP.cs
│ ├── HeapNodeBO.cs
│ ├── PropertyContext.cs
│ ├── TCINFOHEADER.cs
│ ├── TCOLDESC.cs
│ ├── TCRowMatrix.cs
│ ├── TCRowMatrixData.cs
│ ├── TableContext.cs
│ └── unused
│ │ ├── BTHDataRecord.cs
│ │ ├── BTHIndexAllocationRecords.cs
│ │ ├── HNID.cs
│ │ ├── MVPropVarBase.cs
│ │ └── PCBTHRecord.cs
├── MessageLayer
│ ├── Attachment.cs
│ ├── IPMItem.cs
│ ├── MailFolder.cs
│ ├── MailStore.cs
│ ├── Message.cs
│ ├── MessageProperty.cs
│ ├── Recipient.cs
│ ├── Recipients.cs
│ └── unused
│ │ ├── MessagePropertyTypes.cs
│ │ ├── NAMEID.cs
│ │ ├── NamedProperty.cs
│ │ ├── NamedToPropertyLookup.cs
│ │ └── PropType.cs
├── NodeDatabaseLayer
│ ├── BBTENTRY.cs
│ ├── BREF.cs
│ ├── BTENTRY.cs
│ ├── BTPAGEENTRY.cs
│ ├── BTPage.cs
│ ├── BlockBO.cs
│ ├── BlockDataDTO.cs
│ ├── BlockTrailer.cs
│ ├── DatatEncoder.cs
│ ├── IBLOCK.cs
│ ├── NBTENTRY.cs
│ ├── NID.cs
│ ├── NodeDataDTO.cs
│ ├── PSTBTree.cs
│ ├── PageTrailer.cs
│ ├── SIBLOCK.cs
│ ├── SIENTRY.cs
│ ├── SLBLOCK.cs
│ ├── SLENTRY.cs
│ ├── SpecialNIDs.cs
│ ├── XBLOCK.cs
│ ├── XXBLOCK.cs
│ └── unused
│ │ ├── BID.cs
│ │ ├── BlockFactory.cs
│ │ ├── NodeBTree.cs
│ │ ├── PSTBTreeNode.cs
│ │ └── SubNodeDataDTO.cs
├── PSTEnums.cs
├── PSTFile.cs
├── PSTHeader.cs
├── PSTParse.csproj
├── PSTRoot.cs
└── Utilities
│ ├── ArrayUtilities.cs
│ ├── CRC32.cs
│ ├── RtfDecompressor.cs
│ └── Utilities.cs
├── PSTParseApp.sln
├── PSTParseApp
├── PSTParseApp.csproj
└── Program.cs
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | #customs
2 | db.lock
3 | storage.ide
4 | storage.ide-shm
5 | storage.ide-wal
6 | .dtbcache
7 | *.nupkg
8 | *.pst
9 |
10 | #OS junk files
11 | [Tt]humbs.db
12 | *.DS_Store
13 |
14 | #Visual Studio files
15 | *.[Oo]bj
16 | *.user
17 | *.aps
18 | *.pch
19 | *.vspscc
20 | *.vssscc
21 | *_i.c
22 | *_p.c
23 | *.ncb
24 | *.suo
25 | *.tlb
26 | *.tlh
27 | *.bak
28 | *.[Cc]ache
29 | *.ilk
30 | *.log
31 | *.lib
32 | *.sbr
33 | *.sdf
34 | *.opensdf
35 | *.unsuccessfulbuild
36 | ipch/
37 | obj/
38 | [Bb]in
39 | [Dd]ebug*/
40 | [Rr]elease*/
41 | Ankh.NoLoad
42 |
43 | #MonoDevelop
44 | *.pidb
45 | *.userprefs
46 |
47 | #Tooling
48 | _ReSharper*/
49 | *.resharper
50 | [Tt]est[Rr]esult*
51 | *.sass-cache
52 |
53 | #Project files
54 | [Bb]uild/
55 |
56 | #Subversion files
57 | .svn
58 |
59 | # Office Temp Files
60 | ~$*
61 |
62 | #NuGet
63 | packages/
64 |
65 | #ncrunch
66 | *ncrunch*
67 | *crunch*.local.xml
68 |
69 | # visual studio database projects
70 | *.dbmdl
71 |
72 | #Test files
73 | *.testsettings
74 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": ".NET Core Launch (console)",
6 | "type": "coreclr",
7 | "request": "launch",
8 | "preLaunchTask": "build",
9 | "program": "${workspaceFolder}/PSTParseApp/bin/Debug/net60/PSTParseApp.dll",
10 | "args": [],
11 | "cwd": "${workspaceFolder}",
12 | "console": "internalConsole",
13 | "stopAtEntry": false
14 | },
15 | {
16 | "name": ".NET Core Attach",
17 | "type": "coreclr",
18 | "request": "attach"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/PSTParseApp/PSTParseApp.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/PSTParseApp/PSTParseApp.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "${workspaceFolder}/PSTParseApp/PSTParseApp.csproj",
36 | "/property:GenerateFullPaths=true",
37 | "/consoleloggerparameters:NoSummary"
38 | ],
39 | "problemMatcher": "$msCompile"
40 | }
41 | ]
42 | }
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 sharpiro
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the Software), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/BTH.cs:
--------------------------------------------------------------------------------
1 | using PSTParse.MessageLayer;
2 | using PSTParse.Utilities;
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | namespace PSTParse.ListsTablesPropertiesLayer
7 | {
8 | ///
9 | /// Btree on Heap (BTH)
10 | ///
11 | public class BTH
12 | {
13 | public HN HeapNode { get; set; }
14 | public BTHHEADER Header { get; set; }
15 | public BTHIndexNode Root { get; set; }
16 | public int CurrentLevel { get; set; }
17 | public Dictionary Properties { get; set; }
18 |
19 | public BTH(HN heapNode, HID userRoot = null)
20 | {
21 | HeapNode = heapNode;
22 |
23 | var bthHeaderHID = userRoot ?? heapNode.HeapNodes[0].Header.UserRoot;
24 | Header = new BTHHEADER(HeapNodeBO.GetHNHIDBytes(heapNode, bthHeaderHID));
25 | Root = new BTHIndexNode(Header.BTreeRoot, this, (int)Header.NumLevels);
26 |
27 | Properties = new Dictionary(new ArrayUtilities.ByteArrayComparer());
28 |
29 | var stack = new Stack();
30 | stack.Push(Root);
31 | while (stack.Count > 0)
32 | {
33 | var cur = stack.Pop();
34 |
35 | try
36 | {
37 | if (cur.Data != null)
38 | foreach (var entry in cur.Data.DataEntries)
39 | Properties.Add(entry.Key, entry);
40 | }
41 | catch (Exception ex)
42 | {
43 | throw new Exception("Cannot display this view. Failed to create all properties in this location", ex);
44 | }
45 |
46 |
47 | if (cur.Children != null)
48 | foreach (var child in cur.Children)
49 | stack.Push(child);
50 |
51 | }
52 | }
53 |
54 | public HNDataDTO GetHIDBytes(HID hid)
55 | {
56 | return HeapNode.GetHIDBytes(hid);
57 | }
58 |
59 | // todo: this might be getting called twice
60 | public Dictionary GetExchangeProperties()
61 | {
62 | var ret = new Dictionary();
63 |
64 | var stack = new Stack();
65 | stack.Push(Root);
66 | while (stack.Count > 0)
67 | {
68 | var cur = stack.Pop();
69 |
70 | if (cur.Data != null)
71 | foreach (var entry in cur.Data.DataEntries)
72 | {
73 | var curKey = BitConverter.ToUInt16(entry.Key, 0);
74 | int i = 0;
75 | if (curKey == 0x02)
76 | i++;
77 | ret.Add((MessageProperty)curKey, new ExchangeProperty(entry, this));
78 | }
79 |
80 | if (cur.Children != null)
81 | foreach (var child in cur.Children)
82 | stack.Push(child);
83 |
84 | }
85 |
86 | return ret;
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/BTHDataEntry.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using PSTParse.NodeDatabaseLayer;
6 | using PSTParse.Utilities;
7 |
8 | namespace PSTParse.ListsTablesPropertiesLayer
9 | {
10 | public class BTHDataEntry
11 | {
12 | public byte[] Key;
13 | public byte[] Data;
14 |
15 | public ulong DataOffset;
16 | public ulong DataBlockOffset;
17 | public BTH ParentTree;
18 |
19 | public BTHDataEntry(HNDataDTO data, int offset, BTH tree)
20 | {
21 | this.Key = data.Data.RangeSubset(offset, (int) tree.Header.KeySize);
22 | //this.Key = bytes.Skip(offset).Take((int)tree.Header.KeySize).ToArray();
23 | var temp = offset + (int) tree.Header.KeySize;
24 | this.Data = data.Data.RangeSubset(temp, (int)tree.Header.DataSize);
25 | this.DataOffset = (ulong) offset + tree.Header.KeySize;
26 | this.ParentTree = tree;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/BTHDataNode.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace PSTParse.ListsTablesPropertiesLayer
4 | {
5 | public class BTHDataNode
6 | {
7 | public List DataEntries { get; }
8 | public HNDataDTO Data { get; }
9 | public BTH Tree { get; }
10 |
11 | public BTHDataNode(HID hid, BTH tree)
12 | {
13 | Tree = tree;
14 |
15 | var bytes = tree.GetHIDBytes(hid);
16 | Data = bytes;
17 | DataEntries = new List();
18 | for (int i = 0; i < bytes.Data.Length; i += (int)(tree.Header.KeySize + tree.Header.DataSize))
19 | DataEntries.Add(new BTHDataEntry(bytes, i, tree));
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/BTHHEADER.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using PSTParse.NodeDatabaseLayer;
6 | using PSTParse.Utilities;
7 |
8 | namespace PSTParse.ListsTablesPropertiesLayer
9 | {
10 | public class BTHHEADER
11 | {
12 | public uint BType;
13 | //must be 2,4,8,16
14 | public uint KeySize;
15 | //must be >0 <=32
16 | public uint DataSize;
17 | public uint NumLevels;
18 | public HID BTreeRoot;
19 |
20 | public BTHHEADER(HNDataDTO block)
21 | {
22 | var bytes = block.Data;
23 | this.BType = bytes[0];
24 | this.KeySize = bytes[1];
25 | this.DataSize = bytes[2];
26 | this.NumLevels = bytes[3];
27 | this.BTreeRoot = new HID(bytes.RangeSubset(4, 4));
28 |
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/BTHIndexEntry.cs:
--------------------------------------------------------------------------------
1 | using PSTParse.Utilities;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | namespace PSTParse.ListsTablesPropertiesLayer
8 | {
9 | public class BTHIndexEntry
10 | {
11 | public byte[] Key;
12 | public HID HID;
13 |
14 | public BTHIndexEntry(byte[] bytes, int offset, BTHHEADER header)
15 | {
16 | this.Key = bytes.RangeSubset(offset,(int)header.KeySize);
17 | var temp = offset + (int) header.KeySize;
18 | this.HID = new HID(bytes.RangeSubset(temp, 4));
19 |
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/BTHIndexNode.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace PSTParse.ListsTablesPropertiesLayer
4 | {
5 | public class BTHIndexNode
6 | {
7 | public HID HID;
8 | public int Level;
9 |
10 | public List Entries;
11 | public List Children;
12 | public BTHDataNode Data;
13 |
14 | public BTHIndexNode(HID hid, BTH tree, int level)
15 | {
16 | this.Level = level;
17 | this.HID = hid;
18 | if (hid.hidBlockIndex == 0 && hid.hidIndex == 0)
19 | return;
20 |
21 | this.Entries = new List();
22 |
23 | if (level == 0)
24 | {
25 | this.Data = new BTHDataNode(hid, tree);
26 | /*
27 | for (int i = 0; i < bytes.Length; i += (int)tree.Header.KeySize + 4)
28 | this.Entries.Add(new BTHIndexEntry(bytes, i, tree.Header));
29 | this.DataChildren = new List();
30 | foreach(var entry in this.Entries)
31 | this.DataChildren.Add(new BTHDataNode(entry.HID, tree));*/
32 | }
33 | else
34 | {
35 | var bytes = tree.GetHIDBytes(hid);
36 | for (int i = 0; i < bytes.Data.Length; i += (int)tree.Header.KeySize + 4)
37 | this.Entries.Add(new BTHIndexEntry(bytes.Data, i, tree.Header));
38 | this.Children = new List();
39 | foreach (var entry in this.Entries)
40 | this.Children.Add(new BTHIndexNode(entry.HID, tree, level - 1));
41 | }
42 |
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/EntryID.cs:
--------------------------------------------------------------------------------
1 | using PSTParse.Utilities;
2 | using System;
3 | using System.IO;
4 |
5 | namespace PSTParse.ListsTablesPropertiesLayer
6 | {
7 | public class EntryID
8 | {
9 | public uint Flags { get; set; }
10 | public byte[] PSTUID { get; set; }
11 | public ulong NID { get; set; }
12 |
13 | public EntryID(byte[] bytes, int offset = 0)
14 | {
15 | if (bytes.Length == 0)
16 | {
17 | throw new InvalidDataException("The entry id was invalid, try running a PST repair");
18 | }
19 |
20 | Flags = BitConverter.ToUInt32(bytes, offset);
21 | PSTUID = bytes.RangeSubset(4+offset, 16);
22 | NID = BitConverter.ToUInt32(bytes, offset + 20);
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/ExchangeProperty.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using PSTParse.MessageLayer;
4 | using PSTParse.NodeDatabaseLayer;
5 | using PSTParse.Utilities;
6 |
7 | namespace PSTParse.ListsTablesPropertiesLayer
8 | {
9 | public class ExchangeProperty
10 | {
11 | public enum PropType : uint
12 | {
13 | Unspecified = 0x0000,
14 | NullType = 0x0001,
15 | Integer16 = 0x0002,
16 | Integer32 = 0x0003,
17 | Floating32 = 0x0004,
18 | Floating64 = 0x0005,
19 | Currency = 0x0006,
20 | FloatingTime = 0x0007,
21 | ErrorCode = 0x000A,
22 | Boolean = 0x000B,
23 | ObjectType = 0x000D,
24 | Integer64 = 0x0014,
25 | String = 0x001F,
26 | String8 = 0x001E,
27 | Time = 0x0040,
28 | Guid = 0x0048,
29 | ServerId = 0x00FB,
30 | Restriction = 0x00FD,
31 | RuleAction = 0x00FE,
32 | Binary = 0x0102,
33 | MultipleInteger16 = 0x1002,
34 | MultipleInteger32 = 0x1003,
35 | MultipleFloating32 = 0x1004,
36 | MultipleFloating64 = 0x1005,
37 | MultipleCurrency = 0x1006,
38 | MultipleFloatingTime = 0x1007,
39 | MultipleInteger64 = 0x1014,
40 | MultipleString = 0x101F,
41 | MultipleString8 = 0x101E,
42 | MultipleTime = 0x1040,
43 | MultipleGuid = 0x1048,
44 | MultipleBinary = 0x1102,
45 | }
46 |
47 | public static Dictionary
48 | PropertyLookupByTypeID = new Dictionary
49 |
50 | {
51 | {(PropType)0x0002,new ExchangeProperty{ByteCount = 2,Type = (PropType)0x0002,MultiValue = false, Variable = false}},
52 | {(PropType)0x0003,new ExchangeProperty{ByteCount = 4,Type = (PropType)0x0003,MultiValue = false, Variable = false}},
53 | {(PropType)0x0004,new ExchangeProperty{ByteCount = 4,Type = (PropType)0x0004,MultiValue = false, Variable = false}},
54 | {(PropType)0x0005,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x0005,MultiValue = false, Variable = false}},
55 | {(PropType)0x0006,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x0006,MultiValue = false, Variable = false}},
56 | {(PropType)0x0007,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x0007,MultiValue = false, Variable = false}},
57 | {(PropType)0x000A,new ExchangeProperty{ByteCount = 4,Type = (PropType)0x000A,MultiValue = false, Variable = false}},
58 | {(PropType)0x000B,new ExchangeProperty{ByteCount = 1,Type = (PropType)0x000B,MultiValue = false, Variable = false}},
59 | {(PropType)0x0014,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x0014,MultiValue = false, Variable = false}},
60 | {(PropType)0x001F,new ExchangeProperty{ByteCount = 0,Type = (PropType)0x001F,MultiValue = true, Variable = true}},
61 | {(PropType)0x001E,new ExchangeProperty{ByteCount = 0,Type = (PropType)0x001E,MultiValue = true, Variable = true}},
62 | {(PropType)0x0040,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x0040,MultiValue = false, Variable = false}},
63 | {(PropType)0x0048,new ExchangeProperty{ByteCount = 16,Type = (PropType)0x0048,MultiValue = false, Variable = false}},
64 | {(PropType)0x00FB,new ExchangeProperty{ByteCount = 0,Type = (PropType)0x00FB,MultiValue = false, Variable = true}},
65 | {(PropType)0x00FD,new ExchangeProperty{ByteCount = 0,Type = (PropType)0x00FD,MultiValue = false, Variable = true}},
66 | {(PropType)0x00FE,new ExchangeProperty{ByteCount = 0,Type = (PropType)0x00FE,MultiValue = true, Variable = true}},
67 | {(PropType)0x0102,new ExchangeProperty{ByteCount = 1,Type = (PropType)0x0102,MultiValue = true, Variable = false}},
68 | {(PropType)0x1002,new ExchangeProperty{ByteCount = 2,Type = (PropType)0x1002,MultiValue = true, Variable = false}},
69 | {(PropType)0x1003,new ExchangeProperty{ByteCount = 4,Type = (PropType)0x1003,MultiValue = true, Variable = false}},
70 | {(PropType)0x1004,new ExchangeProperty{ByteCount = 4,Type = (PropType)0x1004,MultiValue = true, Variable = false}},
71 | {(PropType)0x1005,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x1005,MultiValue = true, Variable = false}},
72 | {(PropType)0x1006,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x1006,MultiValue = true, Variable = false}},
73 | {(PropType)0x1007,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x1007,MultiValue = true, Variable = false}},
74 | {(PropType)0x1014,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x1014,MultiValue = true, Variable = false}},
75 | {(PropType)0x101F,new ExchangeProperty{ByteCount = 0,Type = (PropType)0x101F,MultiValue = true, Variable = true}},
76 | {(PropType)0x101E,new ExchangeProperty{ByteCount = 0,Type = (PropType)0x101E,MultiValue = true, Variable = true}},
77 | {(PropType)0x1040,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x1040,MultiValue = true, Variable = false}},
78 | {(PropType)0x1048,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x1048,MultiValue = true, Variable = false}},
79 | {(PropType)0x1102,new ExchangeProperty{ByteCount = 0,Type = (PropType)0x1102,MultiValue = true, Variable = true}},
80 | //{0x1102,new ExchangeProperty{ByteCount = 0,Type = 0x1102,MultiValue = true, Variable = true}}
81 | };
82 |
83 | public MessageProperty ID { get; set; }
84 | public PropType Type { get; set; }
85 | public bool MultiValue { get; set; }
86 | public bool Variable { get; set; }
87 | public uint ByteCount { get; set; }
88 | public byte[] Data { get; set; }
89 | //private BTHDataEntry entry;
90 |
91 | private readonly byte[] _key;
92 |
93 | public ExchangeProperty() { }
94 |
95 | public ExchangeProperty(UInt16 ID, UInt16 type, BTH heap, byte[] key)
96 | {
97 | this.ID = (MessageProperty)ID;
98 | this.Type = (PropType)type;
99 | /*var tempKey = new byte[key.Length + 2];
100 | tempKey[0] = 0x00;
101 | tempKey[1] = 0x00;
102 | for (int i = 0; i < key.Length; i++)
103 | tempKey[i + 2] = key[i];*/
104 | this._key = key;
105 |
106 | GetData(heap, true);
107 | }
108 |
109 | public ExchangeProperty(BTHDataEntry entry, BTH heap)
110 | {
111 | //this.entry = entry;
112 |
113 | _key = entry.Data.RangeSubset(2, entry.Data.Length - 2);
114 | ID = (MessageProperty)BitConverter.ToUInt16(entry.Key, 0);
115 | Type = (PropType)BitConverter.ToUInt16(entry.Data, 0);
116 |
117 | GetData(heap);
118 | }
119 |
120 | private void GetData(BTH heap, bool isTable = false)
121 | {
122 | if (PropertyLookupByTypeID.ContainsKey(Type))
123 | {
124 | var prop = PropertyLookupByTypeID[this.Type];
125 | MultiValue = prop.MultiValue;
126 | Variable = prop.Variable;
127 | ByteCount = prop.ByteCount;
128 | }
129 | //get data here
130 |
131 | if (!this.MultiValue && !this.Variable)
132 | {
133 | if (this.ByteCount <= 4 || (isTable && this.ByteCount <= 8))
134 | {
135 | this.Data = this._key;
136 | }
137 | else
138 | {
139 | this.Data = heap.GetHIDBytes(new HID(this._key)).Data;
140 | }
141 | }
142 | else
143 | {
144 | //oh no, it's an HNID
145 | var curID = BitConverter.ToUInt32(this._key, 0);
146 |
147 | if (curID == 0)
148 | {
149 |
150 | }
151 | else if ((curID & 0x1F) == 0) //must be HID
152 | {
153 | this.Data = heap.GetHIDBytes(new HID(this._key)).Data;
154 | }
155 | else //let's assume NID
156 | {
157 | var totalSize = 0;
158 | var dataBlocks = new List();
159 | if (heap.HeapNode.HeapSubNode.ContainsKey(curID))
160 | dataBlocks = heap.HeapNode.HeapSubNode[curID].NodeData;
161 | else
162 | {
163 | var tempSubNodeXREF = new Dictionary();
164 | foreach (var heapSubNode in heap.HeapNode.HeapSubNode)
165 | tempSubNodeXREF.Add(heapSubNode.Key & 0xFFFFFFFF, heapSubNode.Value);
166 | dataBlocks = tempSubNodeXREF[curID].NodeData;
167 | //dataBlocks = entry.ParentTree.HeapNode.HeapSubNode[curID].NodeData;
168 | }
169 | foreach (var dataBlock in dataBlocks)
170 | totalSize += dataBlock.Data.Length;
171 | var allData = new byte[totalSize];
172 | var curPos = 0;
173 | foreach (var datablock in dataBlocks)
174 | {
175 | for (int i = 0; i < datablock.Data.Length; i++)
176 | {
177 | allData[i + curPos] = datablock.Data[i];
178 | }
179 | curPos += datablock.Data.Length;
180 | }
181 | this.Data = allData;
182 | }
183 | }
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/HID.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace PSTParse.ListsTablesPropertiesLayer
7 | {
8 | public class HID
9 | {
10 | public ulong HID_Type;
11 | //the index in the allocations for the specific heap block.
12 | public ulong hidIndex;
13 | //the index in the block array for this heap
14 | public ulong hidBlockIndex;
15 |
16 | public HID(byte[] bytes, int offset = 0)
17 | {
18 | var temp = BitConverter.ToUInt32(bytes, offset);
19 | this.HID_Type = temp & 0x1F;
20 | this.hidIndex = (temp >> 5) & 0x7FF;
21 | this.hidBlockIndex = BitConverter.ToUInt16(bytes, offset + 2);
22 | }
23 |
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/HN.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using PSTParse.NodeDatabaseLayer;
3 |
4 | namespace PSTParse.ListsTablesPropertiesLayer
5 | {
6 | ///
7 | /// Heap on Node (HN)
8 | ///
9 | public class HN
10 | {
11 | // why unused
12 | // public NBTENTRY HNNode { get; set; }
13 | public List HeapNodes { get; set; }
14 | public Dictionary HeapSubNode { get; set; }
15 |
16 | public HN(NodeDataDTO nodeData)
17 | {
18 | HeapNodes = new List();
19 | var numBlocks = nodeData.NodeData.Count;
20 | for (int i = 0; i < numBlocks; i++)
21 | {
22 | var curBlock = new HNBlock(i, nodeData.NodeData[i]);
23 | HeapNodes.Add(curBlock);
24 | }
25 |
26 | HeapSubNode = nodeData.SubNodeData;
27 | }
28 |
29 | public HNDataDTO GetHIDBytes(HID hid)
30 | {
31 | return HeapNodes[(int)hid.hidBlockIndex].GetAllocation(hid);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/HNBITMAPHDR.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace PSTParse.ListsTablesPropertiesLayer
7 | {
8 | public class HNBITMAPHDR
9 | {
10 | public uint HNPageMapOffset;
11 | public byte[] FillLevel;
12 |
13 | public HNBITMAPHDR(ref byte[] bytes)
14 | {
15 | this.HNPageMapOffset = BitConverter.ToUInt16(bytes, 0);
16 | this.FillLevel = bytes.Skip(2).Take(64).ToArray();
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/HNBlock.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using PSTParse.NodeDatabaseLayer;
3 | using PSTParse.Utilities;
4 |
5 | namespace PSTParse.ListsTablesPropertiesLayer
6 | {
7 | ///
8 | /// Heap Node Block (HNBlock)
9 | ///
10 | public class HNBlock
11 | {
12 | public HNHDR Header { get; }
13 | public HNPAGEHDR PageHeader { get; }
14 | public HNBITMAPHDR BitMapPageHeader { get; }
15 | public HNPAGEMAP PageMap { get; }
16 | public ushort PageMapOffset { get; }
17 | private BlockDataDTO _bytes { get; }
18 |
19 | public HNBlock(int blockIndex, BlockDataDTO bytes)
20 | {
21 | _bytes = bytes;
22 | var bytesData = _bytes.Data;
23 |
24 | PageMapOffset = BitConverter.ToUInt16(_bytes.Data, 0);
25 | PageMap = new HNPAGEMAP(_bytes.Data, PageMapOffset);
26 | if (blockIndex == 0)
27 | {
28 | Header = new HNHDR(_bytes.Data);
29 | } else if (blockIndex % 128 == 8)
30 | {
31 | BitMapPageHeader = new HNBITMAPHDR(ref bytesData);
32 | } else
33 | {
34 | PageHeader = new HNPAGEHDR(ref bytesData);
35 | }
36 | }
37 |
38 | public HNDataDTO GetAllocation(HID hid)
39 | {
40 | var begOffset = PageMap.AllocationTable[(int) hid.hidIndex - 1];
41 | var endOffset = PageMap.AllocationTable[(int) hid.hidIndex];
42 | return new HNDataDTO
43 | {
44 | Data = _bytes.Data.RangeSubset(begOffset, endOffset - begOffset),
45 | BlockOffset = begOffset,
46 | Parent = _bytes
47 | };
48 | }
49 |
50 | public int GetOffset()
51 | {
52 | if (Header != null)
53 | return 12;
54 | if (PageHeader != null)
55 | return 2;
56 | return 66;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/HNDataDTO.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using PSTParse.NodeDatabaseLayer;
6 |
7 | namespace PSTParse.ListsTablesPropertiesLayer
8 | {
9 | public class HNDataDTO
10 | {
11 | public BlockDataDTO Parent;
12 | public long BlockOffset;
13 | public byte[] Data;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/HNHDR.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace PSTParse.ListsTablesPropertiesLayer
7 | {
8 | public class HNHDR
9 | {
10 | public enum ClientSig
11 | {
12 | TableContext = 0x7C,
13 | BTreeHeap = 0xB5,
14 | PropertyContext = 0xBC
15 | }
16 | public ulong OffsetHNPageMap;
17 | public ulong bSig;
18 | public ClientSig ClientSigType;
19 | public HID UserRoot;
20 | public ulong FillLevel_raw;
21 |
22 | public HNHDR(byte[] bytes)
23 | {
24 | this.ClientSigType = (ClientSig)bytes[3];
25 | this.bSig = bytes[2];
26 | this.OffsetHNPageMap = BitConverter.ToUInt16(bytes, 0);
27 | this.UserRoot = new HID(bytes.Skip(4).Take(4).ToArray());
28 | this.FillLevel_raw = BitConverter.ToUInt32(bytes, 8);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/HNPAGEHDR.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace PSTParse.ListsTablesPropertiesLayer
7 | {
8 | public class HNPAGEHDR
9 | {
10 | public UInt16 HNPageMapOffset;
11 | public HNPAGEHDR(ref byte[] bytes)
12 | {
13 | this.HNPageMapOffset = BitConverter.ToUInt16(bytes, 0);
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/HNPAGEMAP.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace PSTParse.ListsTablesPropertiesLayer
7 | {
8 | public class HNPAGEMAP
9 | {
10 | public uint AllocationsCount;
11 | public uint FreeItemsCount;
12 | public List AllocationTable;
13 |
14 | public HNPAGEMAP(byte[] bytes, int offset)
15 | {
16 | this.AllocationsCount = BitConverter.ToUInt16(bytes, offset);
17 | this.FreeItemsCount = BitConverter.ToUInt16(bytes, offset+2);
18 | this.AllocationTable = new List();
19 |
20 | for(int i= 0;i < AllocationsCount+1;i++)
21 | this.AllocationTable.Add(BitConverter.ToUInt16(bytes,offset+4+i*2));
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/HeapNodeBO.cs:
--------------------------------------------------------------------------------
1 | using PSTParse.NodeDatabaseLayer;
2 |
3 | namespace PSTParse.ListsTablesPropertiesLayer
4 | {
5 | public static class HeapNodeBO
6 | {
7 | public static HN GetHeapNode(ulong NID, PSTFile pst)
8 | {
9 | return new HN(BlockBO.GetNodeData(NID, pst));
10 | }
11 |
12 | public static HNDataDTO GetHNHIDBytes(HN heapNode, HID hid)
13 | {
14 | var hnblock = heapNode.HeapNodes[(int)hid.hidBlockIndex];
15 | return hnblock.GetAllocation(hid);
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/PropertyContext.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using PSTParse.MessageLayer;
3 | using PSTParse.NodeDatabaseLayer;
4 | using static PSTParse.Utilities.Utilities;
5 | using static System.Text.Encoding;
6 |
7 | namespace PSTParse.ListsTablesPropertiesLayer
8 | {
9 | public class PropertyContext
10 | {
11 | private string _messageClassProperty;
12 |
13 | public BTH BTH { get; }
14 | public Dictionary Properties { get; }
15 | public ulong NID { get; }
16 | public string MessageClassProperty =>
17 | Lazy(ref _messageClassProperty, () => Unicode.GetString(Properties[MessageProperty.MessageClass].Data));
18 |
19 | public PropertyContext(ulong nid, PSTFile pst) : this(BlockBO.GetNodeData(nid, pst)) => NID = nid;
20 | public PropertyContext(NodeDataDTO nodeData)
21 | {
22 | var HN = new HN(nodeData);
23 | BTH = new BTH(HN);
24 | Properties = BTH.GetExchangeProperties();
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/TCINFOHEADER.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace PSTParse.ListsTablesPropertiesLayer
5 | {
6 | public class TCINFOHEADER
7 | {
8 | public byte Type;
9 | public ushort ColumnCount;
10 | public ushort EndOffset48;
11 | public ushort EndOffset2;
12 | public ushort EndOffset1;
13 | public ushort EndOffsetCEB;
14 | public HID RowIndexLocation;
15 | public ulong RowMatrixLocation;
16 |
17 | public List ColumnsDescriptors;
18 |
19 | public TCINFOHEADER(byte[] bytes)
20 | {
21 | this.Type = bytes[0];
22 | this.ColumnCount = bytes[1];
23 | this.EndOffset48 = BitConverter.ToUInt16(bytes, 2);
24 | this.EndOffset2 = BitConverter.ToUInt16(bytes, 4);
25 | this.EndOffset1 = BitConverter.ToUInt16(bytes, 6);
26 | this.EndOffsetCEB = BitConverter.ToUInt16(bytes, 8);
27 | this.RowIndexLocation = new HID(bytes, 10);
28 | this.RowMatrixLocation = BitConverter.ToUInt32(bytes, 14);
29 | this.ColumnsDescriptors = new List();
30 | for (var i = 0; i < this.ColumnCount; i++)
31 | {
32 | this.ColumnsDescriptors.Add(new TCOLDESC(bytes, 22 + i*8));
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/TCOLDESC.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace PSTParse.ListsTablesPropertiesLayer
7 | {
8 | public class TCOLDESC
9 | {
10 | public uint Tag;
11 | public ushort DataOffset;
12 | public ushort DataSize;
13 | public ushort CEBIndex;
14 |
15 | public TCOLDESC(byte[] bytes, int offset = 0)
16 | {
17 | this.Tag = BitConverter.ToUInt32(bytes, offset);
18 | this.DataOffset = BitConverter.ToUInt16(bytes, offset + 4);
19 | this.DataSize = bytes[offset + 6];
20 | this.CEBIndex = bytes[offset + 7];
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/TCRowMatrix.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using PSTParse.NodeDatabaseLayer;
6 |
7 | namespace PSTParse.ListsTablesPropertiesLayer
8 | {
9 | public class TCRowMatrix
10 | {
11 | public TableContext TableContext;
12 | public List TCRMData;
13 |
14 | public List Rows;
15 | public Dictionary RowXREF;
16 |
17 | public TCRowMatrix(TableContext tableContext, BTH heap)
18 | {
19 | this.Rows = new List();
20 | this.RowXREF = new Dictionary();
21 |
22 | this.TableContext = tableContext;
23 | var rowMatrixHNID = this.TableContext.TCHeader.RowMatrixLocation;
24 | if (rowMatrixHNID == 0)
25 | return;
26 |
27 | if ((rowMatrixHNID & 0x1F) == 0)//HID
28 | {
29 | this.TCRMData = new List{
30 | new BlockDataDTO
31 | {
32 | Data = this.TableContext.HeapNode.GetHIDBytes(new HID(BitConverter.GetBytes(rowMatrixHNID))).Data
33 | }};
34 | } else
35 | {
36 | if (this.TableContext.HeapNode.HeapSubNode.ContainsKey(rowMatrixHNID))
37 | this.TCRMData = this.TableContext.HeapNode.HeapSubNode[rowMatrixHNID].NodeData;
38 | else
39 | {
40 | var tempSubNodes = new Dictionary();
41 | foreach(var nod in this.TableContext.HeapNode.HeapSubNode)
42 | tempSubNodes.Add(nod.Key & 0xffffffff, nod.Value);
43 | this.TCRMData = tempSubNodes[rowMatrixHNID].NodeData;
44 | }
45 | }
46 | //this.TCRMSubNodeData = this.TableContext.HeapNode.HeapSubNode[];
47 | var rowSize = this.TableContext.TCHeader.EndOffsetCEB;
48 | //var rowPerBlock = (8192 - 16)/rowSize;
49 |
50 | foreach(var row in this.TableContext.RowIndexBTH.Properties)
51 | {
52 | var rowIndex = BitConverter.ToUInt32(row.Value.Data, 0);
53 |
54 | var blockTrailerSize = 16;
55 | var maxBlockSize = 8192 - blockTrailerSize;
56 | var recordsPerBlock = maxBlockSize/rowSize;
57 |
58 | var blockIndex = (int)rowIndex/recordsPerBlock;
59 | var indexInBlock = rowIndex%recordsPerBlock;
60 | var curRow = new TCRowMatrixData(this.TCRMData[blockIndex].Data, this.TableContext, heap,
61 | (int) indexInBlock*rowSize);
62 | this.RowXREF.Add(BitConverter.ToUInt32(row.Key, 0), curRow);
63 | this.Rows.Add(curRow);
64 | }
65 | /*
66 | uint curIndex = 0;
67 | foreach (var dataBlock in this.TCRMData)
68 | {
69 | for(int i = 0;i + rowSize < dataBlock.Data.Length; i += rowSize)
70 | {
71 | var curRow = new TCRowMatrixData(dataBlock.Data, this.TableContext, i);
72 | this.RowXREF.Add(this.TableContext.ReverseRowIndex[curIndex], curRow);
73 | this.Rows.Add(curRow);
74 | curIndex++;
75 | }
76 | }*/
77 |
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/TCRowMatrixData.cs:
--------------------------------------------------------------------------------
1 | using PSTParse.MessageLayer;
2 | using PSTParse.Utilities;
3 | using System;
4 | using System.Collections;
5 | using System.Collections.Generic;
6 |
7 | namespace PSTParse.ListsTablesPropertiesLayer
8 | {
9 | public class TCRowMatrixData : IEnumerable
10 | {
11 | private readonly BTH _heap;
12 |
13 | public Dictionary ColumnXREF { get; set; }
14 |
15 | public TCRowMatrixData(byte[] bytes, TableContext context, BTH heap, int offset = 0)
16 | {
17 | ColumnXREF = new Dictionary();
18 | _heap = heap;
19 |
20 | //todo: cell existence test
21 | //var rowSize = context.TCHeader.EndOffsetCEB;
22 | foreach (var col in context.TCHeader.ColumnsDescriptors)
23 | {
24 | ColumnXREF.Add((MessageProperty)col.Tag, bytes.RangeSubset(offset + col.DataOffset, col.DataSize));
25 | }
26 | }
27 |
28 | public IEnumerator GetEnumerator()
29 | {
30 | foreach (var col in this.ColumnXREF)
31 | {
32 | var uIntKey = (UInt16)(((uint)col.Key) >> 16);
33 | var type = (UInt16)((uint)col.Key & 0xFFFF);
34 | yield return new ExchangeProperty(uIntKey, type, _heap, col.Value);
35 | }
36 | }
37 |
38 | IEnumerator IEnumerable.GetEnumerator()
39 | {
40 | return GetEnumerator();
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/TableContext.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using PSTParse.NodeDatabaseLayer;
4 |
5 | namespace PSTParse.ListsTablesPropertiesLayer
6 | {
7 | public class TableContext
8 | {
9 | public TCINFOHEADER TCHeader { get; }
10 | public HN HeapNode { get; }
11 | public NodeDataDTO NodeData { get; }
12 | public BTH RowIndexBTH { get; }
13 | public Dictionary ReverseRowIndex { get; }
14 | public TCRowMatrix RowMatrix { get; }
15 |
16 | public TableContext(ulong nid, PSTFile pst)
17 | {
18 | NodeData = BlockBO.GetNodeData(nid, pst);
19 |
20 | HeapNode = new HN(NodeData);
21 |
22 | var tcinfoHID = HeapNode.HeapNodes[0].Header.UserRoot;
23 | var tcinfoHIDbytes = HeapNode.GetHIDBytes(tcinfoHID);
24 | TCHeader = new TCINFOHEADER(tcinfoHIDbytes.Data);
25 |
26 | RowIndexBTH = new BTH(HeapNode,TCHeader.RowIndexLocation);
27 | ReverseRowIndex = new Dictionary();
28 | foreach(var prop in RowIndexBTH.Properties)
29 | {
30 | var temp = BitConverter.ToUInt32(prop.Value.Data, 0);
31 | ReverseRowIndex.Add(temp,BitConverter.ToUInt32(prop.Key, 0));
32 | }
33 | RowMatrix = new TCRowMatrix(this, RowIndexBTH);
34 | }
35 |
36 | public TableContext(NodeDataDTO nodeData)
37 | {
38 | NodeData = nodeData;
39 | HeapNode = new HN(NodeData);
40 |
41 | var tcinfoHID = HeapNode.HeapNodes[0].Header.UserRoot;
42 | var tcinfoHIDbytes = HeapNode.GetHIDBytes(tcinfoHID);
43 | TCHeader = new TCINFOHEADER(tcinfoHIDbytes.Data);
44 |
45 | RowIndexBTH = new BTH(HeapNode, TCHeader.RowIndexLocation);
46 | ReverseRowIndex = new Dictionary();
47 | foreach (var prop in RowIndexBTH.Properties)
48 | {
49 | var temp = BitConverter.ToUInt32(prop.Value.Data, 0);
50 | ReverseRowIndex.Add(temp, BitConverter.ToUInt32(prop.Key, 0));
51 | }
52 | RowMatrix = new TCRowMatrix(this, RowIndexBTH);
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/unused/BTHDataRecord.cs:
--------------------------------------------------------------------------------
1 | // using System;
2 | // using System.Collections.Generic;
3 | // using System.Linq;
4 | // using System.Text;
5 |
6 | // namespace PSTParse.ListsTablesPropertiesLayer
7 | // {
8 | // public class BTHDataRecord
9 | // {
10 | // public uint Key;
11 | // public uint Value;
12 |
13 | // public BTHDataRecord(byte[] bytes, BTHHEADER header)
14 | // {
15 | // var keySize = (int)header.KeySize;
16 | // var dataSize = (int) header.DataSize;
17 |
18 | // this.Key = BitConverter.ToUInt16(bytes.Take(keySize).ToArray(), 0);
19 | // this.Value = BitConverter.ToUInt32(bytes.Skip(keySize).Take(dataSize).ToArray(), 0);
20 | // }
21 | // }
22 | // }
23 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/unused/BTHIndexAllocationRecords.cs:
--------------------------------------------------------------------------------
1 | // using System;
2 | // using System.Collections.Generic;
3 | // using System.Linq;
4 | // using System.Text;
5 |
6 | // namespace PSTParse.ListsTablesPropertiesLayer
7 | // {
8 | // public class BTHIndexAllocationRecords
9 | // {
10 | // public List BTHIndexRecords;
11 | // public int CurrentLevel;
12 | // public BTHIndexAllocationRecords(byte[] bytes, int offset, BTHHEADER header, int level)
13 | // {
14 | // this.CurrentLevel = level;
15 | // this.BTHIndexRecords = new List();
16 |
17 | // var keySize = (int)header.KeySize;
18 | // for (int i = offset; i < bytes.Length; i += 4 + keySize)
19 | // this.BTHIndexRecords.Add(new BTHIndexNode(bytes, header, i));
20 | // }
21 | // }
22 | // }
23 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/unused/HNID.cs:
--------------------------------------------------------------------------------
1 | // using System;
2 | // using System.Collections.Generic;
3 | // using System.Linq;
4 | // using System.Text;
5 |
6 | // namespace PSTParse.ListsTablesPropertiesLayer
7 | // {
8 | // public class HNID
9 | // {
10 | // public ulong HNID_Type;
11 | // public ulong hnidIndex;
12 | // public ulong hnidBlockIndex;
13 |
14 | // public HNID(byte[] bytes)
15 | // {
16 | // var temp = BitConverter.ToUInt64(bytes, 0);
17 | // this.HNID_Type = temp & 0x1F;
18 | // this.hnidIndex = (temp >> 5) & 0x4FF;
19 | // this.hnidBlockIndex = temp >> 16;
20 | // }
21 | // }
22 | // }
23 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/unused/MVPropVarBase.cs:
--------------------------------------------------------------------------------
1 | // using System;
2 | // using System.Collections.Generic;
3 | // using System.Linq;
4 | // using System.Text;
5 |
6 | // namespace PSTParse.ListsTablesPropertiesLayer
7 | // {
8 | // public class MVPropVarBase
9 | // {
10 | // public UInt32 PropCount;
11 | // private List PropOffsets;
12 | // private List PropDataItems;
13 | // public MVPropVarBase(byte[] bytes)
14 | // {
15 | // this.PropCount = BitConverter.ToUInt32(bytes, 0);
16 | // this.PropOffsets = new List();
17 |
18 | // for(int i= 0;i < this.PropCount; i++)
19 | // this.PropOffsets.Add(BitConverter.ToUInt64(bytes, 4 + i*8));
20 |
21 | // this.PropDataItems = new List();
22 | // for(int i = 0;i < this.PropCount; i++)
23 | // {
24 | // if (i < PropCount-1)
25 | // {
26 | // this.PropDataItems.Add(
27 | // bytes.Skip((int) this.PropOffsets[i]).Take((int) (this.PropOffsets[i + 1] - this.PropOffsets[i]))
28 | // .ToArray());
29 | // }
30 | // }
31 | // }
32 | // }
33 | // }
34 |
--------------------------------------------------------------------------------
/PSTParse/ListsTablesPropertiesLayer/unused/PCBTHRecord.cs:
--------------------------------------------------------------------------------
1 | // using PSTParse.Utilities;
2 | // using System;
3 | // using System.Linq;
4 |
5 | // namespace PSTParse.ListsTablesPropertiesLayer
6 | // {
7 | // public class PCBTHRecord
8 | // {
9 | // public UInt16 PropID;
10 | // public UInt16 PropType;
11 | // public ExchangeProperty PropertyValue;
12 |
13 | // public PCBTHRecord(byte[] bytes)
14 | // {
15 | // PropID = BitConverter.ToUInt16(bytes.Take(2).ToArray(), 0);
16 | // PropType = BitConverter.ToUInt16(bytes.Skip(2).Take(2).ToArray(), 0);
17 | // var prop= PropertyValue = ExchangeProperty.PropertyLookupByTypeID[(ExchangeProperty.PropType)PropType];
18 | // if (!prop.MultiValue)
19 | // {
20 | // if (!prop.Variable)
21 | // {
22 | // if (prop.ByteCount <= 4 && prop.ByteCount != 0)
23 | // {
24 | // PropertyValue.Data = bytes.RangeSubset(4, (int) prop.ByteCount);
25 | // }
26 | // else
27 | // {
28 |
29 | // }
30 | // }
31 | // }
32 | // //HNID = new HNID(bytes.Skip(4).ToArray());
33 | // }
34 | // }
35 | // }
--------------------------------------------------------------------------------
/PSTParse/MessageLayer/Attachment.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using PSTParse.ListsTablesPropertiesLayer;
6 |
7 | namespace PSTParse.MessageLayer
8 | {
9 | public enum NodeValue
10 | {
11 | AttachmentTable = 0x671 // 1649
12 | }
13 |
14 | public enum AttachmentMethod
15 | {
16 | NONE = 0x00,
17 | BY_VALUE = 0x01,
18 | BY_REFERENCE = 0X02,
19 | BY_REFERENCE_ONLY = 0X04,
20 | EMBEDDEED_MESSAGE = 0X05,
21 | STORAGE = 0X06
22 | }
23 |
24 | public class Attachment
25 | {
26 | public AttachmentMethod Method { get; set; }
27 | public uint Size { get; set; }
28 | public uint RenderingPosition { get; set; }
29 | public string Filename { get; set; }
30 | public string AttachmentLongFileName { get; set; }
31 | public string DisplayName { get; set; }
32 | public uint LTPRowID { get; set; }
33 | public uint LTPRowVer { get; set; }
34 | public bool InvisibleInHTML { get; set; }
35 | public bool InvisibleInRTF { get; set; }
36 | public bool RenderedInBody { get; set; }
37 | public byte[] Data { get; set; }
38 |
39 | public Attachment(PropertyContext propertyContext) : this(propertyContext?.Properties.Select(d => d.Value)) { }
40 |
41 | public Attachment(IEnumerable exchangeProperties)
42 | {
43 | exchangeProperties = exchangeProperties ?? Enumerable.Empty();
44 | foreach (var property in exchangeProperties)
45 | {
46 | switch (property.ID)
47 | {
48 | case MessageProperty.AttachmentData:
49 | Data = property.Data;
50 | break;
51 | case MessageProperty.AttachmentSize:
52 | Size = BitConverter.ToUInt32(property.Data, 0);
53 | break;
54 | case MessageProperty.AttachmentFileName:
55 | if (property.Data != null)
56 | Filename = Encoding.Unicode.GetString(property.Data);
57 | //else
58 | // Filename = Guid.NewGuid().ToString();
59 | break;
60 | case MessageProperty.DisplayName:
61 | if (property.Data != null)
62 | DisplayName = Encoding.Unicode.GetString(property.Data);
63 | else
64 | DisplayName = Guid.NewGuid().ToString();
65 | break;
66 | case MessageProperty.AttachmentLongFileName:
67 | if (property.Data != null)
68 | AttachmentLongFileName = Encoding.Unicode.GetString(property.Data);
69 | //else
70 | // AttachmentLongFileName = Guid.NewGuid().ToString();
71 | break;
72 | case MessageProperty.AttachmentMethod:
73 | Method = (AttachmentMethod)BitConverter.ToUInt32(property.Data, 0);
74 | break;
75 | case MessageProperty.AttachmentRenderPosition:
76 | RenderingPosition = BitConverter.ToUInt32(property.Data, 0);
77 | break;
78 | case MessageProperty.AttachmentFlags:
79 | var flags = BitConverter.ToUInt32(property.Data, 0);
80 | InvisibleInHTML = (flags & 0x1) != 0;
81 | InvisibleInRTF = (flags & 0x02) != 0;
82 | RenderedInBody = (flags & 0x04) != 0;
83 | break;
84 | case MessageProperty.AttachmentLTPRowID:
85 | LTPRowID = BitConverter.ToUInt32(property.Data, 0);
86 | break;
87 | case MessageProperty.AttachmentLTPRowVer:
88 | LTPRowVer = BitConverter.ToUInt32(property.Data, 0);
89 | break;
90 | default:
91 | break;
92 | }
93 | }
94 | }
95 | }
96 | }
--------------------------------------------------------------------------------
/PSTParse/MessageLayer/IPMItem.cs:
--------------------------------------------------------------------------------
1 | using PSTParse.ListsTablesPropertiesLayer;
2 |
3 | namespace PSTParse.MessageLayer
4 | {
5 | public class IPMItem
6 | {
7 | public string MessageClass => PropertyContext.MessageClassProperty;
8 | protected PropertyContext PropertyContext { get; }
9 |
10 | //public IPMItem(PSTFile pst, ulong nid)
11 | //{
12 | // PropertyContext = new PropertyContext(nid, pst);
13 | // MessageClass = Encoding.Unicode.GetString(PropertyContext.Properties[(MessageProperty)0x1a].Data);
14 | //}
15 |
16 | public IPMItem(PSTFile pst, PropertyContext propertyContext)
17 | {
18 | PropertyContext = propertyContext;
19 | //MessageClass = Encoding.Unicode.GetString(PropertyContext.Properties[(MessageProperty)0x1a].Data);
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/PSTParse/MessageLayer/MailFolder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using PSTParse.ListsTablesPropertiesLayer;
5 | using PSTParse.MessageLayer;
6 |
7 | namespace PSTParse
8 | {
9 | public class MailFolder
10 | {
11 | private readonly PSTFile _pst;
12 | private readonly PropertyContext PC;
13 | private readonly TableContext HeirachyTC;
14 | private readonly TableContext ContentsTC;
15 | private readonly TableContext FaiTC;
16 |
17 | public string DisplayName { get; }
18 | public string ContainerClass { get; }
19 | public List Path { get; }
20 | public List SubFolders { get; }
21 | public int Count => ContentsTC.RowIndexBTH.Properties.Count;
22 |
23 | public MailFolder(ulong nid, List path, PSTFile pst)
24 | {
25 | _pst = pst;
26 |
27 | Path = path;
28 | var pcNID = ((nid >> 5) << 5) | 0x02;
29 | PC = new PropertyContext(pcNID, pst);
30 | DisplayName = Encoding.Unicode.GetString(PC.Properties[MessageProperty.DisplayName].Data);
31 |
32 |
33 | PC.Properties.TryGetValue(MessageProperty.ContainerClass, out ExchangeProperty containerClassProperty);
34 | ContainerClass = containerClassProperty?.Data == null
35 | ? ""
36 | : Encoding.Unicode.GetString(containerClassProperty.Data);
37 |
38 | Path = new List(path) { DisplayName };
39 |
40 | var heirachyNID = ((nid >> 5) << 5) | 0x0D;
41 | var contentsNID = ((nid >> 5) << 5) | 0x0E;
42 | var faiNID = ((nid >> 5) << 5) | 0x0F;
43 |
44 | HeirachyTC = new TableContext(heirachyNID, pst);
45 |
46 | SubFolders = new List();
47 | foreach (var row in HeirachyTC.ReverseRowIndex)
48 | {
49 | SubFolders.Add(new MailFolder(row.Value, Path, pst));
50 | //SubFolderEntryIDs.Add(row.);
51 | }
52 |
53 | ContentsTC = new TableContext(contentsNID, pst);
54 |
55 |
56 | FaiTC = new TableContext(faiNID, pst);
57 | }
58 |
59 | public IEnumerable GetIpmItems()
60 | {
61 | foreach (var row in ContentsTC.ReverseRowIndex)
62 | {
63 | var propertyContext = GetPropertyContext(row.Value);
64 | if (propertyContext.MessageClassProperty == "IPM.Note")
65 | {
66 | yield return new Message(_pst, propertyContext);
67 | }
68 | else
69 | {
70 | yield return new IPMItem(_pst, propertyContext);
71 | }
72 | }
73 | }
74 |
75 | public IEnumerable GetIpmNotes()
76 | {
77 | foreach (var row in ContentsTC.ReverseRowIndex)
78 | {
79 | var propertyContext = GetPropertyContext(row.Value);
80 | if (propertyContext.MessageClassProperty == "IPM.Note")
81 | {
82 | yield return new Message(_pst, propertyContext);
83 | }
84 | }
85 | }
86 |
87 | private PropertyContext GetPropertyContext(uint rowValue)
88 | {
89 | try
90 | {
91 | return new PropertyContext(rowValue, _pst);
92 | }
93 | catch (Exception ex)
94 | {
95 | throw new Exception($"Error: PST corruption detected while parsing {string.Join("/", Path)}", ex);
96 | }
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/PSTParse/MessageLayer/MailStore.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
3 | using System.Text;
4 | using PSTParse.ListsTablesPropertiesLayer;
5 | using PSTParse.NodeDatabaseLayer;
6 |
7 | namespace PSTParse.MessageLayer
8 | {
9 | public class MailStore
10 | {
11 | private PropertyContext _pc;
12 |
13 | public EntryID RootFolder { get; }
14 | public string? DisplayName { get; }
15 |
16 | public MailStore(PSTFile pst)
17 | {
18 | _pc = new PropertyContext(SpecialNIDs.NID_MESSAGE_STORE, pst);
19 | RootFolder = new EntryID(_pc.Properties[MessageProperty.RootFolder].Data);
20 |
21 | _pc.Properties.TryGetValue(MessageProperty.DisplayName, out ExchangeProperty? displayProp);
22 | if (displayProp?.Data != null)
23 | {
24 | DisplayName = Encoding.Unicode.GetString(displayProp.Data);
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/PSTParse/MessageLayer/Message.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Text;
5 | using PSTParse.ListsTablesPropertiesLayer;
6 | using PSTParse.NodeDatabaseLayer;
7 | using PSTParse.Utilities;
8 | using static PSTParse.Utilities.Utilities;
9 |
10 | namespace PSTParse.MessageLayer
11 | {
12 | public class Message : IPMItem
13 | {
14 | //private readonly NodeDataDTO _data;
15 | //private readonly TableContext _attachmentTable;
16 | //private readonly TableContext _recipientTable;
17 | //private readonly PropertyContext _attachmentPC;
18 | //private readonly PropertyContext _messagePC;
19 | private readonly PSTFile _pst;
20 | private readonly ulong _nid;
21 | private Dictionary _subNodeDataDtoLazy;
22 | private Dictionary _subNodeHeaderDataDtoLazy;
23 | private readonly Lazy _isRMSEncryptedLazy;
24 | private readonly Lazy _isRMSEncryptedHeadersLazy;
25 | private Recipients _recipientsLazy;
26 | private List _attachmentsLazy;
27 | private IEnumerable _attachmentHeadersLazy;
28 |
29 | private Dictionary SubNodeDataDto => Lazy(ref _subNodeDataDtoLazy, () => BlockBO.GetSubNodeData(_nid, _pst));
30 | private Dictionary SubNodeHeaderDataDto => Lazy(ref _subNodeHeaderDataDtoLazy, () => BlockBO.GetSubNodeData(_nid, _pst, take: 1));
31 |
32 | public string Subject { get; set; }
33 | public string SubjectPrefix { get; set; }
34 | public Importance Importance { get; set; }
35 | public Sensitivity Sensitivity { get; set; }
36 | public DateTime LastSaved { get; set; }
37 | public DateTime ClientSubmitTime { get; set; }
38 | public string SentRepresentingName { get; set; }
39 | public string ConversationTopic { get; set; }
40 | public string SenderName { get; set; }
41 | public string SenderAddress { get; set; }
42 | public string SenderAddressType { get; set; }
43 | public string SenderSMTPAddress { get; set; }
44 | public DateTime MessageDeliveryTime { get; set; }
45 | public bool Read { get; set; }
46 | public bool Unsent { get; set; }
47 | public bool Unmodified { get; set; }
48 | public bool HasAttachments { get; set; }
49 | public bool FromMe { get; set; }
50 | public bool IsFAI { get; set; }
51 | public bool NotifyReadRequested { get; set; }
52 | public bool NotifyUnreadRequested { get; set; }
53 | public bool EverRead { get; set; }
54 | public uint MessageSize { get; set; }
55 | public string Headers { get; set; }
56 | public string BodyPlainText { get; set; }
57 | public string BodyHtml { get; set; }
58 | public uint InternetArticleNumber { get; set; }
59 | public string BodyCompressedRTFString { get; set; }
60 | public string InternetMessageID { get; set; }
61 | public string UrlCompositeName { get; set; }
62 | public bool AttributeHidden { get; set; }
63 | public bool ReadOnly { get; set; }
64 | public DateTime CreationTime { get; set; }
65 | public DateTime LastModificationTime { get; set; }
66 | public uint CodePage { get; set; }
67 | public String CreatorName { get; set; }
68 | public uint NonUnicodeCodePage { get; set; }
69 | public uint MessageFlags { get; set; }
70 | public Recipients Recipients => Lazy(ref _recipientsLazy, GetRecipients);
71 | public List Attachments => Lazy(ref _attachmentsLazy, GetAttachments);
72 | public IEnumerable AttachmentHeaders => Lazy(ref _attachmentHeadersLazy, GetAttachmentHeaders);
73 | public bool IsRMSEncrypted => _isRMSEncryptedLazy.Value;
74 | public bool IsRMSEncryptedHeaders => _isRMSEncryptedHeadersLazy.Value;
75 |
76 | //public Message(PSTFile pst, ulong nid) : this(pst, new PropertyContext(nid, pst)) { }
77 |
78 | public Message(PSTFile pst, PropertyContext propertyContext) : base(pst, propertyContext)
79 | {
80 | _nid = propertyContext.NID;
81 | _pst = pst;
82 |
83 | //_subNodeDataDtoLazy = new Lazy>(() => BlockBO.GetSubNodeData(_nid, _pst));
84 | _isRMSEncryptedLazy = new Lazy(GetIsRMSEncrypted);
85 | _isRMSEncryptedHeadersLazy = new Lazy(GetIsRMSEncryptedHeaders);
86 |
87 | foreach (var prop in PropertyContext.Properties)
88 | {
89 | if (prop.Value.Data == null)
90 | continue;
91 | switch (prop.Key)
92 | {
93 | case MessageProperty.Importance:
94 | Importance = (Importance)BitConverter.ToInt16(prop.Value.Data, 0);
95 | break;
96 | case MessageProperty.Sensitivity:
97 | Sensitivity = (Sensitivity)BitConverter.ToInt16(prop.Value.Data, 0);
98 | break;
99 | case MessageProperty.Subject:
100 | Subject = Encoding.Unicode.GetString(prop.Value.Data);
101 | if (Subject.Length > 0)
102 | {
103 | var chars = Subject.ToCharArray();
104 | if (chars[0] == 0x001)
105 | {
106 | var length = (int)chars[1];
107 | int i = 0;
108 | if (length > 1)
109 | i++;
110 | SubjectPrefix = Subject.Substring(2, length - 1);
111 | Subject = Subject.Substring(2 + length - 1);
112 | }
113 | }
114 | break;
115 | case MessageProperty.ClientSubmitTime:
116 | ClientSubmitTime = DateTime.FromFileTimeUtc(BitConverter.ToInt64(prop.Value.Data, 0));
117 | break;
118 | case MessageProperty.SentRepresentingName:
119 | SentRepresentingName = Encoding.Unicode.GetString(prop.Value.Data);
120 | break;
121 | case MessageProperty.ConversationTopic:
122 | ConversationTopic = Encoding.Unicode.GetString(prop.Value.Data);
123 | break;
124 |
125 | //already done in base
126 | //case MessageProperty.MessageClass:
127 | // //MessageClassBuffer = prop.Value.Data;
128 | // MessageClass = Encoding.Unicode.GetString(prop.Value.Data);
129 | // //IsIPMNote = prop.Value.Data.Length == 16 && prop.Value.Data[14]== (byte)'e';
130 | // break;
131 | case MessageProperty.SenderAddress:
132 | SenderAddress = Encoding.Unicode.GetString(prop.Value.Data);
133 | break;
134 | case MessageProperty.SenderAddressType:
135 | SenderAddressType = Encoding.Unicode.GetString(prop.Value.Data);
136 | break;
137 | case MessageProperty.SenderSMTPAddress:
138 | SenderSMTPAddress = Encoding.Unicode.GetString(prop.Value.Data);
139 | break;
140 | case MessageProperty.SenderName:
141 | SenderName = Encoding.Unicode.GetString(prop.Value.Data);
142 | break;
143 | case MessageProperty.MessageDeliveryTime:
144 | MessageDeliveryTime = DateTime.FromFileTimeUtc(BitConverter.ToInt64(prop.Value.Data, 0));
145 | break;
146 | case MessageProperty.MessageFlags:
147 | MessageFlags = BitConverter.ToUInt32(prop.Value.Data, 0);
148 |
149 | Read = (MessageFlags & 0x1) != 0;
150 | Unsent = (MessageFlags & 0x8) != 0;
151 | Unmodified = (MessageFlags & 0x2) != 0;
152 | HasAttachments = (MessageFlags & 0x10) != 0;
153 | FromMe = (MessageFlags & 0x20) != 0;
154 | IsFAI = (MessageFlags & 0x40) != 0;
155 | NotifyReadRequested = (MessageFlags & 0x100) != 0;
156 | NotifyUnreadRequested = (MessageFlags & 0x200) != 0;
157 | EverRead = (MessageFlags & 0x400) != 0;
158 | break;
159 | case MessageProperty.MessageSize:
160 | MessageSize = BitConverter.ToUInt32(prop.Value.Data, 0);
161 | break;
162 | case MessageProperty.InternetArticleNumber:
163 | InternetArticleNumber = BitConverter.ToUInt32(prop.Value.Data, 0);
164 | break;
165 | case (MessageProperty)0xe27:
166 | //unknown
167 | break;
168 | case MessageProperty.NextSentAccount:
169 | //nextSentAccount, ignore this, string
170 | break;
171 | case (MessageProperty)0xe62:
172 | //unknown
173 | break;
174 | case MessageProperty.TrustedSender:
175 | //trusted sender
176 | break;
177 | case MessageProperty.Headers:
178 | Headers = Encoding.Unicode.GetString(prop.Value.Data);
179 | break;
180 | case MessageProperty.BodyPlainText:
181 | BodyPlainText = Encoding.Unicode.GetString(prop.Value.Data);
182 | break;
183 | case MessageProperty.BodyCompressedRTF:
184 | BodyCompressedRTFString = new RtfDecompressor().Decompress(prop.Value.Data);
185 | break;
186 | case MessageProperty.BodyHtml:
187 | //var temp = MessagePropertyTypes.PropertyToString(true, prop.Value);
188 | BodyHtml = Encoding.ASCII.GetString(prop.Value.Data);
189 | break;
190 | case MessageProperty.MessageID:
191 | InternetMessageID = Encoding.Unicode.GetString(prop.Value.Data);
192 | break;
193 | case MessageProperty.UrlCompositeName:
194 | UrlCompositeName = Encoding.Unicode.GetString(prop.Value.Data);
195 | break;
196 | case MessageProperty.AttributeHidden:
197 | AttributeHidden = prop.Value.Data[0] == 0x01;
198 | break;
199 | case (MessageProperty)0x10F5:
200 | //unknown
201 | break;
202 | case MessageProperty.ReadOnly:
203 | ReadOnly = prop.Value.Data[0] == 0x01;
204 | break;
205 | case MessageProperty.CreationTime:
206 | CreationTime = DateTime.FromFileTimeUtc(BitConverter.ToInt64(prop.Value.Data, 0));
207 | break;
208 | case MessageProperty.LastModificationTime:
209 | LastModificationTime = DateTime.FromFileTimeUtc(BitConverter.ToInt64(prop.Value.Data, 0));
210 | break;
211 | case MessageProperty.SearchKey:
212 | //seach key
213 | break;
214 | case MessageProperty.CodePage:
215 | CodePage = BitConverter.ToUInt32(prop.Value.Data, 0);
216 | break;
217 | case MessageProperty.LocaleID:
218 | //localeID
219 | break;
220 | case MessageProperty.CreatorName:
221 | CreatorName = Encoding.Unicode.GetString(prop.Value.Data);
222 | break;
223 | case MessageProperty.CreatorEntryID:
224 | //creator entryid
225 | break;
226 | case MessageProperty.LastModifierName:
227 | //last modifier name
228 | break;
229 | case MessageProperty.LastModifierEntryID:
230 | //last modifier entryid
231 | break;
232 | case MessageProperty.NonUnicodeCodePage:
233 | NonUnicodeCodePage = BitConverter.ToUInt32(prop.Value.Data, 0);
234 | break;
235 | case (MessageProperty)0x4019:
236 | //unknown
237 | break;
238 | case MessageProperty.SentRepresentingFlags:
239 | //sentrepresentingflags
240 | break;
241 | case MessageProperty.UserEntryID:
242 | //userentryid
243 | break;
244 | default:
245 | break;
246 | }
247 | }
248 | }
249 |
250 | private Recipients GetRecipients()
251 | {
252 | const ulong recipientsFlag = 1682;
253 | var recipients = new Recipients();
254 |
255 | var exists = SubNodeDataDto.TryGetValue(recipientsFlag, out NodeDataDTO subNode);
256 | if (!exists) return recipients;
257 |
258 | var recipientTable = new TableContext(subNode);
259 | foreach (var row in recipientTable.RowMatrix.Rows)
260 | {
261 | var recipient = new Recipient(row);
262 | switch (recipient.Type)
263 | {
264 | case Recipient.RecipientType.TO:
265 | recipients.To.Add(recipient);
266 | break;
267 | case Recipient.RecipientType.CC:
268 | recipients.CC.Add(recipient);
269 | break;
270 | case Recipient.RecipientType.BCC:
271 | recipients.BCC.Add(recipient);
272 | break;
273 | }
274 | }
275 | return recipients;
276 | }
277 |
278 | private IEnumerable GetAttachmentHeaders()
279 | {
280 | if (!HasAttachments) yield break;
281 |
282 | var attachmentSet = new HashSet();
283 | foreach (var subNode in SubNodeHeaderDataDto)
284 | {
285 | if ((NodeValue)subNode.Key != NodeValue.AttachmentTable)
286 | {
287 | throw new Exception("expected node to be an attachment table");
288 | }
289 |
290 | var attachmentTable = new TableContext(subNode.Value);
291 | var attachmentRows = attachmentTable.RowMatrix.Rows;
292 |
293 | foreach (var attachmentRow in attachmentRows)
294 | {
295 | var attachment = new Attachment(attachmentRow);
296 | yield return attachment;
297 | }
298 | }
299 | }
300 |
301 | private List GetAttachments()
302 | {
303 | var attachments = new List();
304 | if (!HasAttachments) return attachments;
305 |
306 | var attachmentSet = new HashSet();
307 | foreach (var subNode in SubNodeDataDto)
308 | {
309 | var nodeType = NID.GetNodeType(subNode.Key);
310 | if (nodeType != NID.NodeType.ATTACHMENT_PC) continue;
311 |
312 | var attachmentPC = new PropertyContext(subNode.Value);
313 | var attachment = new Attachment(attachmentPC);
314 | if (attachmentSet.Contains(attachment.AttachmentLongFileName))
315 | {
316 | var smallGuid = Guid.NewGuid().ToString().Substring(0, 5);
317 | attachment.AttachmentLongFileName = $"{smallGuid}-{attachment.AttachmentLongFileName}";
318 | }
319 | attachmentSet.Add(attachment.AttachmentLongFileName);
320 | attachments.Add(attachment);
321 | }
322 | return attachments;
323 | }
324 |
325 | private bool GetIsRMSEncrypted()
326 | {
327 | if (!HasAttachments) return false;
328 |
329 | foreach (var attachment in Attachments)
330 | {
331 | if (attachment.AttachmentLongFileName?.ToLowerInvariant().EndsWith(".rpmsg") ?? false)
332 | {
333 | if (Attachments.Count > 1) throw new NotSupportedException("too many attachments for rms");
334 | return true;
335 | }
336 | if (attachment.Filename?.ToLowerInvariant().EndsWith(".rpmsg") ?? false)
337 | {
338 | if (Attachments.Count > 1) throw new NotSupportedException("too many attachments for rms");
339 | return true;
340 | }
341 | if (attachment.DisplayName?.ToLowerInvariant().EndsWith(".rpmsg") ?? false)
342 | {
343 | if (Attachments.Count > 1) throw new NotSupportedException("too many attachments for rms");
344 | return true;
345 | }
346 | }
347 | return false;
348 | }
349 |
350 | private bool GetIsRMSEncryptedHeaders()
351 | {
352 | if (!HasAttachments) return false;
353 |
354 | foreach (var attachment in AttachmentHeaders)
355 | {
356 | Debug.Assert(!(attachment.AttachmentLongFileName?.ToLowerInvariant().EndsWith(".rpm") ?? false));
357 | Debug.Assert(!(attachment.DisplayName?.ToLowerInvariant().EndsWith(".rpm") ?? false));
358 |
359 | if (attachment.Filename?.ToLowerInvariant().EndsWith(".rpm") ?? false)
360 | {
361 | Debug.Assert(Attachments.Count == 1, "too many attachments for rms");
362 | return true;
363 | }
364 |
365 | return false;
366 | }
367 | return false;
368 | }
369 | }
370 |
371 | public enum Importance
372 | {
373 | LOW = 0x00,
374 | NORMAL = 0x01,
375 | HIGH = 0x02
376 | }
377 |
378 | public enum Sensitivity
379 | {
380 | Normal = 0x00,
381 | Personal = 0x01,
382 | Private = 0x02,
383 | Confidential = 0x03
384 | }
385 | }
386 |
--------------------------------------------------------------------------------
/PSTParse/MessageLayer/MessageProperty.cs:
--------------------------------------------------------------------------------
1 | namespace PSTParse.MessageLayer
2 | {
3 | public enum MessageProperty : uint
4 | {
5 | GuidList = 0x2,
6 | EntryList = 0x3,
7 | StringList = 0x4,
8 | Importance = 0x17,
9 | MessageClass = 0x1a,
10 | DeliveryReportRequested = 0x23,
11 | Priority = 0x26,
12 | ReadReceiptRequested = 0x29,
13 | RecipientReassignmentProhibited = 0x2B,
14 | SensitivityOriginal = 0x2E,
15 | ReportTime = 0x32,
16 | Sensitivity = 0x36,
17 | Subject = 0x37,
18 | ClientSubmitTime = 0x39,
19 | OriginalSenderWithScheme = 0x3b,
20 | ReceivedByEntryID = 0x3F,
21 | ReceivedByName = 0x40,
22 | SentRepresentingEntryID = 0x41,
23 | SentRepresentingName = 0x42,
24 | ReceivedRepresentingEntryID = 0x43,
25 | ReceivedRepresentingName = 0x44,
26 | ReplyRecipientEntries = 0x4F,
27 | ReplyRecipientNames = 0x50,
28 | ReceivedBySearchKey = 0x51,
29 | ReceivedRepresentingSearchKey = 0x52,
30 | MessageToMe = 0x57,
31 | MessageCCMe = 0x58,
32 | MessageRecipientMe = 0x59,
33 | ResponseRequested = 0x60,
34 | SentRepresentingAddressType = 0x64,
35 | SentRepresentingAddress = 0x65,
36 | ConversationTopic = 0x70,
37 | ConversationIndex = 0x71,
38 | OriginalDisplayBcc = 0x72,
39 | OriginalDisplayCc = 0x73,
40 | OriginalDisplayTo = 0x74,
41 | ReceivedByAddressType = 0x75,
42 | ReceivedByAddress = 0x76,
43 | ReceivedRepresentingAddressType = 0x77,
44 | ReceivedRepresentingAddress = 0x78,
45 | Headers = 0x7d,
46 | UserEntryID = 0x619,
47 | NdrReasonCode = 0xC04,
48 | NdrDiagCode = 0xC05,
49 | NonReceiptNotificationRequested = 0xC06,
50 | RecipientType = 0xc15,
51 | ReplyRequested = 0xc17,
52 | SenderEntryID = 0xc19,
53 | SenderName = 0xc1a,
54 | SupplementaryInfo = 0xc1b,
55 | SenderSearchKey = 0xc1d,
56 | SenderAddressType = 0xc1e,
57 | SenderAddress = 0xc1f,
58 | DeleteAfterSubmit = 0xe01,
59 | DisplayBccAddresses = 0xe02,
60 | DisplayCcAddresses = 0xe03,
61 | RecipientName = 0xe04,
62 | MessageDeliveryTime = 0xe06,
63 | MessageFlags = 0xe07,
64 | MessageSize = 0xe08,
65 | SentMailEntryID = 0xe0a,
66 | RecipientResponsibility = 0xe0f,
67 | NormalizedSubject = 0xe1d,
68 | RtfInSync = 0xe1f,
69 | AttachmentSize = 0xe20,
70 | InternetArticleNumber = 0xe23,
71 | NextSentAccount = 0xe29,
72 | TrustedSender = 0xe79,
73 | RecordKey = 0xff9,
74 | RecipientObjType = 0xffe,
75 | RecipientEntryID = 0xfff,
76 | BodyPlainText = 0x1000,
77 | ReportText = 0x1001,
78 | BodyRtfCrc = 0x1006,
79 | BodyRtfSyncCount = 0x1007,
80 | BodyRtfSyncTag = 0x1008,
81 | BodyCompressedRTF = 0x1009,
82 | BodyRtfSyncPrefixCount = 0x1010,
83 | BodyRtfSyncTrailingCount = 0x1011,
84 | BodyHtml = 0x1013,
85 | MessageID = 0x1035,
86 | ReferencesMessageID = 0x1039,
87 | ReplyToMessageID = 0x1042,
88 | UnsubscribeAddress = 0x1045,
89 | ReturnPath = 0x1046,
90 | UrlCompositeName = 0x10F3,
91 | AttributeHidden = 0x10F4,
92 | ReadOnly = 0x10F6,
93 | DisplayName = 0x3001,
94 | AddressType = 0x3002,
95 | AddressName = 0x3003,
96 | Comment = 0x3004,
97 | CreationTime = 0x3007,
98 | LastModificationTime = 0x3008,
99 | SearchKey = 0x300B,
100 | ValidFolderMask = 0x35df,
101 | RootFolder = 0x35e0,
102 | OutboxFolder = 0x35e2,
103 | DeletedItemsFolder = 0x35e3,
104 | SentFolder = 0x35e4,
105 | UserViewsFolder = 0x35e5,
106 | CommonViewsFolder = 0x35e6,
107 | SearchFolder = 0x35e7,
108 | FolderContentCount = 0x3602,
109 | FolderUnreadCount = 0x3603,
110 | FolderHasChildren = 0x360a,
111 | ContainerClass = 0x3613,
112 | AssocContentCount = 0x3617,
113 | AttachmentData = 0x3701,
114 | AttachmentFileName = 0x3704,
115 | AttachmentMethod = 0x3705,
116 | AttachmentLongFileName = 0x3707,
117 | AttachmentRenderPosition = 0x370b,
118 | AttachmentMimeType = 0x370e,
119 | AttachmentMimeSequence = 0x3710,
120 | AttachmentContentID = 0x3712,
121 | AttachmentFlags = 0x3714,
122 | SMTPAddress = 0x39fe,
123 | CodePage = 0x3fDE,
124 | CreatorName = 0x3ff8,
125 | NonUnicodeCodePage = 0x3ffd,
126 | LocaleID = 0x3ff1,
127 | CreatorEntryID = 0x3ff9,
128 | LastModifierName = 0x3ffa,
129 | LastModifierEntryID = 0x3ffb,
130 | SentRepresentingFlags = 0x401a,
131 | SenderSMTPAddress = 0x5d01,
132 | BodyPlainText2 = 0x6619,
133 | AttachmentLTPRowID = 0x67F2,
134 | AttachmentLTPRowVer = 0x67F3,
135 | BodyPlainText3 = 0x8008,
136 | ContentClass = 0x8009,
137 | PopAccountName = 0x800d,
138 | PopUri = 0x8011,
139 | ContentType2 = 0x8013,
140 | TransferEncoding2 = 0x8014,
141 | BodyPlainText4 = 0x8015,
142 | PopUri2 = 0x804c,
143 | PopServerName = 0x8070,
144 | ContentType = 0x8076,
145 | TransferEncoding = 0x807b,
146 | BodyPlainText5 = 0x807e,
147 | MailSoftwareName = 0x8088,
148 | PopAccountName2 = 0x808a,
149 | MailSoftwareEngine = 0x808b,
150 | }
151 | }
--------------------------------------------------------------------------------
/PSTParse/MessageLayer/Recipient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Text;
4 | using PSTParse.ListsTablesPropertiesLayer;
5 |
6 | namespace PSTParse.MessageLayer
7 | {
8 | public class Recipient
9 | {
10 | public enum RecipientType
11 | {
12 | FROM = 0x00,
13 | TO = 0x01,
14 | CC = 0x02,
15 | BCC = 0x03
16 | }
17 |
18 | public RecipientType Type;
19 | public PSTEnums.ObjectType ObjType;
20 | public bool Responsibility;
21 | public byte[] Tag;
22 | public EntryID EntryID;
23 | public string DisplayName;
24 | public string EmailAddress;
25 | public string EmailSMTPAddress;
26 | public string EmailAddressType;
27 |
28 | public Recipient(TCRowMatrixData row)
29 | {
30 | foreach (var exProp in row)
31 | {
32 | var data = exProp.Data ?? new byte[0];
33 | switch (exProp.ID)
34 | {
35 | case MessageProperty.RecipientType:
36 | Type = (RecipientType)BitConverter.ToUInt32(data, 0);
37 | break;
38 | case MessageProperty.RecipientResponsibility:
39 | Responsibility = data.Any() ? data[0] == 0x01 : false;
40 | break;
41 | case MessageProperty.RecordKey:
42 | Tag = data;
43 | break;
44 | case MessageProperty.RecipientObjType:
45 | ObjType = (PSTEnums.ObjectType)BitConverter.ToUInt32(data, 0);
46 | break;
47 | case MessageProperty.RecipientEntryID:
48 | EntryID = new EntryID(data);
49 | break;
50 | case MessageProperty.DisplayName:
51 | DisplayName = Encoding.Unicode.GetString(data);
52 | break;
53 | case MessageProperty.AddressType:
54 | EmailAddressType = Encoding.Unicode.GetString(data);
55 | break;
56 | case MessageProperty.AddressName:
57 | EmailAddress = Encoding.Unicode.GetString(data);
58 | break;
59 | case MessageProperty.SMTPAddress:
60 | EmailSMTPAddress = Encoding.Unicode.GetString(data);
61 | break;
62 | default:
63 | break;
64 | }
65 | }
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/PSTParse/MessageLayer/Recipients.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace PSTParse.MessageLayer
4 | {
5 | public class Recipients
6 | {
7 | public List To { get; } = new List();
8 | public List CC { get; } = new List();
9 | public List BCC { get; } = new List();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/PSTParse/MessageLayer/unused/MessagePropertyTypes.cs:
--------------------------------------------------------------------------------
1 | // using PSTParse.ListsTablesPropertiesLayer;
2 | // using System;
3 | // using System.Text;
4 |
5 | // namespace PSTParse.MessageLayer
6 | // {
7 | // public static class MessagePropertyTypes
8 | // {
9 | // public static string PropertyToString(bool unicode, ExchangeProperty prop, bool enforceMaxLength = false)
10 | // {
11 | // int maxStringBytes = enforceMaxLength ? 2048 : Int32.MaxValue;
12 | // try
13 | // {
14 | // if (prop.Type == ExchangeProperty.PropType.Binary && prop.Data.Length > 0)
15 | // {
16 | // return Encoding.ASCII.GetString(prop.Data, 0, prop.Data.Length);
17 | // }
18 | // else if (prop.Type == ExchangeProperty.PropType.Boolean && prop.Data.Length > 0)
19 | // {
20 | // // since it's little-endian, we can just take the value of the first byte,
21 | // // regardless of the total width of the value.
22 | // return (prop.Data[0] != 0).ToString();
23 | // }
24 | // else if (prop.Type == ExchangeProperty.PropType.Currency)
25 | // {
26 | // }
27 | // else if (prop.Type == ExchangeProperty.PropType.ErrorCode)
28 | // {
29 | // }
30 | // else if (prop.Type == ExchangeProperty.PropType.Floating32 && prop.Data.Length >= 4)
31 | // {
32 | // return BitConverter.ToSingle(prop.Data, 0).ToString();
33 | // }
34 | // else if (prop.Type == ExchangeProperty.PropType.Floating64 && prop.Data.Length >= 8)
35 | // {
36 | // return BitConverter.ToDouble(prop.Data, 0).ToString();
37 | // }
38 | // else if (prop.Type == ExchangeProperty.PropType.FloatingTime && prop.Data.Length >= 8)
39 | // {
40 | // return DateTime.FromBinary(BitConverter.ToInt64(prop.Data, 0)).ToString();
41 | // }
42 | // else if (prop.Type == ExchangeProperty.PropType.Guid && prop.Data.Length >= 16)
43 | // {
44 | // return (new Guid(prop.Data)).ToString();
45 | // }
46 | // else if (prop.Type == ExchangeProperty.PropType.Integer16 && prop.Data.Length >= 2)
47 | // {
48 | // return BitConverter.ToUInt16(prop.Data, 0).ToString();
49 | // }
50 | // else if (prop.Type == ExchangeProperty.PropType.Integer32 && prop.Data.Length >= 4)
51 | // {
52 | // return BitConverter.ToUInt32(prop.Data, 0).ToString();
53 | // }
54 | // else if (prop.Type == ExchangeProperty.PropType.Integer64 && prop.Data.Length >= 8)
55 | // {
56 | // return BitConverter.ToUInt64(prop.Data, 0).ToString();
57 | // }
58 | // else if (prop.Type == ExchangeProperty.PropType.MultipleBinary)
59 | // {
60 | // }
61 | // else if (prop.Type == ExchangeProperty.PropType.MultipleCurrency)
62 | // {
63 | // }
64 | // else if (prop.Type == ExchangeProperty.PropType.MultipleFloating32)
65 | // {
66 | // }
67 | // else if (prop.Type == ExchangeProperty.PropType.MultipleFloating64)
68 | // {
69 | // }
70 | // else if (prop.Type == ExchangeProperty.PropType.MultipleFloatingTime)
71 | // {
72 | // }
73 | // else if (prop.Type == ExchangeProperty.PropType.MultipleGuid)
74 | // {
75 | // }
76 | // else if (prop.Type == ExchangeProperty.PropType.MultipleInteger16)
77 | // {
78 | // }
79 | // else if (prop.Type == ExchangeProperty.PropType.MultipleInteger32)
80 | // {
81 | // }
82 | // else if (prop.Type == ExchangeProperty.PropType.MultipleInteger64)
83 | // {
84 | // }
85 | // else if (prop.Type == ExchangeProperty.PropType.MultipleString && prop.Data.Length > 8)
86 | // {
87 | // uint numStrings = BitConverter.ToUInt32(prop.Data, 0);
88 | // // screw it, just render the first string, up until the end of the data.
89 | // return unicode
90 | // ? Encoding.Unicode.GetString(prop.Data, 8, Math.Min(maxStringBytes, prop.Data.Length - 8))
91 | // : Encoding.ASCII.GetString(prop.Data, 8, Math.Min(maxStringBytes, prop.Data.Length - 8));
92 | // }
93 | // else if (prop.Type == ExchangeProperty.PropType.MultipleString8)
94 | // {
95 | // }
96 | // else if (prop.Type == ExchangeProperty.PropType.MultipleTime)
97 | // {
98 | // }
99 | // else if (prop.Type == ExchangeProperty.PropType.Restriction)
100 | // {
101 | // }
102 | // else if (prop.Type == ExchangeProperty.PropType.RuleAction)
103 | // {
104 | // }
105 | // else if (prop.Type == ExchangeProperty.PropType.ServerId)
106 | // {
107 | // }
108 | // else if (prop.Type == ExchangeProperty.PropType.String)
109 | // {
110 | // return unicode
111 | // ? Encoding.Unicode.GetString(prop.Data, 0, Math.Min(maxStringBytes, prop.Data.Length))
112 | // : Encoding.ASCII.GetString(prop.Data, 0, Math.Min(maxStringBytes, prop.Data.Length));
113 | // }
114 | // else if (prop.Type == ExchangeProperty.PropType.String8)
115 | // {
116 | // return Encoding.ASCII.GetString(prop.Data, 0, Math.Min(maxStringBytes, prop.Data.Length));
117 | // }
118 | // else if (prop.Type == ExchangeProperty.PropType.Time && prop.Data.Length >= 8)
119 | // {
120 | // return DateTime.FromFileTimeUtc(BitConverter.ToInt64(prop.Data, 0)).ToString();
121 | // }
122 |
123 | // // If we fall through to here, then just try to render it as ascii...
124 | // Encoding.ASCII.GetString(prop.Data, 0, Math.Min(maxStringBytes, prop.Data.Length));
125 |
126 | // }
127 | // catch { }
128 | // return "";
129 | // }
130 | // }
131 | // }
--------------------------------------------------------------------------------
/PSTParse/MessageLayer/unused/NAMEID.cs:
--------------------------------------------------------------------------------
1 | // using PSTParse.Utilities;
2 | // using System;
3 |
4 | // namespace PSTParse.MessageLayer
5 | // {
6 | // public class NAMEID
7 | // {
8 | // public uint PropertyID;
9 | // public bool PropertyIDStringOffset;
10 | // public Guid Guid;
11 | // public ushort PropIndex;
12 |
13 | // public NAMEID(byte[] bytes, int offset, NamedToPropertyLookup lookup)
14 | // {
15 | // this.PropertyID = BitConverter.ToUInt32(bytes, offset);
16 | // this.PropertyIDStringOffset = (bytes[offset + 4] & 0x1) == 1;
17 | // var guidType = BitConverter.ToUInt16(bytes, offset + 4) >>1;
18 | // if (guidType == 1)
19 | // {
20 | // this.Guid = new Guid("00020328-0000-0000-C000-000000000046");//PS-MAPI
21 | // } else if (guidType == 2)
22 | // {
23 | // this.Guid = new Guid("00020329-0000-0000-C000-000000000046");//PS_PUBLIC_STRINGS
24 | // } else
25 | // {
26 | // this.Guid = new Guid(lookup.GUIDs.RangeSubset((guidType - 3)*16, 16));
27 | // }
28 |
29 | // this.PropIndex = (ushort)(0x8000 + BitConverter.ToUInt16(bytes, offset + 6));
30 | // }
31 | // }
32 | // }
33 |
--------------------------------------------------------------------------------
/PSTParse/MessageLayer/unused/NamedProperty.cs:
--------------------------------------------------------------------------------
1 | // using System;
2 | // using System.Collections.Generic;
3 | // using System.Linq;
4 | // using System.Text;
5 |
6 | // namespace PSTParse.MessageLayer
7 | // {
8 | // public class NamedProperty
9 | // {
10 | // }
11 | // }
12 |
--------------------------------------------------------------------------------
/PSTParse/MessageLayer/unused/NamedToPropertyLookup.cs:
--------------------------------------------------------------------------------
1 | // using System.Collections.Generic;
2 | // using PSTParse.ListsTablesPropertiesLayer;
3 |
4 | // namespace PSTParse.MessageLayer
5 | // {
6 | // public class NamedToPropertyLookup
7 | // {
8 | // private const ulong NodeId = 0x61;
9 |
10 | // public PropertyContext PC { get; set; }
11 | // public Dictionary Lookup { get; set; }
12 |
13 | // internal byte[] GUIDs { get; set; }
14 | // internal byte[] Entries { get; set; }
15 | // internal byte[] String { get; set; }
16 |
17 | // public NamedToPropertyLookup(PSTFile pst)
18 | // {
19 |
20 | // PC = new PropertyContext(NodeId, pst);
21 | // GUIDs = PC.Properties[(MessageProperty)0x0002].Data;
22 | // Entries = PC.Properties[(MessageProperty)0x0003].Data;
23 | // String = PC.Properties[(MessageProperty)0x0004].Data;
24 |
25 | // Lookup = new Dictionary();
26 | // for (int i = 0; i < Entries.Length; i += 8)
27 | // {
28 | // var cur = new NAMEID(Entries, i, this);
29 | // Lookup.Add(cur.PropIndex, cur);
30 | // }
31 | // }
32 | // }
33 | // }
34 |
--------------------------------------------------------------------------------
/PSTParse/MessageLayer/unused/PropType.cs:
--------------------------------------------------------------------------------
1 | // using System;
2 | // using System.Collections.Generic;
3 | // using System.Linq;
4 | // using System.Text;
5 |
6 | // namespace PSTParse.MessageLayer
7 | // {
8 | // public enum PropType
9 | // {
10 | // PtypInteger16 = 0x0002,
11 | // PtypInteger32 = 0x0003,
12 | // PtypFloating32 = 0x0004,
13 | // PtypFloating64 = 0x0005,
14 | // PtypCurrency = 0x0006,
15 | // PtypFloatingTime = 0x0007,
16 | // PtypErrorCode = 0x000A,
17 | // PtypBoolean = 0x000B,
18 | // PtypInteger64 = 0x0014,
19 | // PtypString = 0x001F,
20 | // PtypString8 = 0x001E,
21 | // PtypTime = 0x0040,
22 | // PtypGuid = 0x0048,
23 | // PtypServerId = 0x00FB,
24 | // PtypRestriction = 0x00FD,
25 | // PtypRuleAction = 0x00FE,
26 | // PtypBinary = 0x0102,
27 | // PtypMultipleInteger16 = 0x1002,
28 | // PtypMultipleInteger32 = 0x1003,
29 | // PtypMultipleFloating32 = 0x1004,
30 | // PtypMultipleFloating64 = 0x1005,
31 | // PtypMultipleCurrency = 0x1006,
32 | // PtypMultipleFloatingTime = 0x1007,
33 | // PtypMultipleInteger64 = 0x1014,
34 | // PtypMultipleString = 0x101F,
35 | // PtypMultipleString8 = 0x101E,
36 | // PtypMultipleTime = 0x1040,
37 | // PtypMultipleGuid = 0x1048,
38 | // PtypMultipleBinary = 0x1102,
39 | // PtypUnspecified = 0x0000,
40 | // PtypNull = 0x0001,
41 | // PtypObject = 0x000D
42 | // }
43 | // }
44 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/BBTENTRY.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace PSTParse.NodeDatabaseLayer
4 | {
5 | ///
6 | /// Leaf Block B Tree Entry.
7 | /// BBTENTRY records contain information about blocks and are found in BTPAGES with cLevel equal to 0, with the ptype of "ptypeBBT".
8 | /// These are the leaf entries of the BBT.
9 | /// As noted in section 2.2.2.7.7.1, these structures might not be tightly packed and the cbEnt field of the BTPAGE SHOULD be used to iterate over the entries.
10 | ///
11 | public class BBTENTRY : BTPAGEENTRY
12 | {
13 | public BREF BREF { get; }
14 | ///
15 | /// (CB)
16 | /// The count of bytes of the raw data contained in the block referenced by BREF excluding the block trailer and alignment padding, if any.
17 | ///
18 | public ushort BlockByteCount { get; }
19 | ///
20 | /// (CRef)
21 | /// Reference count indicating the count of references to this block.
22 | /// See section 2.2.2.7.7.3.1 regarding how reference counts work.
23 | ///
24 | public ushort RefCount { get; }
25 | public ulong Key => BREF.BID;
26 | public bool Internal => BREF.IsInternal;
27 |
28 | public BBTENTRY(byte[] bytes)
29 | {
30 | BREF = new BREF(bytes);
31 | /*this.BREF = new BREF_UNICODE
32 | {BID_raw = BitConverter.ToUInt64(bytes, 0), ByteIndex = BitConverter.ToUInt64(bytes, 8)};*/
33 | BlockByteCount = BitConverter.ToUInt16(bytes, 16);
34 | RefCount = BitConverter.ToUInt16(bytes, 18);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/BREF.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace PSTParse.NodeDatabaseLayer
4 | {
5 | ///
6 | /// Block Reference (BREF)
7 | /// A record that maps a BID to its absolute file offset location.
8 | ///
9 | public class BREF
10 | {
11 | ///
12 | /// Block ID (BID)
13 | ///
14 | public ulong BID { get; }
15 | ///
16 | /// Byte Index (IB) 64 bits.
17 | /// An absolute offset within the PST file with respect to the beginning of the file.
18 | ///
19 | public ulong IB { get; }
20 | public bool IsInternal => (BID & 0x02) > 0;
21 |
22 | public BREF(byte[] bref, int offset = 0)
23 | {
24 | BID = BitConverter.ToUInt64(bref, offset);
25 | BID = BID & 0xfffffffffffffffe;
26 | IB = BitConverter.ToUInt64(bref, offset + 8);
27 |
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/BTENTRY.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | namespace PSTParse.NodeDatabaseLayer
5 | {
6 | ///
7 | /// BTENTRY records contain a key value (NID or BID) and a reference to a child BTPAGE page in the BTree.
8 | ///
9 | public class BTENTRY : BTPAGEENTRY
10 | {
11 | ///
12 | /// The key value associated with this BTENTRY.
13 | /// All the entries in the child BTPAGE referenced by BREF have key values greater than or equal to this key value.
14 | /// The btkey is either an NID (zero extended to 8 bytes for Unicode PSTs) or a BID, depending on the ptype of the page.
15 | ///
16 | public ulong BtKey { get; }
17 | ///
18 | /// BREF structure (section 2.2.2.4) that points to the child BTPAGE.
19 | ///
20 | public BREF BREF { get; }
21 |
22 | public BTENTRY(byte[] bytes)
23 | {
24 | BtKey = BitConverter.ToUInt64(bytes, 0);
25 | BREF = new BREF(bytes.Skip(8).Take(16).ToArray());
26 | /*this.BREF = new BREF_UNICODE
27 | {BID_raw = BitConverter.ToUInt64(bytes, 8), ByteIndex = BitConverter.ToUInt64(bytes, 16)};*/
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/BTPAGEENTRY.cs:
--------------------------------------------------------------------------------
1 | namespace PSTParse.NodeDatabaseLayer
2 | {
3 | public interface BTPAGEENTRY { }
4 | }
5 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/BTPage.cs:
--------------------------------------------------------------------------------
1 | using PSTParse.Utilities;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | namespace PSTParse.NodeDatabaseLayer
6 | {
7 | ///
8 | /// B-Tree Page (BTPage)
9 | /// Implements a generic BTree using 512-byte pages.
10 | ///
11 | public class BTPage
12 | {
13 | ///
14 | /// cEnt
15 | /// The number of BTree entries stored in the page data.
16 | ///
17 | private readonly int _numEntries;
18 | ///
19 | /// cEntMax
20 | /// The maximum number of entries that can fit inside the page data.
21 | ///
22 | private readonly int _maxEntries;
23 | private readonly int _cbEnt;
24 | private readonly int _cLevel;
25 | //privatreadonly e bool _isNBT;
26 | private readonly PageTrailer _trailer;
27 | private readonly BREF _bRef;
28 |
29 | public List Entries { get; }
30 | public List InternalChildren { get; }
31 | public bool IsNode => _trailer.PageType == PageType.NBT;
32 | public bool IsBlock => _trailer.PageType == PageType.BBT;
33 | public ulong BID => _trailer.BID;
34 |
35 | public BTPage(byte[] pageData, BREF bRef, PSTFile pst)
36 | {
37 | _bRef = bRef;
38 | InternalChildren = new List();
39 | _numEntries = pageData[488];
40 | _maxEntries = pageData[489];
41 | _cbEnt = pageData[490];
42 | _cLevel = pageData[491];
43 | _trailer = new PageTrailer(pageData.RangeSubset(496, 16));
44 |
45 | Entries = new List();
46 | for (var i = 0; i < _numEntries; i++)
47 | {
48 | var curEntryBytes = pageData.RangeSubset(i * _cbEnt, _cbEnt);
49 | if (_cLevel == 0)
50 | {
51 | if (_trailer.PageType == PageType.NBT)
52 | Entries.Add(new NBTENTRY(curEntryBytes));
53 | else
54 | {
55 | var curEntry = new BBTENTRY(curEntryBytes);
56 | Entries.Add(curEntry);
57 | }
58 | }
59 | else
60 | {
61 | //btentries
62 | var entry = new BTENTRY(curEntryBytes);
63 | Entries.Add(entry);
64 | using (var view = pst.PSTMMF.CreateViewAccessor((long)entry.BREF.IB, 512))
65 | {
66 | var bytes = new byte[512];
67 | view.ReadArray(0, bytes, 0, 512);
68 | InternalChildren.Add(new BTPage(bytes, entry.BREF, pst));
69 | }
70 | }
71 | }
72 | }
73 |
74 | public BBTENTRY GetBIDBBTEntry(ulong BID)
75 | {
76 | int ii = 0;
77 | if (BID % 2 == 1)
78 | ii++;
79 | BID = BID & 0xfffffffffffffffe;
80 | for (int i = 0; i < Entries.Count; i++)
81 | {
82 | var entry = Entries[i];
83 | if (i == Entries.Count - 1)
84 | {
85 |
86 | if (entry is BTENTRY)
87 | return InternalChildren[i].GetBIDBBTEntry(BID);
88 | else
89 | {
90 | var temp = entry as BBTENTRY;
91 | if (BID == temp.Key)
92 | return temp;
93 | }
94 |
95 | }
96 | else
97 | {
98 | var entry2 = Entries[i + 1];
99 | if (entry is BTENTRY)
100 | {
101 | var cur = entry as BTENTRY;
102 | var next = entry2 as BTENTRY;
103 | if (BID >= cur.BtKey && BID < next.BtKey)
104 | return InternalChildren[i].GetBIDBBTEntry(BID);
105 | }
106 | else if (entry is BBTENTRY)
107 | {
108 | var cur = entry as BBTENTRY;
109 | if (BID == cur.Key)
110 | return cur;
111 | }
112 | }
113 | }
114 | return null;
115 | }
116 |
117 | public Tuple GetNIDBID(ulong NID)
118 | {
119 | var isBTEntry = Entries[0] is BTENTRY;
120 | for (int i = 0; i < Entries.Count; i++)
121 | {
122 | if (i == Entries.Count - 1)
123 | {
124 | if (isBTEntry)
125 | return InternalChildren[i].GetNIDBID(NID);
126 | var cur = Entries[i] as NBTENTRY;
127 | return new Tuple(cur.BID_Data, cur.BID_SUB);
128 | }
129 |
130 | var curEntry = Entries[i];
131 | var nextEntry = Entries[i + 1];
132 | if (isBTEntry)
133 | {
134 | var cur = curEntry as BTENTRY;
135 | var next = nextEntry as BTENTRY;
136 | if (NID >= cur.BtKey && NID < next.BtKey)
137 | return InternalChildren[i].GetNIDBID(NID);
138 | }
139 | else
140 | {
141 | var cur = curEntry as NBTENTRY;
142 | if (NID == cur.NID)
143 | return new Tuple(cur.BID_Data, cur.BID_SUB);
144 | }
145 | }
146 | return new Tuple(0, 0);
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/BlockBO.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 |
6 | namespace PSTParse.NodeDatabaseLayer
7 | {
8 | public static class BlockBO
9 | {
10 | public static NodeDataDTO GetNodeData(ulong nid, PSTFile pst)
11 | {
12 | var nodeBIDs = pst.GetNodeBIDs(nid);
13 | var blockBBT_Entry = pst.GetBlockBBTEntry(nodeBIDs.Item1);
14 | var mainData = GetBBTEntryData(blockBBT_Entry, pst);
15 | var subNodeData = new Dictionary();
16 |
17 | if (nodeBIDs.Item2 != 0)
18 | subNodeData = GetSubNodeData(pst.GetBlockBBTEntry(nodeBIDs.Item2), pst);
19 |
20 | return new NodeDataDTO { NodeData = mainData, SubNodeData = subNodeData };
21 | }
22 |
23 | public static Dictionary GetSubNodeData(ulong nid, PSTFile pst, int take = int.MaxValue)
24 | {
25 | var nodeBIDs = pst.GetNodeBIDs(nid);
26 |
27 | Dictionary subNodeData;
28 | if (nodeBIDs.Item2 == 0)
29 | subNodeData = new Dictionary();
30 | else
31 | subNodeData = GetSubNodeData(pst.GetBlockBBTEntry(nodeBIDs.Item2), pst, take);
32 |
33 | return subNodeData;
34 | }
35 |
36 | private static Dictionary GetSubNodeData(BBTENTRY entry, PSTFile pst, int take = int.MaxValue)
37 | {
38 | var allData = GetBBTEntryData(entry, pst);
39 | var dataBlock = allData[0];
40 | if (entry.Internal)
41 | {
42 | var type = dataBlock.Data[0];
43 | var cLevel = dataBlock.Data[1];
44 | if (cLevel == 0) //SLBlock, no intermediate
45 | {
46 | return GetSLBlockData(new SLBLOCK(dataBlock), pst, take);
47 | }
48 | else //SIBlock
49 | {
50 | return GetSIBlockData(new SIBLOCK(dataBlock), pst);
51 | }
52 | }
53 | else
54 | {
55 | throw new Exception("Whoops");
56 | }
57 | }
58 |
59 | private static Dictionary GetSIBlockData(SIBLOCK siblock, PSTFile pst)
60 | {
61 | var ret = new Dictionary();
62 |
63 | foreach (var entry in siblock.Entries)
64 | {
65 | var curSLBlockBBT = pst.GetBlockBBTEntry(entry.SLBlockBID);
66 | var slblock = new SLBLOCK(GetBBTEntryData(curSLBlockBBT, pst)[0]);
67 | var data = GetSLBlockData(slblock, pst);
68 | foreach (var item in data)
69 | ret.Add(item.Key, item.Value);
70 | }
71 |
72 | return ret;
73 | }
74 |
75 | ///
76 | /// Gets all the data for an SL block.
77 | /// An SL block points directly to all the immediate subnodes
78 | ///
79 | private static Dictionary GetSLBlockData(SLBLOCK slblock, PSTFile pst, int take = int.MaxValue)
80 | {
81 | var ret = new Dictionary();
82 | foreach (var entry in slblock.Entries.Take(take))
83 | {
84 | //this data should represent the main data part of the subnode
85 | var data = GetBBTEntryData(pst.GetBlockBBTEntry(entry.SubNodeBID), pst);
86 | var cur = new NodeDataDTO { NodeData = data };
87 | ret.Add(entry.SubNodeNID, cur);
88 |
89 | //see if there are sub nodes of this current sub node
90 | if (entry.SubSubNodeBID != 0)
91 | //if there are subnodes, treat them like any other subnode
92 | cur.SubNodeData = GetSubNodeData(pst.GetBlockBBTEntry(entry.SubSubNodeBID), pst);
93 | }
94 | return ret;
95 | }
96 |
97 | public static NodeDataDTO GetNodeData(NBTENTRY entry, PSTFile pst)
98 | {
99 | var mainData = BlockBO.GetBBTEntryData(pst.GetBlockBBTEntry(entry.BID_Data), pst);
100 | if (entry.BID_SUB != 0)
101 | {
102 | var subnodeData = BlockBO.GetSubNodeData(pst.GetBlockBBTEntry(entry.BID_SUB), pst);
103 | return new NodeDataDTO { NodeData = mainData, SubNodeData = subnodeData };
104 | }
105 |
106 | return new NodeDataDTO { NodeData = mainData, SubNodeData = null };
107 | }
108 |
109 | public static NodeDataDTO GetNodeData(SLENTRY entry, PSTFile pst)
110 | {
111 | var mainData = BlockBO.GetBBTEntryData(pst.GetBlockBBTEntry(entry.SubNodeBID), pst);
112 | if (entry.SubSubNodeBID != 0)
113 | {
114 | var subNodeData = BlockBO.GetSubNodeData(pst.GetBlockBBTEntry(entry.SubSubNodeBID), pst);
115 | return new NodeDataDTO { NodeData = mainData, SubNodeData = subNodeData };
116 | }
117 |
118 | return new NodeDataDTO { NodeData = mainData, SubNodeData = null };
119 | }
120 |
121 | ///
122 | /// For a given bbt entry, retrieve the raw bytes associated with the BID.
123 | /// This includes retrieving data trees via xblocks.
124 | ///
125 | public static List GetBBTEntryData(BBTENTRY entry, PSTFile pst)
126 | {
127 | if (entry == null)
128 | throw new InvalidDataException("Failed while parsing BBTEntry, the data block was invalid, try running a PST repair");
129 |
130 | var dataSize = entry.BlockByteCount;
131 | var blockSize = entry.BlockByteCount + 16;
132 | if (blockSize % 64 != 0)
133 | blockSize += 64 - (blockSize % 64);
134 | List dataBlocks;
135 |
136 | /*if (isSubNode)
137 | {
138 | using (var viewer = PSTFile.PSTMMF.CreateViewAccessor((long)entry.BREF.IB, blockSize))
139 | {
140 | var blockBytes = new byte[dataSize];
141 | viewer.ReadArray(0, blockBytes, 0, dataSize);
142 | dataBlocks = new List
143 | {new BlockDataDTO {Data = blockBytes, PstOffset = entry.BREF.IB, BBTEntry = entry}};
144 | return dataBlocks;
145 | }
146 | } else */
147 | if (entry.Internal)
148 | {
149 | using (var viewer = pst.PSTMMF.CreateViewAccessor((long)entry.BREF.IB, blockSize))
150 | {
151 | var blockBytes = new byte[dataSize];
152 | viewer.ReadArray(0, blockBytes, 0, dataSize);
153 |
154 | var trailerBytes = new byte[16];
155 | viewer.ReadArray(blockSize - 16, trailerBytes, 0, 16);
156 | var trailer = new BlockTrailer(trailerBytes, 0);
157 |
158 | var dataBlockDTO = new BlockDataDTO
159 | {
160 | Data = blockBytes,
161 | PstOffset = entry.BREF.IB,
162 | CRCOffset = (uint)((long)entry.BREF.IB + (blockSize - 12)),
163 | BBTEntry = entry
164 | };
165 | var type = blockBytes[0];
166 | var level = blockBytes[1];
167 |
168 | if (type == 2) //si or sl entry
169 | {
170 | return new List { dataBlockDTO };
171 | }
172 | else if (type == 1)
173 | {
174 | if (blockBytes[1] == 0x01) //XBLOCK
175 | {
176 | var xblock = new XBLOCK(dataBlockDTO);
177 | return BlockBO.GetXBlockData(xblock, pst);
178 |
179 | }
180 | else //XXBLOCK
181 | {
182 | var xxblock = new XXBLOCK(dataBlockDTO);
183 | return BlockBO.GetXXBlockData(xxblock, pst);
184 | }
185 | }
186 | else
187 | {
188 | throw new NotImplementedException();
189 | }
190 | }
191 | }
192 | else
193 | {
194 | using (var viewer = pst.PSTMMF.CreateViewAccessor((long)entry.BREF.IB, blockSize))
195 | {
196 | var dataBytes = new byte[dataSize];
197 | viewer.ReadArray(0, dataBytes, 0, dataSize);
198 |
199 | var trailerBytes = new byte[16];
200 | viewer.ReadArray(blockSize - 16, trailerBytes, 0, 16);
201 | var trailer = new BlockTrailer(trailerBytes, 0);
202 | dataBlocks = new List
203 | {
204 | new BlockDataDTO
205 | {
206 | Data = dataBytes,
207 | PstOffset = entry.BREF.IB,
208 | CRC32 = trailer.CRC,
209 | CRCOffset = (uint) (blockSize -12),
210 | BBTEntry = entry
211 | }
212 | };
213 | }
214 | }
215 |
216 | for (int i = 0; i < dataBlocks.Count; i++)
217 | {
218 | var temp = dataBlocks[i].Data;
219 | DatatEncoder.CryptPermute(temp, temp.Length, false, pst.Header.EncodingAlgotihm);
220 | }
221 | return dataBlocks;
222 | }
223 |
224 | private static List GetXBlockData(XBLOCK xblock, PSTFile pst)
225 | {
226 | var ret = new List();
227 | foreach (var bid in xblock.BIDEntries)
228 | {
229 | var bbtEntry = pst.GetBlockBBTEntry(bid);
230 | ret.AddRange(BlockBO.GetBBTEntryData(bbtEntry, pst));
231 | }
232 | return ret;
233 | }
234 |
235 | private static List GetXXBlockData(XXBLOCK xxblock, PSTFile pst)
236 | {
237 | var ret = new List();
238 | foreach (var bid in xxblock.XBlockBIDs)
239 | {
240 | var bbtEntry = pst.GetBlockBBTEntry(bid);
241 | var curXblockData = BlockBO.GetBBTEntryData(bbtEntry, pst);
242 | //var curXblockData = BlockBO.GetXBlockData(curXblock);
243 | foreach (var block in curXblockData)
244 | ret.Add(block);
245 | }
246 | return ret;
247 | }
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/BlockDataDTO.cs:
--------------------------------------------------------------------------------
1 | namespace PSTParse.NodeDatabaseLayer
2 | {
3 | public class BlockDataDTO
4 | {
5 | public BlockDataDTO Parent { get; set; }
6 | public byte[] Data { get; set; }
7 | public ulong PstOffset { get; set; }
8 | public uint CRC32 { get; set; }
9 | public uint CRCOffset { get; set; }
10 | public BBTENTRY BBTEntry { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/BlockTrailer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace PSTParse.NodeDatabaseLayer
4 | {
5 | ///
6 | /// Stores metadata for a block at the end of a block
7 | ///
8 | public class BlockTrailer
9 | {
10 | ///
11 | /// The amount of data, in bytes, contained within the data section of the block (CB)
12 | ///
13 | public uint DataSize { get; set; }
14 | ///
15 | /// Block signature (wSig)
16 | /// See section 5.5 for the algorithm to calculate the block signature.
17 | ///
18 | public uint WSig { get; set; }
19 | ///
20 | /// 32-bit CRC of the CB bytes of raw data
21 | ///
22 | public uint CRC { get; set; }
23 | ///
24 | /// The Block ID of the data block
25 | ///
26 | public ulong BID_Raw { get; set; }
27 |
28 | public BlockTrailer(byte[] bytes, int offset)
29 | {
30 | DataSize = BitConverter.ToUInt16(bytes, offset);
31 | WSig = BitConverter.ToUInt16(bytes, 2 + offset);
32 | CRC = BitConverter.ToUInt32(bytes, 4 + offset);
33 | BID_Raw = BitConverter.ToUInt64(bytes, 8 + offset);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/DatatEncoder.cs:
--------------------------------------------------------------------------------
1 | using static PSTParse.PSTHeader;
2 |
3 | namespace PSTParse.NodeDatabaseLayer
4 | {
5 | public static class DatatEncoder
6 | {
7 | private static readonly byte[] mpbbCrypt =
8 | {
9 | 65, 54, 19, 98, 168, 33, 110, 187,
10 | 244, 22, 204, 4, 127, 100, 232, 93,
11 | 30, 242, 203, 42, 116, 197, 94, 53,
12 | 210, 149, 71, 158, 150, 45, 154, 136,
13 | 76, 125, 132, 63, 219, 172, 49, 182,
14 | 72, 95, 246, 196, 216, 57, 139, 231,
15 | 35, 59, 56, 142, 200, 193, 223, 37,
16 | 177, 32, 165, 70, 96, 78, 156, 251,
17 | 170, 211, 86, 81, 69, 124, 85, 0,
18 | 7, 201, 43, 157, 133, 155, 9, 160,
19 | 143, 173, 179, 15, 99, 171, 137, 75,
20 | 215, 167, 21, 90, 113, 102, 66, 191,
21 | 38, 74, 107, 152, 250, 234, 119, 83,
22 | 178, 112, 5, 44, 253, 89, 58, 134,
23 | 126, 206, 6, 235, 130, 120, 87, 199,
24 | 141, 67, 175, 180, 28, 212, 91, 205,
25 | 226, 233, 39, 79, 195, 8, 114, 128,
26 | 207, 176, 239, 245, 40, 109, 190, 48,
27 | 77, 52, 146, 213, 14, 60, 34, 50,
28 | 229, 228, 249, 159, 194, 209, 10, 129,
29 | 18, 225, 238, 145, 131, 118, 227, 151,
30 | 230, 97, 138, 23, 121, 164, 183, 220,
31 | 144, 122, 92, 140, 2, 166, 202, 105,
32 | 222, 80, 26, 17, 147, 185, 82, 135,
33 | 88, 252, 237, 29, 55, 73, 27, 106,
34 | 224, 41, 51, 153, 189, 108, 217, 148,
35 | 243, 64, 84, 111, 240, 198, 115, 184,
36 | 214, 62, 101, 24, 68, 31, 221, 103,
37 | 16, 241, 12, 25, 236, 174, 3, 161,
38 | 20, 123, 169, 11, 255, 248, 163, 192,
39 | 162, 1, 247, 46, 188, 36, 104, 117,
40 | 13, 254, 186, 47, 181, 208, 218, 61,
41 | 20, 83, 15, 86, 179, 200, 122, 156,
42 | 235, 101, 72, 23, 22, 21, 159, 2,
43 | 204, 84, 124, 131, 0, 13, 12, 11,
44 | 162, 98, 168, 118, 219, 217, 237, 199,
45 | 197, 164, 220, 172, 133, 116, 214, 208,
46 | 167, 155, 174, 154, 150, 113, 102, 195,
47 | 99, 153, 184, 221, 115, 146, 142, 132,
48 | 125, 165, 94, 209, 93, 147, 177, 87,
49 | 81, 80, 128, 137, 82, 148, 79, 78,
50 | 10, 107, 188, 141, 127, 110, 71, 70,
51 | 65, 64, 68, 1, 17, 203, 3, 63,
52 | 247, 244, 225, 169, 143, 60, 58, 249,
53 | 251, 240, 25, 48, 130, 9, 46, 201,
54 | 157, 160, 134, 73, 238, 111, 77, 109,
55 | 196, 45, 129, 52, 37, 135, 27, 136,
56 | 170, 252, 6, 161, 18, 56, 253, 76,
57 | 66, 114, 100, 19, 55, 36, 106, 117,
58 | 119, 67, 255, 230, 180, 75, 54, 92,
59 | 228, 216, 53, 61, 69, 185, 44, 236,
60 | 183, 49, 43, 41, 7, 104, 163, 14,
61 | 105, 123, 24, 158, 33, 57, 190, 40,
62 | 26, 91, 120, 245, 35, 202, 42, 176,
63 | 175, 62, 254, 4, 140, 231, 229, 152,
64 | 50, 149, 211, 246, 74, 232, 166, 234,
65 | 233, 243, 213, 47, 112, 32, 242, 31,
66 | 5, 103, 173, 85, 16, 206, 205, 227,
67 | 39, 59, 218, 186, 215, 194, 38, 212,
68 | 145, 29, 210, 28, 34, 51, 248, 250,
69 | 241, 90, 239, 207, 144, 182, 139, 181,
70 | 189, 192, 191, 8, 151, 30, 108, 226,
71 | 97, 224, 198, 193, 89, 171, 187, 88,
72 | 222, 95, 223, 96, 121, 126, 178, 138,
73 | 71, 241, 180, 230, 11, 106, 114, 72,
74 | 133, 78, 158, 235, 226, 248, 148, 83,
75 | 224, 187, 160, 2, 232, 90, 9, 171,
76 | 219, 227, 186, 198, 124, 195, 16, 221,
77 | 57, 5, 150, 48, 245, 55, 96, 130,
78 | 140, 201, 19, 74, 107, 29, 243, 251,
79 | 143, 38, 151, 202, 145, 23, 1, 196,
80 | 50, 45, 110, 49, 149, 255, 217, 35,
81 | 209, 0, 94, 121, 220, 68, 59, 26,
82 | 40, 197, 97, 87, 32, 144, 61, 131,
83 | 185, 67, 190, 103, 210, 70, 66, 118,
84 | 192, 109, 91, 126, 178, 15, 22, 41,
85 | 60, 169, 3, 84, 13, 218, 93, 223,
86 | 246, 183, 199, 98, 205, 141, 6, 211,
87 | 105, 92, 134, 214, 20, 247, 165, 102,
88 | 117, 172, 177, 233, 69, 33, 112, 12,
89 | 135, 159, 116, 164, 34, 76, 111, 191,
90 | 31, 86, 170, 46, 179, 120, 51, 80,
91 | 176, 163, 146, 188, 207, 25, 28, 167,
92 | 99, 203, 30, 77, 62, 75, 27, 155,
93 | 79, 231, 240, 238, 173, 58, 181, 89,
94 | 4, 234, 64, 85, 37, 81, 229, 122,
95 | 137, 56, 104, 82, 123, 252, 39, 174,
96 | 215, 189, 250, 7, 244, 204, 142, 95,
97 | 239, 53, 156, 132, 43, 21, 213, 119,
98 | 52, 73, 182, 18, 10, 127, 113, 136,
99 | 253, 157, 24, 65, 125, 147, 216, 88,
100 | 44, 206, 254, 36, 175, 222, 184, 54,
101 | 200, 161, 128, 166, 153, 152, 168, 47,
102 | 14, 129, 101, 115, 228, 194, 162, 138,
103 | 212, 225, 17, 208, 8, 139, 42, 242,
104 | 237, 154, 100, 63, 193, 108, 249, 236
105 | };
106 |
107 | public static void CryptPermute(byte[] pv, int cb, bool fEncrypt, BlockEncoding blockEncoding)
108 | {
109 | if (blockEncoding == BlockEncoding.NONE) return;
110 | if (blockEncoding == BlockEncoding.PERMUTE)
111 | {
112 | int idx = (fEncrypt ? 0 : 512);
113 | int temp = 0;
114 |
115 | for (int pvIndex = 0; pvIndex < cb; pvIndex++)
116 | {
117 | temp = pv[pvIndex];
118 | temp = temp & 0xFF;
119 | pv[pvIndex] = mpbbCrypt[temp + idx];
120 | }
121 | }
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/IBLOCK.cs:
--------------------------------------------------------------------------------
1 | namespace PSTParse.NodeDatabaseLayer
2 | {
3 | ///
4 | /// Blocks are the fundamental units of data storage at the NDB layer.
5 | /// Blocks are assigned in sizes that are multiples of 64 bytes and are aligned on 64-byte boundaries.
6 | /// The maximum size of any block is 8 kilobytes (8192 bytes).
7 | ///
8 | public interface IBLOCK { }
9 | }
10 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/NBTENTRY.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace PSTParse.NodeDatabaseLayer
4 | {
5 | ///
6 | /// NBTENTRY records contain information about nodes and are found in BTPAGES with cLevel equal to 0, with the ptype of ptypeNBT.
7 | ///
8 | public class NBTENTRY : BTPAGEENTRY
9 | {
10 | ///
11 | /// Node ID
12 | ///
13 | public ulong NID { get; set; }
14 | ///
15 | /// The Block ID of the data block for this node
16 | ///
17 | public ulong BID_Data { get; set; }
18 | ///
19 | /// The Block ID of the subnode block for this node.
20 | /// If this value is zero, a subnode block does not exist for this node.
21 | ///
22 | public ulong BID_SUB { get; set; }
23 | ///
24 | /// If this node represents a child of a Folder object defined in the Messaging Layer, then this value is nonzero and contains the NID of the parent Folder object's node.
25 | /// Otherwise, this value is zero. See section 2.2.2.7.7.4.1 for more information.
26 | ///
This field is not interpreted by any structure defined at the NDB Layer.
27 | ///
28 | public uint NID_Parent { get; set; }
29 | public ulong NID_TYPE { get; set; }
30 |
31 | public NBTENTRY(byte[] curEntryBytes)
32 | {
33 | NID = BitConverter.ToUInt64(curEntryBytes, 0);
34 | BID_Data = BitConverter.ToUInt64(curEntryBytes,8);
35 | BID_SUB = BitConverter.ToUInt64(curEntryBytes,16);
36 | NID_Parent = BitConverter.ToUInt32(curEntryBytes, 24);
37 | NID_TYPE = NID & 0x1f;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/NID.cs:
--------------------------------------------------------------------------------
1 | namespace PSTParse.NodeDatabaseLayer
2 | {
3 | ///
4 | /// Node ID (NID)
5 | ///
6 | public class NID
7 | {
8 | public enum NodeType
9 | {
10 | //heap node
11 | HID = 0x00,
12 | INTERNAL = 0x01,
13 | NORMAL_FOLDER = 0x02,
14 | SEARCH_FOLDER = 0x03,
15 | NORMAL_MESSAGE_PC = 0x03,
16 | ATTACHMENT_PC = 0x05,
17 | // queue of changed objects for search folder object
18 | SEARCH_UPDATE_QUEUE = 0x06,
19 | SEARCH_CRITERIA_OBJECT = 0x07,
20 | ASSOC_MESSAGE = 0X08,
21 | CONTENTS_TABLE_INDEX = 0X0A,
22 | //inbox
23 | RECEIVE_FOLDER_TABLE = 0X0B,
24 | //outbox
25 | OUTGOING_QUEUE_TABLE = 0X0C,
26 | HIERARCHY_TABLE = 0X0D,
27 | CONTENTS_TABLE = 0X0E,
28 | ASSOC_CONTENTS_TABLE = 0X0F,
29 | SEARCH_CONTENTS_TABLE = 0X10,
30 | ATTACHMENT_TABLE = 0X11,
31 | RECIPIENT_TABLE = 0X12,
32 | SEARCH_TABLE_INDEX = 0X13,
33 | LTP = 0X14
34 | }
35 | public static NodeType GetNodeType(ulong nid) => (NodeType)(nid & 0x1f);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/NodeDataDTO.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace PSTParse.NodeDatabaseLayer
4 | {
5 | public class NodeDataDTO
6 | {
7 | public List NodeData { get; set; }
8 | public Dictionary SubNodeData { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/PSTBTree.cs:
--------------------------------------------------------------------------------
1 | namespace PSTParse.NodeDatabaseLayer
2 | {
3 | public class PSTBTree
4 | {
5 | public BTPage Root { get; }
6 |
7 | public PSTBTree(BREF bref, PSTFile pst)
8 | {
9 | using (var viewer = pst.PSTMMF.CreateViewAccessor((long)bref.IB, 512))
10 | {
11 | var data = new byte[512];
12 | viewer.ReadArray(0, data, 0, 512);
13 | Root = new BTPage(data, bref, pst);
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/PageTrailer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace PSTParse.NodeDatabaseLayer
4 | {
5 | public enum PageType
6 | {
7 | ///
8 | /// Block B Tree
9 | ///
10 | BBT = 0x80,
11 | ///
12 | /// Node B Tree
13 | ///
14 | NBT = 0x81,
15 | FreeMap = 0x82,
16 | PageMap = 0x83,
17 | AMap = 0x84,
18 | FreePageMap = 0x85,
19 | DensityList = 0x86
20 | }
21 |
22 | ///
23 | /// A PAGETRAILER structure contains information about the page in which it is contained.
24 | /// It is present at the very end of each page in a PST file.
25 | ///
26 | public class PageTrailer
27 | {
28 | ///
29 | /// The type of data contained within the page.
30 | ///
31 | public PageType PageType { get; set; }
32 | ///
33 | /// The BID of the page's block.
34 | ///
35 | public ulong BID { get; set; }
36 |
37 | public PageTrailer(byte[] trailer)
38 | {
39 | PageType = (PageType)trailer[0];
40 | BID = BitConverter.ToUInt64(trailer, 8);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/SIBLOCK.cs:
--------------------------------------------------------------------------------
1 | using PSTParse.Utilities;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | namespace PSTParse.NodeDatabaseLayer
6 | {
7 | public class SIBLOCK : IBLOCK
8 | {
9 | public BlockDataDTO DataBlock;
10 | public UInt16 EntryCount;
11 | public List Entries;
12 |
13 | public SIBLOCK(BlockDataDTO dataBlock)
14 | {
15 | this.DataBlock = dataBlock;
16 | var type = dataBlock.Data[0];
17 | var cLevel = dataBlock.Data[1];
18 | this.EntryCount = BitConverter.ToUInt16(dataBlock.Data, 2);
19 | this.Entries = new List();
20 | for(int i =0;i < EntryCount;i++)
21 | this.Entries.Add(new SIENTRY(dataBlock.Data.RangeSubset(8 + 16*i, 16)));
22 | }
23 |
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/SIENTRY.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace PSTParse.NodeDatabaseLayer
7 | {
8 | public class SIENTRY
9 | {
10 | public ulong NextChildNID;
11 | public ulong SLBlockBID;
12 |
13 | public SIENTRY(byte[] bytes)
14 | {
15 | this.NextChildNID = BitConverter.ToUInt64(bytes, 0);
16 | this.SLBlockBID = BitConverter.ToUInt64(bytes, 8);
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/SLBLOCK.cs:
--------------------------------------------------------------------------------
1 | using PSTParse.Utilities;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | namespace PSTParse.NodeDatabaseLayer
6 | {
7 | public class SLBLOCK : IBLOCK
8 | {
9 | public BlockDataDTO BlockData;
10 | public UInt16 EntryCount;
11 | public List Entries;
12 |
13 | public SLBLOCK(BlockDataDTO blockData)
14 | {
15 | BlockData = blockData;
16 | var type = blockData.Data[0];
17 | var clevel = blockData.Data[1];
18 | EntryCount = BitConverter.ToUInt16(blockData.Data, 2);
19 | Entries = new List();
20 | for (int i = 0; i < EntryCount; i++)
21 | {
22 | Entries.Add(new SLENTRY(blockData.Data.RangeSubset(8 + 24 * i, 24)));
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/SLENTRY.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace PSTParse.NodeDatabaseLayer
7 | {
8 | public class SLENTRY
9 | {
10 | public ulong SubNodeNID;
11 | public ulong SubNodeBID;
12 | public ulong SubSubNodeBID;
13 |
14 | public SLENTRY(byte[] bytes)
15 | {
16 | this.SubNodeNID = BitConverter.ToUInt64(bytes, 0);
17 | this.SubNodeBID = BitConverter.ToUInt64(bytes, 8);
18 | this.SubSubNodeBID = BitConverter.ToUInt64(bytes, 16);
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/SpecialNIDs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace PSTParse.NodeDatabaseLayer
7 | {
8 | public static class SpecialNIDs
9 | {
10 | public static uint NID_MESSAGE_STORE = 0x21;
11 | public static uint NID_NMAE_TO_ID_MAP = 0x61;
12 | public static uint NID_ROOT_FOLDER = 0x122;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/XBLOCK.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace PSTParse.NodeDatabaseLayer
4 | {
5 | public class XBLOCK : IBLOCK
6 | {
7 | public BlockDataDTO Block;
8 | public uint BlockType;
9 | public uint HeaderLevel;
10 | public uint BIDEntryCount;
11 | public uint TotalBytes;
12 |
13 | public ulong[] BIDEntries;
14 |
15 | public XBLOCK(BlockDataDTO block)
16 | {
17 | this.Block = block;
18 | this.BlockType = block.Data[0];
19 | this.HeaderLevel = block.Data[1];
20 | this.BIDEntryCount = BitConverter.ToUInt16(block.Data, 2);
21 | this.TotalBytes = BitConverter.ToUInt32(block.Data, 4);
22 | this.BIDEntries = new ulong[BIDEntryCount];
23 | for (int i = 0; i < BIDEntryCount; i++)
24 | BIDEntries[i] = BitConverter.ToUInt64(block.Data, 8 + i*8);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/XXBLOCK.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace PSTParse.NodeDatabaseLayer
4 | {
5 | public class XXBLOCK : IBLOCK
6 | {
7 | public byte Type;
8 | public byte CLevel;
9 | public UInt16 TotalChildren;
10 | public uint TotalBytes;
11 | public BlockDataDTO Block;
12 |
13 | public ulong[] XBlockBIDs;
14 |
15 | public XXBLOCK(BlockDataDTO block)
16 | {
17 | this.Block = block;
18 |
19 | this.Type = block.Data[0];
20 | this.CLevel = block.Data[1];
21 | this.TotalChildren = BitConverter.ToUInt16(block.Data, 2);
22 | this.TotalBytes = BitConverter.ToUInt32(block.Data, 4);
23 | this.XBlockBIDs = new ulong[this.TotalChildren];
24 | for (var i = 0; i < TotalChildren; i++)
25 | this.XBlockBIDs[i] = BitConverter.ToUInt64(block.Data, 8 + 8*i);
26 |
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/unused/BID.cs:
--------------------------------------------------------------------------------
1 | // using System;
2 |
3 | // namespace PSTParse.NodeDatabaseLayer
4 | // {
5 | // ///
6 | // /// Block ID (BID)
7 | // ///
8 | // public class BID
9 | // {
10 | // public ulong BlockID { get; }
11 |
12 | // public BID(byte[] bytes, int offset = 0)
13 | // {
14 | // BlockID = BitConverter.ToUInt64(bytes, offset) & 0xfffffffffffffffe;
15 | // }
16 | // }
17 | // }
18 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/unused/BlockFactory.cs:
--------------------------------------------------------------------------------
1 | // using System;
2 | // using System.Collections.Generic;
3 | // using System.Linq;
4 | // using System.Text;
5 |
6 | // namespace PSTParse.NodeDatabaseLayer
7 | // {
8 | // public static class BlockFactory
9 | // {
10 | // public static IBLOCK GetBlock(byte[] bytes, int blockDataSize,
11 | // bool isInternal, bool isSubNode)
12 | // {
13 | // var trailerOffset = bytes.Length - 16;
14 | // var trailer = new BlockTrailer(bytes, trailerOffset);
15 |
16 |
17 |
18 | // if (!isInternal)
19 | // return new DataBlock(bytes, blockDataSize);
20 | // else
21 | // {
22 | // var bType = bytes[0];
23 | // var headerLevel = bytes[1];
24 |
25 | // if (isSubNode)
26 | // {
27 | // if (headerLevel == 0)
28 | // return new SLBLOCK(bytes);
29 | // else
30 | // return new SIBLOCK(bytes);
31 | // } else
32 | // {
33 | // if (headerLevel == 1)
34 | // return new XBLOCK(bytes);
35 | // else
36 | // return new XXBLOCK(bytes);
37 | // }
38 | // }
39 | // }
40 | // }
41 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/unused/NodeBTree.cs:
--------------------------------------------------------------------------------
1 | //namespace PSTParse.NodeDatabaseLayer
2 | //{
3 | // public class NodeBTree
4 | // {
5 | // public BREF RootLocation;
6 | // public BTPage Root;
7 |
8 | // public NodeBTree(BREF root)
9 | // {
10 | // this.RootLocation = root;
11 | // }
12 | // }
13 | //}
14 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/unused/PSTBTreeNode.cs:
--------------------------------------------------------------------------------
1 | // using System;
2 | // using System.Collections.Generic;
3 | // using System.IO.MemoryMappedFiles;
4 | // using System.Linq;
5 | // using System.Text;
6 |
7 | // namespace PSTParse.NodeDatabaseLayer
8 | // {
9 | // public class PSTBTreeNode
10 | // {
11 |
12 |
13 | // public bool Internal { get; set; }
14 | // public long Offset { get; set; }
15 | // public BTPage Page { get; set; }
16 | // private MemoryMappedFile _mmf;
17 |
18 | // public List Children { get; set; }
19 |
20 |
21 |
22 | // public PSTBTreeNode(BREF root, MemoryMappedFile pstmmf, bool isNode)
23 | // {
24 | // this.Internal = root.IsInternal;
25 | // this.Offset = (long)root.IB;
26 | // this._mmf = pstmmf;
27 | // this.Children = new List();
28 |
29 | // if (isNode || this.Internal)
30 | // {
31 | // using (var mmfview = this._mmf.CreateViewAccessor(Offset, 512))
32 | // {
33 | // var bytes = new byte[512];
34 | // mmfview.ReadArray(0, bytes, 0, 512);
35 | // this.Page = new BTPage(bytes, root);
36 | // foreach (var child in this.Page.Entries)
37 | // {
38 | // if (child is BTENTRY)
39 | // {
40 | // var cur = child as BTENTRY;
41 | // this.Children.Add(new PSTBTreeNode(cur.BREF, this._mmf, isNode));
42 | // }
43 | // else if (child is BBTENTRY)
44 | // {
45 | // var cur = child as BBTENTRY;
46 | // var dataSize = cur.BlockByteCount;
47 | // using(var mffview2 = this._mmf.CreateViewAccessor((long)cur.BREF.IB,dataSize+16))
48 | // {
49 | // var b = new byte[dataSize + 16];
50 | // mffview2.ReadArray(0, b, 0, dataSize + 16);
51 | // var block = BlockFactory.GetBlock(b, dataSize, true, false);
52 | // if (!(block is DataBlock))
53 | // this.Children.Add(new PSTBTreeNode(cur.BREF, this._mmf, isNode));
54 | // }
55 | // }
56 | // else if (child is NBTENTRY)
57 | // {
58 | // //var cur = child as NBTENTRY;
59 | // //this.Children.Add(new PSTBTreeNode(cur.));
60 | // }
61 | // }
62 | // }
63 | // }
64 | // }
65 | // }
66 |
--------------------------------------------------------------------------------
/PSTParse/NodeDatabaseLayer/unused/SubNodeDataDTO.cs:
--------------------------------------------------------------------------------
1 | // using System;
2 | // using System.Collections.Generic;
3 | // using System.Linq;
4 | // using System.Text;
5 |
6 | // namespace PSTParse.NDB
7 | // {
8 | // public class SubNodeDataDTO
9 | // {
10 | // public List Data;
11 | // //public ulong PstOffset;
12 | // public Dictionary SubNodeData;
13 | // }
14 | // }
15 |
--------------------------------------------------------------------------------
/PSTParse/PSTEnums.cs:
--------------------------------------------------------------------------------
1 | namespace PSTParse
2 | {
3 | public class PSTEnums
4 | {
5 | public enum ObjectType
6 | {
7 | STORE = 0x01,
8 | ADDRESS_BOOK = 0x02,
9 | ADDRESS_BOOK_CONTAINER = 0x04,
10 | MESSAGE_OBJECT = 0x05,
11 | MAIL_USER = 0x06,
12 | ATTACHMENT = 0x07,
13 | DISTRIBUTION_LIST = 0x08
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/PSTParse/PSTFile.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.IO.MemoryMappedFiles;
5 | using PSTParse.MessageLayer;
6 | using PSTParse.NodeDatabaseLayer;
7 | using PSTParse.ListsTablesPropertiesLayer;
8 | using System.Linq;
9 | using PSTParse.Utilities;
10 |
11 | namespace PSTParse
12 | {
13 | public class PSTFile : IDisposable
14 | {
15 | public const int MinFileSizeBytes = 1_000;
16 |
17 | public string Path { get; }
18 | public MemoryMappedFile PSTMMF { get; private set; }
19 | public PSTHeader Header { get; }
20 | public MailStore MailStore { get; }
21 | public MailFolder TopOfPST { get; }
22 | //public NamedToPropertyLookup NamedPropertyLookup { get; }
23 | public ulong Size => Header.Root.FileSizeBytes;
24 |
25 | public PSTFile(string path)
26 | {
27 | if (new FileInfo(path).Length < MinFileSizeBytes)
28 | {
29 | throw new Exception($"Failed opening PST, file size must be greater than {MinFileSizeBytes} bytes");
30 | }
31 | Path = path ?? throw new ArgumentNullException(nameof(path));
32 | PSTMMF = MemoryMappedFile.CreateFromFile(path, FileMode.Open);
33 |
34 | Header = new PSTHeader(this);
35 |
36 | /*var messageStoreData = BlockBO.GetNodeData(SpecialNIDs.NID_MESSAGE_STORE);
37 | var temp = BlockBO.GetNodeData(SpecialNIDs.NID_ROOT_FOLDER);*/
38 | MailStore = new MailStore(this);
39 |
40 | TopOfPST = new MailFolder(MailStore.RootFolder.NID, new List(), this);
41 | //NamedPropertyLookup = new NamedToPropertyLookup(this);
42 |
43 | //var temp = new TableContext(rootEntryID.NID);
44 | }
45 |
46 | public bool IsPasswordProtected()
47 | {
48 | var messageStore = new PropertyContext(SpecialNIDs.NID_MESSAGE_STORE, this);
49 | var rootDataNode = messageStore.BTH.Root.Data;
50 | const int unknown2Bytes = 2;
51 | var passwordKey = new byte[] { 0xFF, 0x67 };
52 | foreach (var entry in rootDataNode.DataEntries)
53 | {
54 | if (entry.Key.SequenceEqual(passwordKey))
55 | {
56 | var dataBlockOffset = (int)entry.DataOffset + (int)rootDataNode.Data.BlockOffset + unknown2Bytes;
57 | var slice = rootDataNode.Data.Parent.Data.Skip(dataBlockOffset).Take(4).ToList();
58 | var isProtected = !slice.SequenceEqual(new byte[] { 0, 0, 0, 0 });
59 | return isProtected;
60 | }
61 | }
62 | return false;
63 | }
64 |
65 | public bool RemovePassword()
66 | {
67 | var messageStore = new PropertyContext(SpecialNIDs.NID_MESSAGE_STORE, this);
68 | var rootDataNode = messageStore.BTH.Root.Data;
69 | const int unknown2Bytes = 2;
70 | var passwordKey = new byte[] { 0xFF, 0x67 };
71 | foreach (var entry in rootDataNode.DataEntries)
72 | {
73 | if (entry.Key.SequenceEqual(passwordKey))
74 | {
75 | var dataBlockOffset = (int)entry.DataOffset + (int)rootDataNode.Data.BlockOffset + unknown2Bytes;
76 | var slice = rootDataNode.Data.Parent.Data.Skip(dataBlockOffset).Take(4).ToList();
77 | var isProtected = !slice.SequenceEqual(new byte[] { 0, 0, 0, 0 });
78 | if (!isProtected) return false;
79 |
80 | CloseMMF();
81 |
82 | using (var stream = new FileStream(Path, FileMode.Open))
83 | {
84 | rootDataNode.Data.Parent.Data[dataBlockOffset] = 0x00;
85 | rootDataNode.Data.Parent.Data[dataBlockOffset + 1] = 0x00;
86 | rootDataNode.Data.Parent.Data[dataBlockOffset + 2] = 0x00;
87 | rootDataNode.Data.Parent.Data[dataBlockOffset + 3] = 0x00;
88 |
89 | DatatEncoder.CryptPermute(rootDataNode.Data.Parent.Data, rootDataNode.Data.Parent.Data.Length, true, Header.EncodingAlgotihm);
90 |
91 | // seems to always be [65, 65, 65, 65]
92 | var permutationBytes = rootDataNode.Data.Parent.Data.Skip(dataBlockOffset).Take(4).ToArray();
93 | stream.Seek((long)rootDataNode.Data.Parent.PstOffset + dataBlockOffset, SeekOrigin.Begin);
94 | stream.Write(permutationBytes, 0, 4);
95 |
96 | var newCRC = new CRC32().ComputeCRC(0, rootDataNode.Data.Parent.Data, (uint)rootDataNode.Data.Parent.Data.Length);
97 | DatatEncoder.CryptPermute(rootDataNode.Data.Parent.Data, rootDataNode.Data.Parent.Data.Length, false, Header.EncodingAlgotihm);
98 | var crcoffset = (long)(rootDataNode.Data.Parent.PstOffset + rootDataNode.Data.Parent.CRCOffset);
99 | stream.Seek(crcoffset, SeekOrigin.Begin);
100 | var crcBuffer = BitConverter.GetBytes(newCRC);
101 | stream.Write(crcBuffer, 0, 4);
102 | }
103 | OpenMMF();
104 | return true;
105 | }
106 | }
107 | return false;
108 | }
109 |
110 | public void CloseMMF()
111 | {
112 | PSTMMF.Dispose();
113 | }
114 |
115 | public void OpenMMF()
116 | {
117 | PSTMMF = MemoryMappedFile.CreateFromFile(Path, FileMode.Open);
118 | }
119 |
120 | public Tuple GetNodeBIDs(ulong NID)
121 | {
122 | return Header.NodeBT.Root.GetNIDBID(NID);
123 | }
124 |
125 | public void Dispose()
126 | {
127 | CloseMMF();
128 | }
129 |
130 | public BBTENTRY GetBlockBBTEntry(ulong item1)
131 | {
132 | return Header.BlockBT.Root.GetBIDBBTEntry(item1);
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/PSTParse/PSTHeader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using PSTParse.NodeDatabaseLayer;
4 |
5 | namespace PSTParse
6 | {
7 | public class PSTHeader
8 | {
9 | public string DWMagic { get; }
10 | public PstVersion Version { get; }
11 | public PSTBTree NodeBT { get; private set; }
12 | public PSTBTree BlockBT { get; private set; }
13 | public BlockEncoding EncodingAlgotihm { get; private set; }
14 | public PSTRoot Root { get; }
15 |
16 | public PSTHeader(PSTFile pst)
17 | {
18 | using (var mmfView = pst.PSTMMF.CreateViewAccessor(0, 684))
19 | {
20 | var dwMagicBuffer = new byte[4];
21 | mmfView.ReadArray(0, dwMagicBuffer, 0, 4);
22 | DWMagic = Encoding.Default.GetString(dwMagicBuffer);
23 |
24 | var ver = mmfView.ReadInt16(10);
25 | Version = ver == 23 ? PstVersion.UNICODE : PstVersion.ANSI;
26 | if (Version == PstVersion.ANSI)
27 | {
28 | throw new Exception("ANSI encoded PST not supported");
29 | }
30 |
31 | var rootBuffer = new byte[72];
32 | mmfView.ReadArray(180, rootBuffer, 0, rootBuffer.Length);
33 |
34 | Root = new PSTRoot(rootBuffer);
35 |
36 | var sentinel = mmfView.ReadByte(512);
37 | var cryptMethod = (uint)mmfView.ReadByte(513);
38 |
39 | EncodingAlgotihm = (BlockEncoding)cryptMethod;
40 |
41 | var bytes = new byte[16];
42 | mmfView.ReadArray(216, bytes, 0, 16);
43 | var nbt_bref = new BREF(bytes);
44 |
45 | mmfView.ReadArray(232, bytes, 0, 16);
46 | var bbt_bref = new BREF(bytes);
47 |
48 | NodeBT = new PSTBTree(nbt_bref, pst);
49 | BlockBT = new PSTBTree(bbt_bref, pst);
50 | }
51 | }
52 |
53 | public enum BlockEncoding
54 | {
55 | NONE = 0,
56 | PERMUTE = 1,
57 | CYCLIC = 2
58 | }
59 |
60 | public enum PstVersion
61 | {
62 | ANSI,
63 | UNICODE,
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/PSTParse/PSTParse.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net60;netstandard2.0
5 | 1.3.2
6 | latest
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/PSTParse/PSTRoot.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace PSTParse
4 | {
5 | public class PSTRoot
6 | {
7 | public uint D_W_Reserved { get; set; }
8 | ///
9 | /// The size of the PST file, in bytes.
10 | ///
11 | public ulong FileSizeBytes { get; }
12 | public byte F_A_MapValid { get; set; }
13 |
14 | public PSTRoot(byte[] rootBuffer)
15 | {
16 | D_W_Reserved = BitConverter.ToUInt32(rootBuffer, 0);
17 | FileSizeBytes = BitConverter.ToUInt64(rootBuffer, 4);
18 | F_A_MapValid = rootBuffer[68];
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/PSTParse/Utilities/ArrayUtilities.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace PSTParse.Utilities
5 | {
6 | public static class ArrayUtilities
7 | {
8 | // create a subset from a range of indices
9 | public static T[] RangeSubset(this T[] array, int startIndex, int length)
10 | {
11 | T[] subset = new T[length];
12 | Array.Copy(array, startIndex, subset, 0, length);
13 | return subset;
14 | }
15 |
16 | // create a subset from a specific list of indices
17 | public static T[] Subset(this T[] array, params int[] indices)
18 | {
19 | T[] subset = new T[indices.Length];
20 | for (int i = 0; i < indices.Length; i++)
21 | {
22 | subset[i] = array[indices[i]];
23 | }
24 | return subset;
25 | }
26 |
27 | //this exists so byte arrays can be used as keys in dictionaries
28 | //http://stackoverflow.com/questions/1440392/use-byte-as-key-in-dictionary
29 | public class ByteArrayComparer : IEqualityComparer
30 | {
31 | public bool Equals(byte[] left, byte[] right)
32 | {
33 | if (left == null || right == null)
34 | return left == right;
35 |
36 | if (left.Length != right.Length)
37 | return false;
38 |
39 | for (int i = 0; i < left.Length; i++)
40 | if (left[i] != right[i])
41 | return false;
42 |
43 | return true;
44 | }
45 |
46 | public int GetHashCode(byte[] key)
47 | {
48 | if (key == null)
49 | throw new ArgumentNullException("key");
50 |
51 | int sum = 0;
52 | foreach (byte cur in key)
53 | sum += cur;
54 | return sum;
55 | }
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/PSTParse/Utilities/CRC32.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace PSTParse.Utilities
4 | {
5 | public class CRC32
6 | {
7 | #region constants
8 | public static uint[] CrcTableOffset32 =
9 | {
10 | 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
11 | 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
12 | 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
13 | 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
14 | 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
15 | 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
16 | 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
17 | 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
18 | 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
19 | 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
20 | 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
21 | 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
22 | 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
23 | 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
24 | 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
25 | 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
26 | 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
27 | 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
28 | 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
29 | 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
30 | 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
31 | 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
32 | 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
33 | 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
34 | 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
35 | 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
36 | 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
37 | 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
38 | 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
39 | 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
40 | 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
41 | 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
42 | };
43 |
44 | private uint[] CrcTableOffset40 =
45 | {
46 | 0x00000000, 0x191B3141, 0x32366282, 0x2B2D53C3, 0x646CC504, 0x7D77F445, 0x565AA786, 0x4F4196C7,
47 | 0xC8D98A08, 0xD1C2BB49, 0xFAEFE88A, 0xE3F4D9CB, 0xACB54F0C, 0xB5AE7E4D, 0x9E832D8E, 0x87981CCF,
48 | 0x4AC21251, 0x53D92310, 0x78F470D3, 0x61EF4192, 0x2EAED755, 0x37B5E614, 0x1C98B5D7, 0x05838496,
49 | 0x821B9859, 0x9B00A918, 0xB02DFADB, 0xA936CB9A, 0xE6775D5D, 0xFF6C6C1C, 0xD4413FDF, 0xCD5A0E9E,
50 | 0x958424A2, 0x8C9F15E3, 0xA7B24620, 0xBEA97761, 0xF1E8E1A6, 0xE8F3D0E7, 0xC3DE8324, 0xDAC5B265,
51 | 0x5D5DAEAA, 0x44469FEB, 0x6F6BCC28, 0x7670FD69, 0x39316BAE, 0x202A5AEF, 0x0B07092C, 0x121C386D,
52 | 0xDF4636F3, 0xC65D07B2, 0xED705471, 0xF46B6530, 0xBB2AF3F7, 0xA231C2B6, 0x891C9175, 0x9007A034,
53 | 0x179FBCFB, 0x0E848DBA, 0x25A9DE79, 0x3CB2EF38, 0x73F379FF, 0x6AE848BE, 0x41C51B7D, 0x58DE2A3C,
54 | 0xF0794F05, 0xE9627E44, 0xC24F2D87, 0xDB541CC6, 0x94158A01, 0x8D0EBB40, 0xA623E883, 0xBF38D9C2,
55 | 0x38A0C50D, 0x21BBF44C, 0x0A96A78F, 0x138D96CE, 0x5CCC0009, 0x45D73148, 0x6EFA628B, 0x77E153CA,
56 | 0xBABB5D54, 0xA3A06C15, 0x888D3FD6, 0x91960E97, 0xDED79850, 0xC7CCA911, 0xECE1FAD2, 0xF5FACB93,
57 | 0x7262D75C, 0x6B79E61D, 0x4054B5DE, 0x594F849F, 0x160E1258, 0x0F152319, 0x243870DA, 0x3D23419B,
58 | 0x65FD6BA7, 0x7CE65AE6, 0x57CB0925, 0x4ED03864, 0x0191AEA3, 0x188A9FE2, 0x33A7CC21, 0x2ABCFD60,
59 | 0xAD24E1AF, 0xB43FD0EE, 0x9F12832D, 0x8609B26C, 0xC94824AB, 0xD05315EA, 0xFB7E4629, 0xE2657768,
60 | 0x2F3F79F6, 0x362448B7, 0x1D091B74, 0x04122A35, 0x4B53BCF2, 0x52488DB3, 0x7965DE70, 0x607EEF31,
61 | 0xE7E6F3FE, 0xFEFDC2BF, 0xD5D0917C, 0xCCCBA03D, 0x838A36FA, 0x9A9107BB, 0xB1BC5478, 0xA8A76539,
62 | 0x3B83984B, 0x2298A90A, 0x09B5FAC9, 0x10AECB88, 0x5FEF5D4F, 0x46F46C0E, 0x6DD93FCD, 0x74C20E8C,
63 | 0xF35A1243, 0xEA412302, 0xC16C70C1, 0xD8774180, 0x9736D747, 0x8E2DE606, 0xA500B5C5, 0xBC1B8484,
64 | 0x71418A1A, 0x685ABB5B, 0x4377E898, 0x5A6CD9D9, 0x152D4F1E, 0x0C367E5F, 0x271B2D9C, 0x3E001CDD,
65 | 0xB9980012, 0xA0833153, 0x8BAE6290, 0x92B553D1, 0xDDF4C516, 0xC4EFF457, 0xEFC2A794, 0xF6D996D5,
66 | 0xAE07BCE9, 0xB71C8DA8, 0x9C31DE6B, 0x852AEF2A, 0xCA6B79ED, 0xD37048AC, 0xF85D1B6F, 0xE1462A2E,
67 | 0x66DE36E1, 0x7FC507A0, 0x54E85463, 0x4DF36522, 0x02B2F3E5, 0x1BA9C2A4, 0x30849167, 0x299FA026,
68 | 0xE4C5AEB8, 0xFDDE9FF9, 0xD6F3CC3A, 0xCFE8FD7B, 0x80A96BBC, 0x99B25AFD, 0xB29F093E, 0xAB84387F,
69 | 0x2C1C24B0, 0x350715F1, 0x1E2A4632, 0x07317773, 0x4870E1B4, 0x516BD0F5, 0x7A468336, 0x635DB277,
70 | 0xCBFAD74E, 0xD2E1E60F, 0xF9CCB5CC, 0xE0D7848D, 0xAF96124A, 0xB68D230B, 0x9DA070C8, 0x84BB4189,
71 | 0x03235D46, 0x1A386C07, 0x31153FC4, 0x280E0E85, 0x674F9842, 0x7E54A903, 0x5579FAC0, 0x4C62CB81,
72 | 0x8138C51F, 0x9823F45E, 0xB30EA79D, 0xAA1596DC, 0xE554001B, 0xFC4F315A, 0xD7626299, 0xCE7953D8,
73 | 0x49E14F17, 0x50FA7E56, 0x7BD72D95, 0x62CC1CD4, 0x2D8D8A13, 0x3496BB52, 0x1FBBE891, 0x06A0D9D0,
74 | 0x5E7EF3EC, 0x4765C2AD, 0x6C48916E, 0x7553A02F, 0x3A1236E8, 0x230907A9, 0x0824546A, 0x113F652B,
75 | 0x96A779E4, 0x8FBC48A5, 0xA4911B66, 0xBD8A2A27, 0xF2CBBCE0, 0xEBD08DA1, 0xC0FDDE62, 0xD9E6EF23,
76 | 0x14BCE1BD, 0x0DA7D0FC, 0x268A833F, 0x3F91B27E, 0x70D024B9, 0x69CB15F8, 0x42E6463B, 0x5BFD777A,
77 | 0xDC656BB5, 0xC57E5AF4, 0xEE530937, 0xF7483876, 0xB809AEB1, 0xA1129FF0, 0x8A3FCC33, 0x9324FD72
78 | };
79 |
80 | private uint[] CrcTableOffset48 =
81 | {
82 | 0x00000000, 0x01C26A37, 0x0384D46E, 0x0246BE59, 0x0709A8DC, 0x06CBC2EB, 0x048D7CB2, 0x054F1685,
83 | 0x0E1351B8, 0x0FD13B8F, 0x0D9785D6, 0x0C55EFE1, 0x091AF964, 0x08D89353, 0x0A9E2D0A, 0x0B5C473D,
84 | 0x1C26A370, 0x1DE4C947, 0x1FA2771E, 0x1E601D29, 0x1B2F0BAC, 0x1AED619B, 0x18ABDFC2, 0x1969B5F5,
85 | 0x1235F2C8, 0x13F798FF, 0x11B126A6, 0x10734C91, 0x153C5A14, 0x14FE3023, 0x16B88E7A, 0x177AE44D,
86 | 0x384D46E0, 0x398F2CD7, 0x3BC9928E, 0x3A0BF8B9, 0x3F44EE3C, 0x3E86840B, 0x3CC03A52, 0x3D025065,
87 | 0x365E1758, 0x379C7D6F, 0x35DAC336, 0x3418A901, 0x3157BF84, 0x3095D5B3, 0x32D36BEA, 0x331101DD,
88 | 0x246BE590, 0x25A98FA7, 0x27EF31FE, 0x262D5BC9, 0x23624D4C, 0x22A0277B, 0x20E69922, 0x2124F315,
89 | 0x2A78B428, 0x2BBADE1F, 0x29FC6046, 0x283E0A71, 0x2D711CF4, 0x2CB376C3, 0x2EF5C89A, 0x2F37A2AD,
90 | 0x709A8DC0, 0x7158E7F7, 0x731E59AE, 0x72DC3399, 0x7793251C, 0x76514F2B, 0x7417F172, 0x75D59B45,
91 | 0x7E89DC78, 0x7F4BB64F, 0x7D0D0816, 0x7CCF6221, 0x798074A4, 0x78421E93, 0x7A04A0CA, 0x7BC6CAFD,
92 | 0x6CBC2EB0, 0x6D7E4487, 0x6F38FADE, 0x6EFA90E9, 0x6BB5866C, 0x6A77EC5B, 0x68315202, 0x69F33835,
93 | 0x62AF7F08, 0x636D153F, 0x612BAB66, 0x60E9C151, 0x65A6D7D4, 0x6464BDE3, 0x662203BA, 0x67E0698D,
94 | 0x48D7CB20, 0x4915A117, 0x4B531F4E, 0x4A917579, 0x4FDE63FC, 0x4E1C09CB, 0x4C5AB792, 0x4D98DDA5,
95 | 0x46C49A98, 0x4706F0AF, 0x45404EF6, 0x448224C1, 0x41CD3244, 0x400F5873, 0x4249E62A, 0x438B8C1D,
96 | 0x54F16850, 0x55330267, 0x5775BC3E, 0x56B7D609, 0x53F8C08C, 0x523AAABB, 0x507C14E2, 0x51BE7ED5,
97 | 0x5AE239E8, 0x5B2053DF, 0x5966ED86, 0x58A487B1, 0x5DEB9134, 0x5C29FB03, 0x5E6F455A, 0x5FAD2F6D,
98 | 0xE1351B80, 0xE0F771B7, 0xE2B1CFEE, 0xE373A5D9, 0xE63CB35C, 0xE7FED96B, 0xE5B86732, 0xE47A0D05,
99 | 0xEF264A38, 0xEEE4200F, 0xECA29E56, 0xED60F461, 0xE82FE2E4, 0xE9ED88D3, 0xEBAB368A, 0xEA695CBD,
100 | 0xFD13B8F0, 0xFCD1D2C7, 0xFE976C9E, 0xFF5506A9, 0xFA1A102C, 0xFBD87A1B, 0xF99EC442, 0xF85CAE75,
101 | 0xF300E948, 0xF2C2837F, 0xF0843D26, 0xF1465711, 0xF4094194, 0xF5CB2BA3, 0xF78D95FA, 0xF64FFFCD,
102 | 0xD9785D60, 0xD8BA3757, 0xDAFC890E, 0xDB3EE339, 0xDE71F5BC, 0xDFB39F8B, 0xDDF521D2, 0xDC374BE5,
103 | 0xD76B0CD8, 0xD6A966EF, 0xD4EFD8B6, 0xD52DB281, 0xD062A404, 0xD1A0CE33, 0xD3E6706A, 0xD2241A5D,
104 | 0xC55EFE10, 0xC49C9427, 0xC6DA2A7E, 0xC7184049, 0xC25756CC, 0xC3953CFB, 0xC1D382A2, 0xC011E895,
105 | 0xCB4DAFA8, 0xCA8FC59F, 0xC8C97BC6, 0xC90B11F1, 0xCC440774, 0xCD866D43, 0xCFC0D31A, 0xCE02B92D,
106 | 0x91AF9640, 0x906DFC77, 0x922B422E, 0x93E92819, 0x96A63E9C, 0x976454AB, 0x9522EAF2, 0x94E080C5,
107 | 0x9FBCC7F8, 0x9E7EADCF, 0x9C381396, 0x9DFA79A1, 0x98B56F24, 0x99770513, 0x9B31BB4A, 0x9AF3D17D,
108 | 0x8D893530, 0x8C4B5F07, 0x8E0DE15E, 0x8FCF8B69, 0x8A809DEC, 0x8B42F7DB, 0x89044982, 0x88C623B5,
109 | 0x839A6488, 0x82580EBF, 0x801EB0E6, 0x81DCDAD1, 0x8493CC54, 0x8551A663, 0x8717183A, 0x86D5720D,
110 | 0xA9E2D0A0, 0xA820BA97, 0xAA6604CE, 0xABA46EF9, 0xAEEB787C, 0xAF29124B, 0xAD6FAC12, 0xACADC625,
111 | 0xA7F18118, 0xA633EB2F, 0xA4755576, 0xA5B73F41, 0xA0F829C4, 0xA13A43F3, 0xA37CFDAA, 0xA2BE979D,
112 | 0xB5C473D0, 0xB40619E7, 0xB640A7BE, 0xB782CD89, 0xB2CDDB0C, 0xB30FB13B, 0xB1490F62, 0xB08B6555,
113 | 0xBBD72268, 0xBA15485F, 0xB853F606, 0xB9919C31, 0xBCDE8AB4, 0xBD1CE083, 0xBF5A5EDA, 0xBE9834ED
114 | };
115 |
116 | private uint[] CrcTableOffset56 =
117 | {
118 | 0x00000000, 0xB8BC6765, 0xAA09C88B, 0x12B5AFEE, 0x8F629757, 0x37DEF032, 0x256B5FDC, 0x9DD738B9,
119 | 0xC5B428EF, 0x7D084F8A, 0x6FBDE064, 0xD7018701, 0x4AD6BFB8, 0xF26AD8DD, 0xE0DF7733, 0x58631056,
120 | 0x5019579F, 0xE8A530FA, 0xFA109F14, 0x42ACF871, 0xDF7BC0C8, 0x67C7A7AD, 0x75720843, 0xCDCE6F26,
121 | 0x95AD7F70, 0x2D111815, 0x3FA4B7FB, 0x8718D09E, 0x1ACFE827, 0xA2738F42, 0xB0C620AC, 0x087A47C9,
122 | 0xA032AF3E, 0x188EC85B, 0x0A3B67B5, 0xB28700D0, 0x2F503869, 0x97EC5F0C, 0x8559F0E2, 0x3DE59787,
123 | 0x658687D1, 0xDD3AE0B4, 0xCF8F4F5A, 0x7733283F, 0xEAE41086, 0x525877E3, 0x40EDD80D, 0xF851BF68,
124 | 0xF02BF8A1, 0x48979FC4, 0x5A22302A, 0xE29E574F, 0x7F496FF6, 0xC7F50893, 0xD540A77D, 0x6DFCC018,
125 | 0x359FD04E, 0x8D23B72B, 0x9F9618C5, 0x272A7FA0, 0xBAFD4719, 0x0241207C, 0x10F48F92, 0xA848E8F7,
126 | 0x9B14583D, 0x23A83F58, 0x311D90B6, 0x89A1F7D3, 0x1476CF6A, 0xACCAA80F, 0xBE7F07E1, 0x06C36084,
127 | 0x5EA070D2, 0xE61C17B7, 0xF4A9B859, 0x4C15DF3C, 0xD1C2E785, 0x697E80E0, 0x7BCB2F0E, 0xC377486B,
128 | 0xCB0D0FA2, 0x73B168C7, 0x6104C729, 0xD9B8A04C, 0x446F98F5, 0xFCD3FF90, 0xEE66507E, 0x56DA371B,
129 | 0x0EB9274D, 0xB6054028, 0xA4B0EFC6, 0x1C0C88A3, 0x81DBB01A, 0x3967D77F, 0x2BD27891, 0x936E1FF4,
130 | 0x3B26F703, 0x839A9066, 0x912F3F88, 0x299358ED, 0xB4446054, 0x0CF80731, 0x1E4DA8DF, 0xA6F1CFBA,
131 | 0xFE92DFEC, 0x462EB889, 0x549B1767, 0xEC277002, 0x71F048BB, 0xC94C2FDE, 0xDBF98030, 0x6345E755,
132 | 0x6B3FA09C, 0xD383C7F9, 0xC1366817, 0x798A0F72, 0xE45D37CB, 0x5CE150AE, 0x4E54FF40, 0xF6E89825,
133 | 0xAE8B8873, 0x1637EF16, 0x048240F8, 0xBC3E279D, 0x21E91F24, 0x99557841, 0x8BE0D7AF, 0x335CB0CA,
134 | 0xED59B63B, 0x55E5D15E, 0x47507EB0, 0xFFEC19D5, 0x623B216C, 0xDA874609, 0xC832E9E7, 0x708E8E82,
135 | 0x28ED9ED4, 0x9051F9B1, 0x82E4565F, 0x3A58313A, 0xA78F0983, 0x1F336EE6, 0x0D86C108, 0xB53AA66D,
136 | 0xBD40E1A4, 0x05FC86C1, 0x1749292F, 0xAFF54E4A, 0x322276F3, 0x8A9E1196, 0x982BBE78, 0x2097D91D,
137 | 0x78F4C94B, 0xC048AE2E, 0xD2FD01C0, 0x6A4166A5, 0xF7965E1C, 0x4F2A3979, 0x5D9F9697, 0xE523F1F2,
138 | 0x4D6B1905, 0xF5D77E60, 0xE762D18E, 0x5FDEB6EB, 0xC2098E52, 0x7AB5E937, 0x680046D9, 0xD0BC21BC,
139 | 0x88DF31EA, 0x3063568F, 0x22D6F961, 0x9A6A9E04, 0x07BDA6BD, 0xBF01C1D8, 0xADB46E36, 0x15080953,
140 | 0x1D724E9A, 0xA5CE29FF, 0xB77B8611, 0x0FC7E174, 0x9210D9CD, 0x2AACBEA8, 0x38191146, 0x80A57623,
141 | 0xD8C66675, 0x607A0110, 0x72CFAEFE, 0xCA73C99B, 0x57A4F122, 0xEF189647, 0xFDAD39A9, 0x45115ECC,
142 | 0x764DEE06, 0xCEF18963, 0xDC44268D, 0x64F841E8, 0xF92F7951, 0x41931E34, 0x5326B1DA, 0xEB9AD6BF,
143 | 0xB3F9C6E9, 0x0B45A18C, 0x19F00E62, 0xA14C6907, 0x3C9B51BE, 0x842736DB, 0x96929935, 0x2E2EFE50,
144 | 0x2654B999, 0x9EE8DEFC, 0x8C5D7112, 0x34E11677, 0xA9362ECE, 0x118A49AB, 0x033FE645, 0xBB838120,
145 | 0xE3E09176, 0x5B5CF613, 0x49E959FD, 0xF1553E98, 0x6C820621, 0xD43E6144, 0xC68BCEAA, 0x7E37A9CF,
146 | 0xD67F4138, 0x6EC3265D, 0x7C7689B3, 0xC4CAEED6, 0x591DD66F, 0xE1A1B10A, 0xF3141EE4, 0x4BA87981,
147 | 0x13CB69D7, 0xAB770EB2, 0xB9C2A15C, 0x017EC639, 0x9CA9FE80, 0x241599E5, 0x36A0360B, 0x8E1C516E,
148 | 0x866616A7, 0x3EDA71C2, 0x2C6FDE2C, 0x94D3B949, 0x090481F0, 0xB1B8E695, 0xA30D497B, 0x1BB12E1E,
149 | 0x43D23E48, 0xFB6E592D, 0xE9DBF6C3, 0x516791A6, 0xCCB0A91F, 0x740CCE7A, 0x66B96194, 0xDE0506F1
150 | };
151 |
152 | private uint[] CrcTableOffset64 =
153 | {
154 | 0x00000000, 0x3D6029B0, 0x7AC05360, 0x47A07AD0, 0xF580A6C0, 0xC8E08F70, 0x8F40F5A0, 0xB220DC10,
155 | 0x30704BC1, 0x0D106271, 0x4AB018A1, 0x77D03111, 0xC5F0ED01, 0xF890C4B1, 0xBF30BE61, 0x825097D1,
156 | 0x60E09782, 0x5D80BE32, 0x1A20C4E2, 0x2740ED52, 0x95603142, 0xA80018F2, 0xEFA06222, 0xD2C04B92,
157 | 0x5090DC43, 0x6DF0F5F3, 0x2A508F23, 0x1730A693, 0xA5107A83, 0x98705333, 0xDFD029E3, 0xE2B00053,
158 | 0xC1C12F04, 0xFCA106B4, 0xBB017C64, 0x866155D4, 0x344189C4, 0x0921A074, 0x4E81DAA4, 0x73E1F314,
159 | 0xF1B164C5, 0xCCD14D75, 0x8B7137A5, 0xB6111E15, 0x0431C205, 0x3951EBB5, 0x7EF19165, 0x4391B8D5,
160 | 0xA121B886, 0x9C419136, 0xDBE1EBE6, 0xE681C256, 0x54A11E46, 0x69C137F6, 0x2E614D26, 0x13016496,
161 | 0x9151F347, 0xAC31DAF7, 0xEB91A027, 0xD6F18997, 0x64D15587, 0x59B17C37, 0x1E1106E7, 0x23712F57,
162 | 0x58F35849, 0x659371F9, 0x22330B29, 0x1F532299, 0xAD73FE89, 0x9013D739, 0xD7B3ADE9, 0xEAD38459,
163 | 0x68831388, 0x55E33A38, 0x124340E8, 0x2F236958, 0x9D03B548, 0xA0639CF8, 0xE7C3E628, 0xDAA3CF98,
164 | 0x3813CFCB, 0x0573E67B, 0x42D39CAB, 0x7FB3B51B, 0xCD93690B, 0xF0F340BB, 0xB7533A6B, 0x8A3313DB,
165 | 0x0863840A, 0x3503ADBA, 0x72A3D76A, 0x4FC3FEDA, 0xFDE322CA, 0xC0830B7A, 0x872371AA, 0xBA43581A,
166 | 0x9932774D, 0xA4525EFD, 0xE3F2242D, 0xDE920D9D, 0x6CB2D18D, 0x51D2F83D, 0x167282ED, 0x2B12AB5D,
167 | 0xA9423C8C, 0x9422153C, 0xD3826FEC, 0xEEE2465C, 0x5CC29A4C, 0x61A2B3FC, 0x2602C92C, 0x1B62E09C,
168 | 0xF9D2E0CF, 0xC4B2C97F, 0x8312B3AF, 0xBE729A1F, 0x0C52460F, 0x31326FBF, 0x7692156F, 0x4BF23CDF,
169 | 0xC9A2AB0E, 0xF4C282BE, 0xB362F86E, 0x8E02D1DE, 0x3C220DCE, 0x0142247E, 0x46E25EAE, 0x7B82771E,
170 | 0xB1E6B092, 0x8C869922, 0xCB26E3F2, 0xF646CA42, 0x44661652, 0x79063FE2, 0x3EA64532, 0x03C66C82,
171 | 0x8196FB53, 0xBCF6D2E3, 0xFB56A833, 0xC6368183, 0x74165D93, 0x49767423, 0x0ED60EF3, 0x33B62743,
172 | 0xD1062710, 0xEC660EA0, 0xABC67470, 0x96A65DC0, 0x248681D0, 0x19E6A860, 0x5E46D2B0, 0x6326FB00,
173 | 0xE1766CD1, 0xDC164561, 0x9BB63FB1, 0xA6D61601, 0x14F6CA11, 0x2996E3A1, 0x6E369971, 0x5356B0C1,
174 | 0x70279F96, 0x4D47B626, 0x0AE7CCF6, 0x3787E546, 0x85A73956, 0xB8C710E6, 0xFF676A36, 0xC2074386,
175 | 0x4057D457, 0x7D37FDE7, 0x3A978737, 0x07F7AE87, 0xB5D77297, 0x88B75B27, 0xCF1721F7, 0xF2770847,
176 | 0x10C70814, 0x2DA721A4, 0x6A075B74, 0x576772C4, 0xE547AED4, 0xD8278764, 0x9F87FDB4, 0xA2E7D404,
177 | 0x20B743D5, 0x1DD76A65, 0x5A7710B5, 0x67173905, 0xD537E515, 0xE857CCA5, 0xAFF7B675, 0x92979FC5,
178 | 0xE915E8DB, 0xD475C16B, 0x93D5BBBB, 0xAEB5920B, 0x1C954E1B, 0x21F567AB, 0x66551D7B, 0x5B3534CB,
179 | 0xD965A31A, 0xE4058AAA, 0xA3A5F07A, 0x9EC5D9CA, 0x2CE505DA, 0x11852C6A, 0x562556BA, 0x6B457F0A,
180 | 0x89F57F59, 0xB49556E9, 0xF3352C39, 0xCE550589, 0x7C75D999, 0x4115F029, 0x06B58AF9, 0x3BD5A349,
181 | 0xB9853498, 0x84E51D28, 0xC34567F8, 0xFE254E48, 0x4C059258, 0x7165BBE8, 0x36C5C138, 0x0BA5E888,
182 | 0x28D4C7DF, 0x15B4EE6F, 0x521494BF, 0x6F74BD0F, 0xDD54611F, 0xE03448AF, 0xA794327F, 0x9AF41BCF,
183 | 0x18A48C1E, 0x25C4A5AE, 0x6264DF7E, 0x5F04F6CE, 0xED242ADE, 0xD044036E, 0x97E479BE, 0xAA84500E,
184 | 0x4834505D, 0x755479ED, 0x32F4033D, 0x0F942A8D, 0xBDB4F69D, 0x80D4DF2D, 0xC774A5FD, 0xFA148C4D,
185 | 0x78441B9C, 0x4524322C, 0x028448FC, 0x3FE4614C, 0x8DC4BD5C, 0xB0A494EC, 0xF704EE3C, 0xCA64C78C
186 | };
187 |
188 | private uint[] CrcTableOffset72 =
189 | {
190 | 0x00000000, 0xCB5CD3A5, 0x4DC8A10B, 0x869472AE, 0x9B914216, 0x50CD91B3, 0xD659E31D, 0x1D0530B8,
191 | 0xEC53826D, 0x270F51C8, 0xA19B2366, 0x6AC7F0C3, 0x77C2C07B, 0xBC9E13DE, 0x3A0A6170, 0xF156B2D5,
192 | 0x03D6029B, 0xC88AD13E, 0x4E1EA390, 0x85427035, 0x9847408D, 0x531B9328, 0xD58FE186, 0x1ED33223,
193 | 0xEF8580F6, 0x24D95353, 0xA24D21FD, 0x6911F258, 0x7414C2E0, 0xBF481145, 0x39DC63EB, 0xF280B04E,
194 | 0x07AC0536, 0xCCF0D693, 0x4A64A43D, 0x81387798, 0x9C3D4720, 0x57619485, 0xD1F5E62B, 0x1AA9358E,
195 | 0xEBFF875B, 0x20A354FE, 0xA6372650, 0x6D6BF5F5, 0x706EC54D, 0xBB3216E8, 0x3DA66446, 0xF6FAB7E3,
196 | 0x047A07AD, 0xCF26D408, 0x49B2A6A6, 0x82EE7503, 0x9FEB45BB, 0x54B7961E, 0xD223E4B0, 0x197F3715,
197 | 0xE82985C0, 0x23755665, 0xA5E124CB, 0x6EBDF76E, 0x73B8C7D6, 0xB8E41473, 0x3E7066DD, 0xF52CB578,
198 | 0x0F580A6C, 0xC404D9C9, 0x4290AB67, 0x89CC78C2, 0x94C9487A, 0x5F959BDF, 0xD901E971, 0x125D3AD4,
199 | 0xE30B8801, 0x28575BA4, 0xAEC3290A, 0x659FFAAF, 0x789ACA17, 0xB3C619B2, 0x35526B1C, 0xFE0EB8B9,
200 | 0x0C8E08F7, 0xC7D2DB52, 0x4146A9FC, 0x8A1A7A59, 0x971F4AE1, 0x5C439944, 0xDAD7EBEA, 0x118B384F,
201 | 0xE0DD8A9A, 0x2B81593F, 0xAD152B91, 0x6649F834, 0x7B4CC88C, 0xB0101B29, 0x36846987, 0xFDD8BA22,
202 | 0x08F40F5A, 0xC3A8DCFF, 0x453CAE51, 0x8E607DF4, 0x93654D4C, 0x58399EE9, 0xDEADEC47, 0x15F13FE2,
203 | 0xE4A78D37, 0x2FFB5E92, 0xA96F2C3C, 0x6233FF99, 0x7F36CF21, 0xB46A1C84, 0x32FE6E2A, 0xF9A2BD8F,
204 | 0x0B220DC1, 0xC07EDE64, 0x46EAACCA, 0x8DB67F6F, 0x90B34FD7, 0x5BEF9C72, 0xDD7BEEDC, 0x16273D79,
205 | 0xE7718FAC, 0x2C2D5C09, 0xAAB92EA7, 0x61E5FD02, 0x7CE0CDBA, 0xB7BC1E1F, 0x31286CB1, 0xFA74BF14,
206 | 0x1EB014D8, 0xD5ECC77D, 0x5378B5D3, 0x98246676, 0x852156CE, 0x4E7D856B, 0xC8E9F7C5, 0x03B52460,
207 | 0xF2E396B5, 0x39BF4510, 0xBF2B37BE, 0x7477E41B, 0x6972D4A3, 0xA22E0706, 0x24BA75A8, 0xEFE6A60D,
208 | 0x1D661643, 0xD63AC5E6, 0x50AEB748, 0x9BF264ED, 0x86F75455, 0x4DAB87F0, 0xCB3FF55E, 0x006326FB,
209 | 0xF135942E, 0x3A69478B, 0xBCFD3525, 0x77A1E680, 0x6AA4D638, 0xA1F8059D, 0x276C7733, 0xEC30A496,
210 | 0x191C11EE, 0xD240C24B, 0x54D4B0E5, 0x9F886340, 0x828D53F8, 0x49D1805D, 0xCF45F2F3, 0x04192156,
211 | 0xF54F9383, 0x3E134026, 0xB8873288, 0x73DBE12D, 0x6EDED195, 0xA5820230, 0x2316709E, 0xE84AA33B,
212 | 0x1ACA1375, 0xD196C0D0, 0x5702B27E, 0x9C5E61DB, 0x815B5163, 0x4A0782C6, 0xCC93F068, 0x07CF23CD,
213 | 0xF6999118, 0x3DC542BD, 0xBB513013, 0x700DE3B6, 0x6D08D30E, 0xA65400AB, 0x20C07205, 0xEB9CA1A0,
214 | 0x11E81EB4, 0xDAB4CD11, 0x5C20BFBF, 0x977C6C1A, 0x8A795CA2, 0x41258F07, 0xC7B1FDA9, 0x0CED2E0C,
215 | 0xFDBB9CD9, 0x36E74F7C, 0xB0733DD2, 0x7B2FEE77, 0x662ADECF, 0xAD760D6A, 0x2BE27FC4, 0xE0BEAC61,
216 | 0x123E1C2F, 0xD962CF8A, 0x5FF6BD24, 0x94AA6E81, 0x89AF5E39, 0x42F38D9C, 0xC467FF32, 0x0F3B2C97,
217 | 0xFE6D9E42, 0x35314DE7, 0xB3A53F49, 0x78F9ECEC, 0x65FCDC54, 0xAEA00FF1, 0x28347D5F, 0xE368AEFA,
218 | 0x16441B82, 0xDD18C827, 0x5B8CBA89, 0x90D0692C, 0x8DD55994, 0x46898A31, 0xC01DF89F, 0x0B412B3A,
219 | 0xFA1799EF, 0x314B4A4A, 0xB7DF38E4, 0x7C83EB41, 0x6186DBF9, 0xAADA085C, 0x2C4E7AF2, 0xE712A957,
220 | 0x15921919, 0xDECECABC, 0x585AB812, 0x93066BB7, 0x8E035B0F, 0x455F88AA, 0xC3CBFA04, 0x089729A1,
221 | 0xF9C19B74, 0x329D48D1, 0xB4093A7F, 0x7F55E9DA, 0x6250D962, 0xA90C0AC7, 0x2F987869, 0xE4C4ABCC
222 | };
223 |
224 | private uint[] CrcTableOffset80 =
225 | {
226 | 0x00000000, 0xA6770BB4, 0x979F1129, 0x31E81A9D, 0xF44F2413, 0x52382FA7, 0x63D0353A, 0xC5A73E8E,
227 | 0x33EF4E67, 0x959845D3, 0xA4705F4E, 0x020754FA, 0xC7A06A74, 0x61D761C0, 0x503F7B5D, 0xF64870E9,
228 | 0x67DE9CCE, 0xC1A9977A, 0xF0418DE7, 0x56368653, 0x9391B8DD, 0x35E6B369, 0x040EA9F4, 0xA279A240,
229 | 0x5431D2A9, 0xF246D91D, 0xC3AEC380, 0x65D9C834, 0xA07EF6BA, 0x0609FD0E, 0x37E1E793, 0x9196EC27,
230 | 0xCFBD399C, 0x69CA3228, 0x582228B5, 0xFE552301, 0x3BF21D8F, 0x9D85163B, 0xAC6D0CA6, 0x0A1A0712,
231 | 0xFC5277FB, 0x5A257C4F, 0x6BCD66D2, 0xCDBA6D66, 0x081D53E8, 0xAE6A585C, 0x9F8242C1, 0x39F54975,
232 | 0xA863A552, 0x0E14AEE6, 0x3FFCB47B, 0x998BBFCF, 0x5C2C8141, 0xFA5B8AF5, 0xCBB39068, 0x6DC49BDC,
233 | 0x9B8CEB35, 0x3DFBE081, 0x0C13FA1C, 0xAA64F1A8, 0x6FC3CF26, 0xC9B4C492, 0xF85CDE0F, 0x5E2BD5BB,
234 | 0x440B7579, 0xE27C7ECD, 0xD3946450, 0x75E36FE4, 0xB044516A, 0x16335ADE, 0x27DB4043, 0x81AC4BF7,
235 | 0x77E43B1E, 0xD19330AA, 0xE07B2A37, 0x460C2183, 0x83AB1F0D, 0x25DC14B9, 0x14340E24, 0xB2430590,
236 | 0x23D5E9B7, 0x85A2E203, 0xB44AF89E, 0x123DF32A, 0xD79ACDA4, 0x71EDC610, 0x4005DC8D, 0xE672D739,
237 | 0x103AA7D0, 0xB64DAC64, 0x87A5B6F9, 0x21D2BD4D, 0xE47583C3, 0x42028877, 0x73EA92EA, 0xD59D995E,
238 | 0x8BB64CE5, 0x2DC14751, 0x1C295DCC, 0xBA5E5678, 0x7FF968F6, 0xD98E6342, 0xE86679DF, 0x4E11726B,
239 | 0xB8590282, 0x1E2E0936, 0x2FC613AB, 0x89B1181F, 0x4C162691, 0xEA612D25, 0xDB8937B8, 0x7DFE3C0C,
240 | 0xEC68D02B, 0x4A1FDB9F, 0x7BF7C102, 0xDD80CAB6, 0x1827F438, 0xBE50FF8C, 0x8FB8E511, 0x29CFEEA5,
241 | 0xDF879E4C, 0x79F095F8, 0x48188F65, 0xEE6F84D1, 0x2BC8BA5F, 0x8DBFB1EB, 0xBC57AB76, 0x1A20A0C2,
242 | 0x8816EAF2, 0x2E61E146, 0x1F89FBDB, 0xB9FEF06F, 0x7C59CEE1, 0xDA2EC555, 0xEBC6DFC8, 0x4DB1D47C,
243 | 0xBBF9A495, 0x1D8EAF21, 0x2C66B5BC, 0x8A11BE08, 0x4FB68086, 0xE9C18B32, 0xD82991AF, 0x7E5E9A1B,
244 | 0xEFC8763C, 0x49BF7D88, 0x78576715, 0xDE206CA1, 0x1B87522F, 0xBDF0599B, 0x8C184306, 0x2A6F48B2,
245 | 0xDC27385B, 0x7A5033EF, 0x4BB82972, 0xEDCF22C6, 0x28681C48, 0x8E1F17FC, 0xBFF70D61, 0x198006D5,
246 | 0x47ABD36E, 0xE1DCD8DA, 0xD034C247, 0x7643C9F3, 0xB3E4F77D, 0x1593FCC9, 0x247BE654, 0x820CEDE0,
247 | 0x74449D09, 0xD23396BD, 0xE3DB8C20, 0x45AC8794, 0x800BB91A, 0x267CB2AE, 0x1794A833, 0xB1E3A387,
248 | 0x20754FA0, 0x86024414, 0xB7EA5E89, 0x119D553D, 0xD43A6BB3, 0x724D6007, 0x43A57A9A, 0xE5D2712E,
249 | 0x139A01C7, 0xB5ED0A73, 0x840510EE, 0x22721B5A, 0xE7D525D4, 0x41A22E60, 0x704A34FD, 0xD63D3F49,
250 | 0xCC1D9F8B, 0x6A6A943F, 0x5B828EA2, 0xFDF58516, 0x3852BB98, 0x9E25B02C, 0xAFCDAAB1, 0x09BAA105,
251 | 0xFFF2D1EC, 0x5985DA58, 0x686DC0C5, 0xCE1ACB71, 0x0BBDF5FF, 0xADCAFE4B, 0x9C22E4D6, 0x3A55EF62,
252 | 0xABC30345, 0x0DB408F1, 0x3C5C126C, 0x9A2B19D8, 0x5F8C2756, 0xF9FB2CE2, 0xC813367F, 0x6E643DCB,
253 | 0x982C4D22, 0x3E5B4696, 0x0FB35C0B, 0xA9C457BF, 0x6C636931, 0xCA146285, 0xFBFC7818, 0x5D8B73AC,
254 | 0x03A0A617, 0xA5D7ADA3, 0x943FB73E, 0x3248BC8A, 0xF7EF8204, 0x519889B0, 0x6070932D, 0xC6079899,
255 | 0x304FE870, 0x9638E3C4, 0xA7D0F959, 0x01A7F2ED, 0xC400CC63, 0x6277C7D7, 0x539FDD4A, 0xF5E8D6FE,
256 | 0x647E3AD9, 0xC209316D, 0xF3E12BF0, 0x55962044, 0x90311ECA, 0x3646157E, 0x07AE0FE3, 0xA1D90457,
257 | 0x579174BE, 0xF1E67F0A, 0xC00E6597, 0x66796E23, 0xA3DE50AD, 0x05A95B19, 0x34414184, 0x92364A30
258 | };
259 |
260 | private uint[] CrcTableOffset88 =
261 | {
262 | 0x00000000, 0xCCAA009E, 0x4225077D, 0x8E8F07E3, 0x844A0EFA, 0x48E00E64, 0xC66F0987, 0x0AC50919,
263 | 0xD3E51BB5, 0x1F4F1B2B, 0x91C01CC8, 0x5D6A1C56, 0x57AF154F, 0x9B0515D1, 0x158A1232, 0xD92012AC,
264 | 0x7CBB312B, 0xB01131B5, 0x3E9E3656, 0xF23436C8, 0xF8F13FD1, 0x345B3F4F, 0xBAD438AC, 0x767E3832,
265 | 0xAF5E2A9E, 0x63F42A00, 0xED7B2DE3, 0x21D12D7D, 0x2B142464, 0xE7BE24FA, 0x69312319, 0xA59B2387,
266 | 0xF9766256, 0x35DC62C8, 0xBB53652B, 0x77F965B5, 0x7D3C6CAC, 0xB1966C32, 0x3F196BD1, 0xF3B36B4F,
267 | 0x2A9379E3, 0xE639797D, 0x68B67E9E, 0xA41C7E00, 0xAED97719, 0x62737787, 0xECFC7064, 0x205670FA,
268 | 0x85CD537D, 0x496753E3, 0xC7E85400, 0x0B42549E, 0x01875D87, 0xCD2D5D19, 0x43A25AFA, 0x8F085A64,
269 | 0x562848C8, 0x9A824856, 0x140D4FB5, 0xD8A74F2B, 0xD2624632, 0x1EC846AC, 0x9047414F, 0x5CED41D1,
270 | 0x299DC2ED, 0xE537C273, 0x6BB8C590, 0xA712C50E, 0xADD7CC17, 0x617DCC89, 0xEFF2CB6A, 0x2358CBF4,
271 | 0xFA78D958, 0x36D2D9C6, 0xB85DDE25, 0x74F7DEBB, 0x7E32D7A2, 0xB298D73C, 0x3C17D0DF, 0xF0BDD041,
272 | 0x5526F3C6, 0x998CF358, 0x1703F4BB, 0xDBA9F425, 0xD16CFD3C, 0x1DC6FDA2, 0x9349FA41, 0x5FE3FADF,
273 | 0x86C3E873, 0x4A69E8ED, 0xC4E6EF0E, 0x084CEF90, 0x0289E689, 0xCE23E617, 0x40ACE1F4, 0x8C06E16A,
274 | 0xD0EBA0BB, 0x1C41A025, 0x92CEA7C6, 0x5E64A758, 0x54A1AE41, 0x980BAEDF, 0x1684A93C, 0xDA2EA9A2,
275 | 0x030EBB0E, 0xCFA4BB90, 0x412BBC73, 0x8D81BCED, 0x8744B5F4, 0x4BEEB56A, 0xC561B289, 0x09CBB217,
276 | 0xAC509190, 0x60FA910E, 0xEE7596ED, 0x22DF9673, 0x281A9F6A, 0xE4B09FF4, 0x6A3F9817, 0xA6959889,
277 | 0x7FB58A25, 0xB31F8ABB, 0x3D908D58, 0xF13A8DC6, 0xFBFF84DF, 0x37558441, 0xB9DA83A2, 0x7570833C,
278 | 0x533B85DA, 0x9F918544, 0x111E82A7, 0xDDB48239, 0xD7718B20, 0x1BDB8BBE, 0x95548C5D, 0x59FE8CC3,
279 | 0x80DE9E6F, 0x4C749EF1, 0xC2FB9912, 0x0E51998C, 0x04949095, 0xC83E900B, 0x46B197E8, 0x8A1B9776,
280 | 0x2F80B4F1, 0xE32AB46F, 0x6DA5B38C, 0xA10FB312, 0xABCABA0B, 0x6760BA95, 0xE9EFBD76, 0x2545BDE8,
281 | 0xFC65AF44, 0x30CFAFDA, 0xBE40A839, 0x72EAA8A7, 0x782FA1BE, 0xB485A120, 0x3A0AA6C3, 0xF6A0A65D,
282 | 0xAA4DE78C, 0x66E7E712, 0xE868E0F1, 0x24C2E06F, 0x2E07E976, 0xE2ADE9E8, 0x6C22EE0B, 0xA088EE95,
283 | 0x79A8FC39, 0xB502FCA7, 0x3B8DFB44, 0xF727FBDA, 0xFDE2F2C3, 0x3148F25D, 0xBFC7F5BE, 0x736DF520,
284 | 0xD6F6D6A7, 0x1A5CD639, 0x94D3D1DA, 0x5879D144, 0x52BCD85D, 0x9E16D8C3, 0x1099DF20, 0xDC33DFBE,
285 | 0x0513CD12, 0xC9B9CD8C, 0x4736CA6F, 0x8B9CCAF1, 0x8159C3E8, 0x4DF3C376, 0xC37CC495, 0x0FD6C40B,
286 | 0x7AA64737, 0xB60C47A9, 0x3883404A, 0xF42940D4, 0xFEEC49CD, 0x32464953, 0xBCC94EB0, 0x70634E2E,
287 | 0xA9435C82, 0x65E95C1C, 0xEB665BFF, 0x27CC5B61, 0x2D095278, 0xE1A352E6, 0x6F2C5505, 0xA386559B,
288 | 0x061D761C, 0xCAB77682, 0x44387161, 0x889271FF, 0x825778E6, 0x4EFD7878, 0xC0727F9B, 0x0CD87F05,
289 | 0xD5F86DA9, 0x19526D37, 0x97DD6AD4, 0x5B776A4A, 0x51B26353, 0x9D1863CD, 0x1397642E, 0xDF3D64B0,
290 | 0x83D02561, 0x4F7A25FF, 0xC1F5221C, 0x0D5F2282, 0x079A2B9B, 0xCB302B05, 0x45BF2CE6, 0x89152C78,
291 | 0x50353ED4, 0x9C9F3E4A, 0x121039A9, 0xDEBA3937, 0xD47F302E, 0x18D530B0, 0x965A3753, 0x5AF037CD,
292 | 0xFF6B144A, 0x33C114D4, 0xBD4E1337, 0x71E413A9, 0x7B211AB0, 0xB78B1A2E, 0x39041DCD, 0xF5AE1D53,
293 | 0x2C8E0FFF, 0xE0240F61, 0x6EAB0882, 0xA201081C, 0xA8C40105, 0x646E019B, 0xEAE10678, 0x264B06E6
294 | };
295 | #endregion
296 | public uint ComputeCRC(uint dwCRC, byte[] pv, uint cbLength)
297 | {
298 | uint i;
299 | uint dw2nd32;
300 |
301 | var cbRunningLength = ((cbLength < 4) ? 0 : ((cbLength)/8)*8);
302 | var cbEndUnalignedBytes = cbLength - cbRunningLength;
303 | var index = 0;
304 | for (i = 0; i < cbRunningLength/8; ++i)
305 | {
306 | dwCRC ^= BitConverter.ToUInt32(pv, index);
307 | dwCRC = CrcTableOffset88[dwCRC & 0x000000FF] ^
308 | CrcTableOffset80[(dwCRC >> 8) & 0x000000FF] ^
309 | CrcTableOffset72[(dwCRC >> 16) & 0x000000FF] ^
310 | CrcTableOffset64[(dwCRC >> 24) & 0x000000FF];
311 | index += 4;
312 |
313 | dw2nd32 = BitConverter.ToUInt32(pv, index);
314 | dwCRC = dwCRC ^
315 | CrcTableOffset56[dw2nd32 & 0x000000FF] ^
316 | CrcTableOffset48[(dw2nd32 >> 8) & 0x000000FF] ^
317 | CrcTableOffset40[(dw2nd32 >> 16) & 0x000000FF] ^
318 | CrcTableOffset32[(dw2nd32 >> 24) & 0x000000FF];
319 | index += 4;
320 | }
321 |
322 | for (i = 0; i < cbEndUnalignedBytes; ++i)
323 | {
324 | dwCRC = CrcTableOffset32[(dwCRC ^ pv[index]) & 0x000000FF] ^ (dwCRC >> 8);
325 | index++;
326 | }
327 |
328 | return dwCRC;
329 | }
330 | }
331 | }
--------------------------------------------------------------------------------
/PSTParse/Utilities/RtfDecompressor.cs:
--------------------------------------------------------------------------------
1 |
2 |
3 | namespace PSTParse.Utilities
4 | {
5 | using System;
6 | using System.Collections;
7 | using System.IO;
8 | using System.Text;
9 |
10 |
11 | ///
12 | /// Copyright 2018 Dmitry Brant
13 | ///
14 | /// Documentation for Microsoft Compressed RTF:
15 | /// https://msdn.microsoft.com/en-us/library/cc463890(v=exchg.80).aspx
16 | ///
17 | public class RtfDecompressor
18 | {
19 | private const int DictionaryLength = 0x1000;
20 | private byte[] InitialDictionary;
21 |
22 | public RtfDecompressor()
23 | {
24 | var builder = new StringBuilder();
25 | builder.Append(@"{\rtf1\ansi\mac\deff0\deftab720{\fonttbl;}{\f0\fnil \froman \fswiss \fmodern \fscript ")
26 | .Append(@"\fdecor MS Sans SerifSymbolArialTimes New RomanCourier{\colortbl\red0\green0\blue0")
27 | .Append("\r\n").Append(@"\par \pard\plain\f0\fs20\b\i\u\tab\tx");
28 | InitialDictionary = Encoding.ASCII.GetBytes(builder.ToString());
29 | }
30 |
31 | public string Decompress(Stream stream)
32 | {
33 | byte[] bytes = new byte[stream.Length];
34 | stream.Read(bytes, 0, bytes.Length);
35 | return Decompress(bytes);
36 | }
37 |
38 | public string Decompress(byte[] bytes)
39 | {
40 | try
41 | {
42 | byte[] decompressed = Decompress(bytes, false);
43 | return Encoding.ASCII.GetString(decompressed);
44 | }
45 | catch (Exception) { }
46 | return "";
47 | }
48 |
49 | public byte[] Decompress(byte[] data, bool enforceCrc)
50 | {
51 | RtfHeader header = new RtfHeader(data);
52 |
53 | if (header.compressionType.Equals("MELA"))
54 | {
55 | // uncompressed!
56 | byte[] outBytes = new byte[data.Length];
57 | data.CopyTo(outBytes, 0);
58 | return outBytes;
59 | }
60 | else if (header.compressionType.Equals("LZFu"))
61 | {
62 | if (enforceCrc)
63 | {
64 | var headerCrc = CRC(data, RtfHeader.Length);
65 | if (headerCrc != header.crc)
66 | {
67 | throw new ArgumentException("Header CRC is incorrect.");
68 | }
69 | }
70 |
71 | var dictionary = new byte[DictionaryLength];
72 | using (var destination = new MemoryStream(header.uncompressedSize))
73 | {
74 | Array.Copy(InitialDictionary, 0, dictionary, 0, InitialDictionary.Length);
75 | var dictionaryPtr = InitialDictionary.Length;
76 | try
77 | {
78 | int bytePtr = RtfHeader.Length;
79 | while (bytePtr < data.Length)
80 | {
81 | var control = new RtfControl(data[bytePtr]);
82 | var offset = 1;
83 |
84 | for (int j = 0; j < control.flags.Length; j++)
85 | {
86 | if (control.flags[j])
87 | {
88 | destination.WriteByte(data[bytePtr + offset]);
89 | dictionary[dictionaryPtr] = data[bytePtr + offset];
90 | dictionaryPtr++;
91 | dictionaryPtr %= DictionaryLength;
92 | }
93 | else
94 | {
95 | var word = (((int)(data[bytePtr + offset])) << 8) | data[bytePtr + offset + 1];
96 | var upper = (word & 0xFFF0) >> 4;
97 | var lower = (word & 0xF) + 2;
98 |
99 | if (upper == dictionaryPtr)
100 | {
101 | return destination.ToArray();
102 | }
103 |
104 | for (int k = 0; k < lower; k++)
105 | {
106 | var correctedOffset = (upper + k);
107 | correctedOffset %= DictionaryLength;
108 | if (destination.Position == header.uncompressedSize)
109 | {
110 | return destination.ToArray();
111 | }
112 | destination.WriteByte(dictionary[correctedOffset]);
113 | dictionary[dictionaryPtr] = dictionary[correctedOffset];
114 | dictionaryPtr++;
115 | dictionaryPtr %= DictionaryLength;
116 | }
117 | offset++;
118 | }
119 | offset++;
120 | }
121 | bytePtr += control.length;
122 | }
123 | }
124 | catch (Exception) { }
125 |
126 | // return partial result in case of error
127 | return destination.ToArray();
128 | }
129 | }
130 | throw new ApplicationException("Unrecognized compression type.");
131 | }
132 |
133 | private struct RtfHeader
134 | {
135 | public const int Length = 0x10;
136 | public int compressedSize;
137 | public int uncompressedSize;
138 | public string compressionType;
139 | public uint crc;
140 |
141 | public RtfHeader(byte[] bytes)
142 | {
143 | compressedSize = BitConverter.ToInt32(bytes, 0);
144 | uncompressedSize = BitConverter.ToInt32(bytes, 4);
145 | compressionType = Encoding.ASCII.GetString(bytes, 8, 4);
146 | crc = BitConverter.ToUInt32(bytes, 12);
147 | }
148 | }
149 |
150 | private struct RtfControl
151 | {
152 | public BitArray flags;
153 | public int length;
154 |
155 | public RtfControl(byte b)
156 | {
157 | flags = new BitArray(8);
158 | int zeros = 0;
159 | for (int i = 0; i < flags.Length; i++)
160 | {
161 | flags[i] = ((b & (0x1 << i)) == 0);
162 | zeros += flags[i] ? 1 : 0;
163 | }
164 | length = ((8 - zeros) * 2) + zeros + 1;
165 | }
166 | }
167 |
168 | private static uint CRC(byte[] buffer, int offset)
169 | {
170 | uint crc = 0;
171 | for (int i = offset; i < buffer.Length; i++)
172 | {
173 | crc = CRC32.CrcTableOffset32[(crc ^ buffer[i]) & 0xFF] ^ (crc >> 8);
174 | }
175 | return crc;
176 | }
177 | }
178 | }
--------------------------------------------------------------------------------
/PSTParse/Utilities/Utilities.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 |
4 | namespace PSTParse.Utilities
5 | {
6 | public static class Utilities
7 | {
8 | public static T Lazy(ref T field, Func func) where T : class
9 | {
10 | return LazyInitializer.EnsureInitialized(ref field, func);
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/PSTParseApp.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26124.0
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSTParse", "PSTParse\PSTParse.csproj", "{03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSTParseApp", "PSTParseApp\PSTParseApp.csproj", "{4DE372B6-A640-46FC-BE30-3E1C71E35469}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Debug|x64 = Debug|x64
14 | Debug|x86 = Debug|x86
15 | Release|Any CPU = Release|Any CPU
16 | Release|x64 = Release|x64
17 | Release|x86 = Release|x86
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
23 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Debug|x64.ActiveCfg = Debug|Any CPU
26 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Debug|x64.Build.0 = Debug|Any CPU
27 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Debug|x86.ActiveCfg = Debug|Any CPU
28 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Debug|x86.Build.0 = Debug|Any CPU
29 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Release|x64.ActiveCfg = Release|Any CPU
32 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Release|x64.Build.0 = Release|Any CPU
33 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Release|x86.ActiveCfg = Release|Any CPU
34 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Release|x86.Build.0 = Release|Any CPU
35 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Debug|x64.ActiveCfg = Debug|Any CPU
38 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Debug|x64.Build.0 = Debug|Any CPU
39 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Debug|x86.ActiveCfg = Debug|Any CPU
40 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Debug|x86.Build.0 = Debug|Any CPU
41 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Release|Any CPU.ActiveCfg = Release|Any CPU
42 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Release|Any CPU.Build.0 = Release|Any CPU
43 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Release|x64.ActiveCfg = Release|Any CPU
44 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Release|x64.Build.0 = Release|Any CPU
45 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Release|x86.ActiveCfg = Release|Any CPU
46 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Release|x86.Build.0 = Release|Any CPU
47 | EndGlobalSection
48 | EndGlobal
49 |
--------------------------------------------------------------------------------
/PSTParseApp/PSTParseApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Exe
9 | net60
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/PSTParseApp/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using PSTParse;
5 | using static System.Console;
6 |
7 | namespace PSTParseApp
8 | {
9 | class Program
10 | {
11 | static void Main(string[] args)
12 | {
13 | var sw = new Stopwatch();
14 | sw.Start();
15 | // var pstPath = @"single_item.pst";
16 | var pstPath = @"passwordprotectedPST.pst";
17 | var fileInfo = new FileInfo(pstPath);
18 | var pstSizeGigabytes = ((double)fileInfo.Length / 1000 / 1000 / 1000).ToString("0.000");
19 | using (var file = new PSTFile(pstPath))
20 | {
21 | var isProtected = file.IsPasswordProtected();
22 | Debug.Assert((ulong)fileInfo.Length == file.Size);
23 | //Console.WriteLine("Magic value: " + file.Header.DWMagic);
24 | //Console.WriteLine("Is Ansi? " + file.Header.IsANSI);
25 |
26 | var stack = new Stack();
27 | stack.Push(file.TopOfPST);
28 | var totalCount = 0;
29 | //var maxSearchSize = 1500;
30 | var maxSearchSize = int.MaxValue;
31 | var totalEncryptedCount = 0;
32 | var totalUnsentCount = 0;
33 | var skippedFolders = new List();
34 | while (stack.Count > 0)
35 | {
36 | var curFolder = stack.Pop();
37 |
38 | foreach (var child in curFolder.SubFolders)
39 | {
40 | stack.Push(child);
41 | }
42 | var count = curFolder.Count;
43 | var line = $"{string.Join(" -> ", curFolder.Path)}({curFolder.ContainerClass}) ({count} messages)";
44 | if (curFolder.Path.Count > 1 && curFolder.ContainerClass != "" && curFolder.ContainerClass != "IPF.Note")
45 | {
46 | skippedFolders.Add(line);
47 | continue;
48 | }
49 | WriteLine(line);
50 |
51 | var currentFolderCount = 0;
52 | foreach (var message in curFolder.GetIpmNotes())
53 | //foreach (var ipmItem in curFolder.GetIpmItems())
54 | {
55 | WriteLine(message.BodyPlainText);
56 | totalCount++;
57 | currentFolderCount++;
58 |
59 | //if (!(ipmItem is Message message))
60 | //{
61 | // nonMessageTypes++;
62 | // continue;
63 | //}
64 | if (message.Unsent)
65 | {
66 | totalUnsentCount++;
67 | continue;
68 | }
69 |
70 | //if (message.IsRMSEncrypted)
71 | //{
72 | // totalEncryptedCount++;
73 | // //stack.Clear();
74 | // //break;
75 | //}
76 | Debug.Assert(message.IsRMSEncryptedHeaders == message.IsRMSEncrypted, "encryption mismatch, big problems");
77 | if (message.IsRMSEncryptedHeaders)
78 | {
79 | totalEncryptedCount++;
80 | //stack.Clear();
81 | //break;
82 | }
83 | if (totalCount == maxSearchSize)
84 | {
85 | stack.Clear();
86 | break;
87 | }
88 |
89 | //var recipients = message.Recipients;
90 | //if (!message.HasAttachments) continue;
91 | //foreach (var attachment in message.AttachmentHeaders)
92 | //{
93 |
94 | //}
95 | }
96 | }
97 | sw.Stop();
98 | var elapsedSeconds = (double)sw.ElapsedMilliseconds / 1000;
99 | WriteLine("{0} messages total", totalCount);
100 | WriteLine("{0} encrypted messages total", totalEncryptedCount);
101 | WriteLine("{0} totalUnsentCount", totalUnsentCount);
102 | WriteLine("Parsed {0} ({1} GB) in {2:0.00} seconds", Path.GetFileName(pstPath), pstSizeGigabytes, elapsedSeconds);
103 |
104 | WriteLine("\r\nSkipped Folders:\r\n");
105 | foreach (var line in skippedFolders)
106 | {
107 | WriteLine(line);
108 | }
109 | }
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PST-Parser
2 |
3 | A library for reading the [PST mailstore file format.](http://msdn.microsoft.com/en-us/library/ff385210(v=office.12).aspx)
4 |
5 | This library is intended to be as accurate, fast implementation of the PST mailstore file format specification. The original motivation for this project came from my experiences with other mailstore libraries that either 1) required Outlook to be installed in order to function or 2) were developed inconsistently by a third party. Such inconsistencies range from libraries that "missed" items and other libraries that failed when encountering errors. The intention of this project is to provide a basis to developers of applications that need to read and write to the PST format.
6 |
7 | ## PST Structure Overview
8 |
9 | The structure of the PST file format is divided into 3 layers: NDB layer, LTP layer, and the Messaging Layer. Each layer is implemented on top of the preceding layer. For example, the LTP layer may implement a heap which is stored on a node in the NDB layer. Each layer is divided into it's own namespace. The main entry point of parsing a PST is through the header. In the header, information about the format and encoding is stored. The first offsets for the NDB layer are contained Root structure in the header.
10 |
11 | The Node Database (NDB) layer layer consists of two [B-trees](http://en.wikipedia.org/wiki/Btree) : one for nodes and another for data blocks. Each B-tree implementation consists of intermediate blocks and leaf blocks. The node B-tree consists of nodes that reference block IDs (BIDs) and sub nodes. BIDs are used to traverse the data block B-tree to resolve to absolute offsets to data streams in the PST. Data stream themselves can be in one data block or stored in another BTree if the data stream is too large to fit in one page. XBLOCK and XXBLOCKs structures are used to store the B-trees that are used to store large data streams.
12 |
13 | The LTP layer provides the interface for the messaging layer to access properties and variable arrays of content. The base of the LTP layer is a heap which can be stored on a node (heap-on-node or HN). On the HN, yet another B-tree (B-tree-on-heap or BTH) is implemented and is used to store values on the HN using keys. The BTH (can be thought of just as a heap) is used to store Property Contexts (PCs) and Table Contexts (TCs).
14 |
15 | The messaging layer uses the LTP layer to represent folder hierarchies and the messages that exist in a give folder.
16 |
17 | ## Installation
18 | ```ps
19 | Install-Package PSTParse
20 | ```
21 |
22 | ## Usage
23 |
24 | ```c#
25 | using System.Collections.Generic;
26 | using PSTParse;
27 |
28 | namespace PSTParseApp
29 | {
30 | class Program
31 | {
32 | static void Main(string[] args)
33 | {
34 | var pstPath = @"path\to\file.pst";
35 | using (var file = new PSTFile(pstPath))
36 | {
37 | var stack = new Stack();
38 | stack.Push(file.TopOfPST);
39 | while (stack.Count > 0)
40 | {
41 | var curFolder = stack.Pop();
42 | foreach (var child in curFolder.SubFolders)
43 | {
44 | stack.Push(child);
45 | }
46 |
47 | foreach (var ipmItem in curFolder.GetIpmItems())
48 | {
49 | // process item
50 | }
51 | }
52 | }
53 | }
54 | }
55 | }
56 | ```
57 |
--------------------------------------------------------------------------------