// Some sub-nodes use sub-sub-nodes to hold local data, so we need to give access to both levels public Node LookupSubNodeAndReadItsSubNodeBtree(FileStream fs, BTree <Node> subNodeTree, NID nid, out BTree <Node> childSubNodeTree) { childSubNodeTree = null; var rn = LookupSubNode(subNodeTree, nid); if (rn == null) { throw new XstException("Node block does not exist"); } // If there is a sub-node, read its btree so that we can resolve references to nodes in it later if (rn.SubDataBid != 0) { childSubNodeTree = ReadSubNodeBtree(fs, rn.SubDataBid); } return(rn); }
private void ReadMessageTables(FileStream fs, BTree <Node> subNodeTree, Message m, bool isAttached = false) { // Read the recipient table for the message var recipientsNid = new NID(EnidSpecial.NID_RECIPIENT_TABLE); if (ltp.IsTablePresent(subNodeTree, recipientsNid)) { var rs = ltp.ReadTable <Recipient>(fs, subNodeTree, recipientsNid, pgMessageRecipient, null, (r, p) => r.Properties.Add(p)); m.Recipients.Clear(); foreach (var r in rs) { // Sort the properties List <Property> lp = new List <Property>(r.Properties); lp.Sort((a, b) => a.Tag.CompareTo(b.Tag)); r.Properties.Clear(); foreach (var p in lp) { r.Properties.Add(p); } m.Recipients.Add(r); } } // Read any attachments var attachmentsNid = new NID(EnidSpecial.NID_ATTACHMENT_TABLE); if (m.HasAttachment) { if (!ltp.IsTablePresent(subNodeTree, attachmentsNid)) { throw new XstException("Could not find expected Attachment table"); } // Read the attachment table, which is held in the subnode of the message var atts = ltp.ReadTable <Attachment>(fs, subNodeTree, attachmentsNid, pgAttachmentList, (a, id) => a.Nid = new NID(id)).ToList(); foreach (var a in atts) { a.XstFile = this; // For lazy reading of the complete properties a.Parent = m; // If the long name wasn't in the attachment table, go look for it in the attachment properties if (a.LongFileName == null) { ltp.ReadProperties <Attachment>(fs, subNodeTree, a.Nid, pgAttachmentName, a); } // Read properties relating to HTML images presented as attachments ltp.ReadProperties <Attachment>(fs, subNodeTree, a.Nid, pgAttachedHtmlImages, a); // If this is an embedded email, tell the attachment where to look for its properties // This is needed because the email node is not in the main node tree if (isAttached) { a.subNodeTreeProperties = subNodeTree; } } m.SortAndSaveAttachments(atts); } }
private dynamic ReadTableColumnValue(FileStream fs, BTree <Node> subNodeTree, List <HNDataBlock> blocks, RowDataBlock db, long rowOffset, TCOLDESC col) { dynamic val = null; HNID hnid; switch (col.wPropType) { case EpropertyType.PtypInteger32: if (col.cbData != 4) { throw new XstException("Unexpected property length"); } val = Map.MapType <Int32>(db.Buffer, (int)rowOffset + col.ibData); break; case EpropertyType.PtypBoolean: val = (db.Buffer[rowOffset + col.ibData] == 0x01); break; case EpropertyType.PtypBinary: if (col.cbData != 4) { throw new XstException("Unexpected property length"); } hnid = Map.MapType <HNID>(db.Buffer, (int)rowOffset + col.ibData); if (!hnid.HasValue) { val = ""; } else { var buf = GetBytesForHNID(fs, blocks, subNodeTree, hnid); if (buf == null) { val = null; } else { val = buf; } } break; case EpropertyType.PtypString: // Unicode string if (col.cbData != 4) { throw new XstException("Unexpected property length"); } hnid = Map.MapType <HNID>(db.Buffer, (int)rowOffset + col.ibData); if (!hnid.HasValue) { val = ""; } else { var buf = GetBytesForHNID(fs, blocks, subNodeTree, hnid); if (buf == null) { val = "<Could not read string value>"; } else { int skip = 0; if (col.wPropId == EpropertyTag.PidTagSubjectW) { if (buf[0] == 0x01 && buf[1] == 0x00) // Unicode 0x01 { skip = 4; } } val = Encoding.Unicode.GetString(buf, skip, buf.Length - skip); } } if (val == "" && col.wPropId == EpropertyTag.PidTagSubjectW) { val = "<No subject>"; } break; case EpropertyType.PtypString8: // Multibyte string in variable encoding if (col.cbData != 4) { throw new XstException("Unexpected property length"); } hnid = Map.MapType <HNID>(db.Buffer, (int)rowOffset + col.ibData); if (!hnid.HasValue) { val = ""; } else { var buf = GetBytesForHNID(fs, blocks, subNodeTree, hnid); if (buf == null) { val = "<Could not read string value>"; } else { int skip = 0; if (col.wPropId == EpropertyTag.PidTagSubjectW) { if (buf[0] == 0x01) // ANSI 0x01 { skip = 2; } } val = Encoding.UTF8.GetString(buf, skip, buf.Length - skip); } } if (val == "" && col.wPropId == EpropertyTag.PidTagSubjectW) { val = "<No subject>"; } break; case EpropertyType.PtypTime: // In a Table Context, time values are held in line if (col.cbData != 8) { throw new XstException("Unexpected property length"); } var fileTime = Map.MapType <Int64>(db.Buffer, (int)rowOffset + col.ibData); try { val = DateTime.FromFileTimeUtc(fileTime); } catch (System.ArgumentOutOfRangeException) { val = null; } break; default: val = String.Format("Unsupported property type {0}", col.wPropType); break; } return(val); }
// Read the data rows of a table, populating the members of target type T as specified by the supplied property getters, and optionally getting all columns as properties private IEnumerable <T> ReadTableData <T>(FileStream fs, TCINFO t, List <HNDataBlock> blocks, List <RowDataBlock> dataBlocks, TCOLDESC[] cols, List <TCOLDESC> colsToGet, BTree <Node> subNodeTree, TCROWIDUnicode[] indexes, PropertyGetters <T> g, Action <T, UInt32> idGetter, Action <T, Property> storeProp) where T : new() { int rgCEBSize = (int)Math.Ceiling((decimal)t.cCols / 8); int rowsPerBlock; if (ndb.IsUnicode4K) { rowsPerBlock = (ndb.BlockSize4K - Marshal.SizeOf(typeof(BLOCKTRAILERUnicode4K))) / t.rgibTCI_bm; } else if (ndb.IsUnicode) { rowsPerBlock = (ndb.BlockSize - Marshal.SizeOf(typeof(BLOCKTRAILERUnicode))) / t.rgibTCI_bm; } else { rowsPerBlock = (ndb.BlockSize - Marshal.SizeOf(typeof(BLOCKTRAILERANSI))) / t.rgibTCI_bm; } foreach (var index in indexes) { int blockNum = (int)(index.dwRowIndex / rowsPerBlock); if (blockNum >= dataBlocks.Count) { throw new XstException("Data block number out of bounds"); } var db = dataBlocks[blockNum]; long rowOffset = db.Offset + (index.dwRowIndex % rowsPerBlock) * t.rgibTCI_bm; T row = new T(); // Retrieve the node ID that accesses the message if (idGetter != null) { idGetter(row, index.dwRowID); } if (rowOffset + t.rgibTCI_bm > db.Offset + db.Length) { throw new XstException("Out of bounds reading table data"); } // Read the column existence data var rgCEB = Map.MapArray <Byte>(db.Buffer, (int)(rowOffset + t.rgibTCI_1b), rgCEBSize); foreach (var col in colsToGet) { // Check if the column exists if ((rgCEB[col.iBit / 8] & (0x01 << (7 - (col.iBit % 8)))) == 0) { continue; } dynamic val = ReadTableColumnValue(fs, subNodeTree, blocks, db, rowOffset, col); g[col.wPropId](row, val); } // If we were asked for all column values as properties, read them and store them if (storeProp != null) { foreach (var col in cols) { // Check if the column exists if ((rgCEB[col.iBit / 8] & (0x01 << (7 - (col.iBit % 8)))) == 0) { continue; } dynamic val = ReadTableColumnValue(fs, subNodeTree, blocks, db, rowOffset, col); Property p = CreatePropertyObject(fs, col.wPropId, val); storeProp(row, p); } } yield return(row); } yield break; // No more entries }
// Common implementation of table reading takes a data ID for a block in the main block tree private IEnumerable <T> ReadTableInternal <T>(FileStream fs, BTree <Node> subNodeTree, UInt64 dataBid, PropertyGetters <T> g, Action <T, UInt32> idGetter, Action <T, Property> storeProp) where T : new() { var blocks = ReadHeapOnNode(fs, dataBid); var h = blocks.First(); if (h.bClientSig != EbType.bTypeTC) { throw new XstException("Was expecting a table"); } // Read the table information var t = MapType <TCINFO>(blocks, h.hidUserRoot); // Read the column descriptions var cols = MapArray <TCOLDESC>(blocks, h.hidUserRoot, t.cCols, Marshal.SizeOf(typeof(TCINFO))); // Read the row index TCROWIDUnicode[] indexes; if (ndb.IsUnicode) { indexes = ReadBTHIndex <TCROWIDUnicode>(blocks, t.hidRowIndex).ToArray(); } else { // For ANSI, convert the index entries to the slightly more capacious Unicode equivalents indexes = ReadBTHIndex <TCROWIDANSI>(blocks, t.hidRowIndex).Select(e => new TCROWIDUnicode { dwRowID = e.dwRowID, dwRowIndex = e.dwRowIndex }).ToArray(); } // Work out which of the columns are both present in the table and have getters defined var colsToGet = cols.Where(c => g.ContainsKey(c.wPropId)).ToList(); // The data rows may be held in line, or in a sub node if (t.hnidRows.IsHID) { // Data is in line var buf = GetBytesForHNID(fs, blocks, subNodeTree, t.hnidRows); var dataBlocks = new List <RowDataBlock> { new RowDataBlock { Buffer = buf, Offset = 0, Length = buf.Length, } }; return(ReadTableData <T>(fs, t, blocks, dataBlocks, cols, colsToGet, subNodeTree, indexes, g, idGetter, storeProp)); } else if (t.hnidRows.NID.HasValue) { // Don't use GetBytesForHNID in this case, as we need to handle multiple blocks var dataBlocks = ReadSubNodeRowDataBlocks(fs, subNodeTree, t.hnidRows.NID); return(ReadTableData <T>(fs, t, blocks, dataBlocks, cols, colsToGet, subNodeTree, indexes, g, idGetter, storeProp)); } else { return(Enumerable.Empty <T>()); } }
private dynamic ReadPropertyValue(FileStream fs, BTree <Node> subNodeTree, List <HNDataBlock> blocks, PCBTH prop) { dynamic val = null; byte[] buf = null; switch (prop.wPropType) { case EpropertyType.PtypInteger16: val = (Int16)prop.dwValueHnid.dwValue; break; case EpropertyType.PtypInteger32: val = prop.dwValueHnid.dwValue; break; case EpropertyType.PtypInteger64: buf = GetBytesForHNID(fs, blocks, subNodeTree, prop.dwValueHnid); if (buf == null) { val = "<Could not read Integer64 value>"; } else { val = Map.MapType <Int64>(buf); } break; case EpropertyType.PtypFloating64: buf = GetBytesForHNID(fs, blocks, subNodeTree, prop.dwValueHnid); if (buf == null) { val = "<Could not read Floating64 value>"; } else { val = Map.MapType <Double>(buf); } break; case EpropertyType.PtypMultipleInteger32: buf = GetBytesForHNID(fs, blocks, subNodeTree, prop.dwValueHnid); if (buf == null) { val = "<Could not read MultipleInteger32 value>"; } else { val = Map.MapArray <Int32>(buf, 0, buf.Length / sizeof(Int32)); } break; case EpropertyType.PtypBoolean: val = (prop.dwValueHnid.dwValue == 0x01); break; case EpropertyType.PtypBinary: if (prop.dwValueHnid.HasValue && prop.dwValueHnid.hidType != EnidType.HID && prop.wPropId == EpropertyTag.PidTagAttachDataBinary) { // Special case for out of line attachment contents: don't dereference to binary yet val = prop.dwValueHnid.NID; } else { buf = GetBytesForHNID(fs, blocks, subNodeTree, prop.dwValueHnid); if (buf == null) { val = null; } else { val = buf; } } break; case EpropertyType.PtypString: // Unicode string if (!prop.dwValueHnid.HasValue) { val = ""; } else { buf = GetBytesForHNID(fs, blocks, subNodeTree, prop.dwValueHnid); if (buf == null) { val = "<Could not read string value>"; } else { val = Encoding.Unicode.GetString(buf, 0, buf.Length); } } break; case EpropertyType.PtypString8: // Multipoint string in variable encoding if (!prop.dwValueHnid.HasValue) { val = ""; } else { buf = GetBytesForHNID(fs, blocks, subNodeTree, prop.dwValueHnid); if (buf == null) { val = "<Could not read string value>"; } else { val = Encoding.UTF8.GetString(buf, 0, buf.Length); } } break; case EpropertyType.PtypMultipleString: // Unicode strings if (!prop.dwValueHnid.HasValue) { val = ""; } else { buf = GetBytesForHNID(fs, blocks, subNodeTree, prop.dwValueHnid); if (buf == null) { val = "<Could not read MultipleString value>"; } else { var count = Map.MapType <UInt32>(buf); var offsets = Map.MapArray <UInt32>(buf, sizeof(UInt32), (int)count); var ss = new string[count]; // Offsets are relative to the start of the buffer for (int i = 0; i < count; i++) { int len; if (i < count - 1) { len = (int)(offsets[i + 1] - offsets[i]); } else { len = buf.Length - (int)offsets[i]; } ss[i] = Encoding.Unicode.GetString(buf, (int)offsets[i], len); } val = ss; } } break; case EpropertyType.PtypTime: // In a Property Context, time values are references to data buf = GetBytesForHNID(fs, blocks, subNodeTree, prop.dwValueHnid); if (buf != null) { var fileTime = Map.MapType <Int64>(buf); val = DateTime.FromFileTimeUtc(fileTime); } break; case EpropertyType.PtypGuid: buf = GetBytesForHNID(fs, blocks, subNodeTree, prop.dwValueHnid); if (buf == null) { val = "<Could not read Guid value>"; } else { val = new Guid(buf); } break; case EpropertyType.PtypObject: buf = GetBytesForHNID(fs, blocks, subNodeTree, prop.dwValueHnid); if (buf == null) { val = "<Could not read Object value>"; } else { val = Map.MapType <PtypObjectValue>(buf); } break; default: val = String.Format("Unsupported property type {0}", prop.wPropType); break; } return(val); }
// Test for the presence of an optional table in the supplied sub node tree public bool IsTablePresent(BTree <Node> subNodeTree, NID nid) { return(subNodeTree != null && NDB.LookupSubNode(subNodeTree, nid) != null); }