{
  $Project$
  $Workfile$
  $Revision$
  $DateUTC$
  $Id$

  This file is part of the Indy (Internet Direct) project, and is offered
  under the dual-licensing agreement described on the Indy website.
  (http://www.indyproject.org/)

  Copyright:
   (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
  $Log$
}
{
{   Rev 1.3    16/01/2005 22:33:06  CCostelloe
{ Minor update
}
{
{   Rev 1.2    22/12/2004 23:09:52  CCostelloe
{ Another intermediate check-in, this sorts out the compressed RTF message and
{ (if it has one) the message .Body.
}
{
{   Rev 1.1    18/12/2004 20:35:24  CCostelloe
{ Intermediate check-in, lots more to be done.
}
{
{   Rev 1.0    02/12/2004 22:56:26  CCostelloe
{ Initial version
}
unit IdCoderTNEF;

{
    This is for decoding Microsoft's TNEF email messages, which are usually
      transmitted as an attachment called either "WINMAIL.DAT" or "ATT00001.DAT"
      in a MIME-encoded message (though other variants of the file name, but not
      the extension, are occasionally encountered).
      The MIME type should be "application/ms-tnef".
    This is deliberately NOT similar to the other coder classes, this
      should usually be used by passing in a TNEF TIdAttachment which has
      been extracted from a message.
    You can optionally get a debugging log of the parsing by setting the third
      optional parameter to True, as in "TIdCoderTNEF.Parse(A_TIdBytes,
      A_TIdMessage, True);", and then load the log into a TMemo by calling
      "Memo1.Text := A_TIdCoderTNEF.Log;".
    The aim of the initial implementation was just to extract any attachments from
      the TNEF, along with their filenames, and put them in the TIdMessage's
      MessageParts for the user.
    Some additional applicable fields have also been parsed out of the TNEF
      and inserted into the TIdMessage.  This is an ongoing development.
    METHODOLOGY:
      A TNEF is a collection of blocks of data, each block is either a
        IdTNEFLvlMessage or a IdTNEFLvlAttachment block.  IdTNEFLvlAttachment
        blocks are straightforward, see the parsing below.
        A IdTNEFLvlMessage block may contain an item like the message subject,
        date sent, etc.  However, a block may also contain a IdTNEFattMAPIProps,
        which contains sub-blocks corresponding to MAPI properties which will
        also typically include the message subject, date sent, etc.
        The methodology used by me is that the IdTNEFLvlMessage level takes
        priority, i.e. if there is a message subject at both the IdTNEFLvlMessage
        and the IdTNEFattMAPIProps level, then the IdTNEFLvlMessage is the one
        that will be used.
      The TIdMessage is filled in from the TNEF in a similar manner to the way
        Indy decodes MIME messages.  Particularly note that the message text
        is often only in an RTF format and so it will be put in a TIdText part
        and NOT in the TIdMessage.Body.  The TIdMessage.Body MAY contain the
        plain-text version of the message, IF it is present.
      Parts (either TIdText or TIdAttachmentFile) are added to the TIdMessage
        in the order they are encountered.  This may be different from an
        RFC-compliant MIME email, which (in the case of alternative versions
        of the message text) puts the simplest first, i.e. text/plain
        before text/html before text/rtf.

To do:
    When finished decoding, rescan the parts and insert parts like
      'multipart/alternative' and set up the ParentPart pointers as required to
      imitate Indy's treatment of MIME parts in Indy10.
    For LLong, LShort, get them to get signed values.
    GetMapiSysTime or GetTime may be out by an hour?  Time zone issue?
    Add meaningful headers like ContentType to message parts.
    Some TIdMessage fields may be found in different TNEF fields (e.g. sender/from).
      They don't appear to be consistent in practice, and there may be semantical
      differences between the usage of terms like Sender between TNEF and MIME.
    See the "TODO"s in the code below.
}

interface

{$I IdCompilerDefines.inc}

uses
  Classes,
  IdGlobal,
  IdMessage,
  IdException,
  IdAttachment,
  IdAttachmentFile,
  SysUtils;

type
  TIdCoderTNEF = class(TObject)
  private
    FData: TStream;         //Used for walking through the file
    FKey: Word;             //Every TNEF has one, but no-one seems to know why!
    FLog: string;           //The (optional) debugging log goes here
    FDoLogging: Boolean;    //Should we be doing the optional logging?
    FMsg: TIdMessage;       //The destination for our extracted attachments
    FCurrentAttachment: TIdAttachment;  //Attachment we are currently decoding into
    FReceiptRequested: Boolean;  //Need to cache this because receipt flag may precede sender address
    //
    procedure DoLog(const AMsg: String; const AAppendSize: Boolean = True);
    procedure DoLogFmt(const AFormat: string; const Args: array of const; AAppendSize: Boolean = True);
    //Low-level utility functions:
    function  GetMultipleUnicodeOrString8String(AType: Word): TIdUnicodeString;
    function  GetUnicodeOrString8String(AType: Word): TIdUnicodeString;
    function  GetByte: Byte;
    function  GetBytes(ALength: Integer; APeek: Boolean = False): TIdBytes;
    function  GetByteAsHexString: string; overload;
    function  GetByteAsHexString(AByte: Byte): string; overload;
    function  GetByteAsChar(AByte: Byte): char;
    function  GetBytesAsHexString(ACount: integer): string;
    function  GetWord: Word;
    function  GetLongWord: LongWord;
    function  GetInt64: Int64;
    function  GetString(ALength: Word): string;
    function  GetDate(ALength: Word): TDateTime;
    procedure Skip(ACount: integer);
    procedure CheckForEof(ANumBytesRequested: integer);
    procedure Checksum(ANumBytesToCheck: integer);
    function  PadWithZeroes(const AStr: string; ACount: integer): string;
    procedure DumpBytes(const ABytes: TIdBytes);
    //Attribute-specific stuff...
    function  GetAttributeString(const AAttributeName: string; AType: Word): string;
    //MAPI parsing...
    function  GetStringForMapiType(AType: Word): string;
    function  GetMapiBoolean(AType: Word; const AText: string): Smallint;
    function  GetMapiLong(AType: Word; const AText: string): Longint;
    function  GetMapiStrings(AType: Word; const AText: string): string;
    function  GetMapiBinary(AType: Word; const AText: string): TIdBytes;
    function  GetMapiBinaryAsEmailName(AType: Word; const AText: string): string;
    function  GetMapiBinaryAsString(AType: Word; const AText: string): string;
    //function  GetMapiObject(AType: Word; AText: string): TIdBytes;
    function  GetMapiItemAsBytes(AType: Word; const AText: string): TIdBytes;
    function  GetMapiItemAsBytesPossiblyCompressed(AType: Word; const AText: string): TIdBytes;
    function  DecompressRtf(ACount, ALength: LongWord; AType: Word; const AText: string): TIdBytes;
    function  GetMapiSysTime(AType: Word; const AText: string): TDateTime;
    function  InternalGetMapiItemAsBytes(ACount, ALength: LongWord; AType: Word; const AText: string): TIdBytes;
  protected
    procedure ParseMessageBlock;
    procedure ParseAttachmentBlock;
    procedure ParseAttribute(AAttribute, AType: Word);
    procedure ParseMapiProps(ALength: LongWord);
    procedure ParseMapiProp;
    procedure IsCurrentAttachmentValid;
    //For debugging log...
    function  GetStringForAttribute(AAttribute: Word): string;
    function  GetStringForType(AType: Word): string;
  public
    //The following is the normal parser call you would use...
    procedure Parse(const AIn: TIdAttachment; AMsg: TIdMessage; ALog: Boolean = False); overload;
    //TIdIMAP4 should set up a stream & use this...
    procedure Parse(const AIn: TStream; AMsg: TIdMessage; ALog: Boolean = False); overload;
    //The TIdBytes and string versions are really for debugging...
    procedure Parse(const AIn: TIdBytes; AMsg: TIdMessage; ALog: Boolean = False); overload;
    procedure Parse(const AIn: string; AMsg: TIdMessage; ALog: Boolean = False);   overload;
    //Tells you if a filename matches TNEF semantics (winmail.dat, att0001.dat)
    class function IsFilenameTnef(const AFilename: string): Boolean; static; {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use standalone IsFilenameTnef() function'{$ENDIF};{$ENDIF}
    property  Key: Word read FKey;  //TODO: Does this have a meaningful use?
    property  Log: string read FLog;
  end;

  EIdTnefInvalidTNEFSignature = class(EIdException);
  EIdTnefRanOutOfBytes = class(EIdException);
  EIdTnefUnknownBlockType = class(EIdException);
  EIdTnefChecksumFailure = class(EIdException);
  EIdTnefCurrentAttachmentInvalid = class(EIdException);
  EIdTnefAttributeUnexpectedType = class(EIdException);
  EIdTnefUnexpectedType = class(EIdException);
  EIdTnefUnexpectedValue = class(EIdException);
  EIdTnefNotSupported = class(EIdException);
  EIdTnefUnknownMapiType = class(EIdException);
  EIdTnefCorruptData = class(EIdException);

function IsFilenameTnef(const AFilename: string): Boolean;

implementation

uses
  DateUtils, IdMessageClient, IdText, IdStream;

const
  //Initial RTF-compression decode string...
  IdTNEF_decode_string: string = '{\rtf1\ansi\mac\deff0\deftab720{\fonttbl;}{\f0\fnil \froman \fswiss \fmodern \fscript \fdecor MS Sans SerifSymbolArialTimes New RomanCourier{\colortbl\red0\green0\blue0'+#10#13+'\par \pard\plain\f0\fs20\b\i\u\tab\tx';  {Do not localize}

  //The following are equivalents to those defined Microsoft's TNEF.H.
  //File signature...
  IdTNEFSignature = $223E9F78;          //Every TNEF should have this

  //Highest-level block types...
  IdTNEFLvlMessage    = 1;              //Corresponds to Microsoft's LVL_MESSAGE
  IdTNEFLvlAttachment = 2;              //Corresponds to Microsoft's LVL_ATTACHMENT

  //Data types...
  IdTNEFAtpTriples  = 0;                //Corresponds to Microsoft's AtpTriples, similarly for the following
  IdTNEFAtpString   = 1;
  IdTNEFAtpText     = 2;
  IdTNEFAtpDate     = 3;
  IdTNEFAtpShort    = 4;
  IdTNEFAtpLong     = 5;
  IdTNEFAtpByte     = 6;
  IdTNEFAtpWord     = 7;
  IdTNEFAtpDWord    = 8;
  IdTNEFAtpMax      = 9;

  //Attribute types...
  IdTNEFattNull	                         = $0000;
  IdTNEFattFrom                          = $8000;  // /* PR_ORIGINATOR_RETURN_ADDRESS */
  IdTNEFattSubject                       = $8004;  // /* PR_SUBJECT */
  IdTNEFattDateSent                      = $8005;  // /* PR_CLIENT_SUBMIT_TIME */
  IdTNEFattDateRecd                      = $8006;  // /* PR_MESSAGE_DELIVERY_TIME */
  IdTNEFattMessageStatus                 = $8007;  // /* PR_MESSAGE_FLAGS */
  IdTNEFattMessageClass                  = $8008;  // /* PR_MESSAGE_CLASS */
  IdTNEFattMessageID                     = $8009;  // /* PR_MESSAGE_ID */
  IdTNEFattParentID                      = $800A;  // /* PR_PARENT_ID */
  IdTNEFattConversationID                = $800B;  // /* PR_CONVERSATION_ID */
  IdTNEFattBody                          = $800C;  // /* PR_BODY */
  IdTNEFattPriority                      = $800D;  // /* PR_IMPORTANCE */
  IdTNEFattAttachData                    = $800F;  // /* PR_ATTACH_DATA_xxx */
  IdTNEFattAttachTitle                   = $8010;  // /* PR_ATTACH_FILENAME */
  IdTNEFattAttachMetaFile                = $8011;  // /* PR_ATTACH_RENDERING */
  IdTNEFattAttachCreateDate              = $8012;  // /* PR_CREATION_TIME */
  IdTNEFattAttachModifyDate              = $8013;  // /* PR_LAST_MODIFICATION_TIME */
  IdTNEFattDateModified                  = $8020;  // /* PR_LAST_MODIFICATION_TIME */
  IdTNEFattAttachTransportFilename       = $9001;  // /* PR_ATTACH_TRANSPORT_NAME */
  IdTNEFattAttachRenddata                = $9002;  //
  IdTNEFattMAPIProps                     = $9003;  //
  IdTNEFattRecipTable                    = $9004;  // /* PR_MESSAGE_RECIPIENTS */
  IdTNEFattAttachment                    = $9005;  //
  IdTNEFattTnefVersion                   = $9006;  //
  IdTNEFattOemCodepage                   = $9007;  //
  IdTNEFattOriginalMessageClass          = $0006;  // /* PR_ORIG_MESSAGE_CLASS */

  IdTNEFattOwner                         = $0000;  // /* PR_RCVD_REPRESENTING_xxx  or PR_SENT_REPRESENTING_xxx */
  IdTNEFattSentFor                       = $0001;  // /* PR_SENT_REPRESENTING_xxx */
  IdTNEFattDelegate                      = $0002;  // /* PR_RCVD_REPRESENTING_xxx */
  IdTNEFattDateStart                     = $0006;  // /* PR_DATE_START */
  IdTNEFattDateEnd                       = $0007;  // /* PR_DATE_END */
  IdTNEFattAidOwner                      = $0008;  // /* PR_OWNER_APPT_ID */
  IdTNEFattRequestRes                    = $0009;  // /* PR_RESPONSE_REQUESTED */

  //Message priorities...
  IdTNEFprioLow		= 3;
  IdTNEFprioNorm	= 2;
  IdTNEFprioHigh	= 1;

  //MAPI property value types...
  IdTNEF_PT_UNSPECIFIED	= 0;	//TODO: * (Reserved for interface use) type doesn't matter to caller */
  IdTNEF_PT_NULL		= 1;	//TODO: * NULL property value */
  IdTNEF_PT_I2			= 2;	//* Signed 16-bit value */
  IdTNEF_PT_LONG		= 3;	//* Signed 32-bit value */
  IdTNEF_PT_R4			= 4;	//* 4-byte floating point */
  IdTNEF_PT_DOUBLE		= 5;	//TODO: * Floating point double */
  IdTNEF_PT_CURRENCY		= 6;	//TODO: * Signed 64-bit int (decimal w/	4 digits right of decimal pt) */
  IdTNEF_PT_APPTIME		= 7;	//TODO: * Application time */
  IdTNEF_PT_ERROR		= 10;	//TODO: * 32-bit error value */
  IdTNEF_PT_BOOLEAN		= 11;	//* 16-bit boolean (non-zero true) */
  IdTNEF_PT_OBJECT		= 13;	//* Embedded object in a property */
  IdTNEF_PT_I8			= 20;   //TODO: * 8-byte signed integer */
  IdTNEF_PT_STRING8		= 30;   //* Null terminated 8-bit character string */
  IdTNEF_PT_UNICODE		= 31;   //* Null terminated Unicode string */
  IdTNEF_PT_SYSTIME		= 64;   //TODO: * FILETIME 64-bit int w/ number of 100ns periods since Jan 1,1601 */
  IdTNEF_PT_CLSID		= 72;	//TODO:* OLE GUID */
  IdTNEF_PT_BINARY		= 258;  //* Uninterpreted (counted byte array) */

  IdTNEF_MV_FLAG		= $1000;			//TODO: * Multi-value flag */


  //MAPI property tags...
  IdTNEF_PR_ALTERNATE_RECIPIENT_ALLOWED				= $0002; //PROP_TAG( PT_BOOLEAN,	0x0002)
  IdTNEF_PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED	= $0023; //PROP_TAG( PT_BOOLEAN,	0x0023)
  IdTNEF_PR_PRIORITY								= $0026; //PROP_TAG( PT_LONG,		0x0026)
  IdTNEF_PR_READ_RECEIPT_REQUESTED					= $0029; //PROP_TAG( PT_BOOLEAN,	0x0029)
  IdTNEF_PR_ORIGINAL_SENSITIVITY					= $002E; //PROP_TAG( PT_LONG,		0x002E)
  IdTNEF_PR_SENSITIVITY								= $0036; //PROP_TAG( PT_LONG,		0x0036)
  IdTNEF_PR_CLIENT_SUBMIT_TIME						= $0039; //PROP_TAG( PT_SYSTIME,	0x0039)
  IdTNEF_PR_SUBJECT_PREFIX							= $003D; //PROP_TAG( PT_TSTRING,	0x003D)
  IdTNEF_PR_MESSAGE_SUBMISSION_ID					= $0047; //PROP_TAG( PT_BINARY,	    0x0047)
  IdTNEF_PR_ORIGINAL_SUBJECT						= $0049; //PROP_TAG( PT_TSTRING,	0x0049)
  IdTNEF_PR_ORIGINAL_AUTHOR_NAME					= $004D; //PROP_TAG( PT_TSTRING,	0x004D)
  IdTNEF_PR_ORIGINAL_SUBMIT_TIME					= $004E; //PROP_TAG( PT_SYSTIME,	0x004E)
  IdTNEF_PR_ORIGINAL_SENDER_NAME					= $005A; //PROP_TAG( PT_TSTRING,	0x005A)
  IdTNEF_PR_ORIGINAL_SENDER_ENTRYID					= $005B; //PROP_TAG( PT_BINARY,	    0x005B)
  IdTNEF_PR_ORIGINAL_SENDER_SEARCH_KEY				= $005C; //PROP_TAG( PT_BINARY,	    0x005C)
  IdTNEF_PR_ORIGINAL_SENT_REPRESENTING_NAME			= $005D; //PROP_TAG( PT_TSTRING,	0x005D)
  IdTNEF_PR_ORIGINAL_SENT_REPRESENTING_ENTRYID		= $005E; //PROP_TAG( PT_BINARY,	    0x005E)
  IdTNEF_PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY	= $005F; //PROP_TAG( PT_BINARY,	    0x005F)
  IdTNEF_PR_ORIGINAL_SENDER_ADDRTYPE				= $0066; //PROP_TAG( PT_TSTRING,	0x0066)
  IdTNEF_PR_ORIGINAL_SENDER_EMAIL_ADDRESS			= $0067; //PROP_TAG( PT_TSTRING,	0x0067)
  IdTNEF_PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE		= $0068; //PROP_TAG( PT_TSTRING,	0x0068)
  IdTNEF_PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS= $0069; //PROP_TAG( PT_TSTRING,	0x0069)
  IdTNEF_PR_CONVERSATION_TOPIC						= $0070; //PROP_TAG( PT_TSTRING,	0x0070)
  IdTNEF_PR_CONVERSATION_INDEX						= $0071; //PROP_TAG( PT_BINARY,	    0x0071)
  IdTNEF_PR_ORIGINAL_DISPLAY_CC						= $0073; //PROP_TAG( PT_TSTRING,	0x0073)
  IdTNEF_PR_ORIGINAL_DISPLAY_TO						= $0074; //PROP_TAG( PT_TSTRING,	0x0074)
  IdTNEF_PR_REPLY_REQUESTED							= $0C17; //PROP_TAG( PT_BOOLEAN,	0x0C17)
  IdTNEF_PR_SENDER_SEARCH_KEY						= $0C1D; //PROP_TAG( PT_BINARY,	    0x0C1D)
  IdTNEF_PR_SENDER_NAME								= $0C1A; //PROP_TAG( PT_TSTRING,	0x0C1A)
  IdTNEF_PR_DELETE_AFTER_SUBMIT						= $0E01; //PROP_TAG( PT_BOOLEAN,	0x0E01)
  IdTNEF_PR_MESSAGE_DELIVERY_TIME					= $0E06; //PROP_TAG( PT_SYSTIME,	0x0E06)
  IdTNEF_PR_SENTMAIL_ENTRYID						= $0E0A; //PROP_TAG( PT_BINARY,	    0x0E0A)
  IdTNEF_PR_NORMALIZED_SUBJECT						= $0E1D; //PROP_TAG( PT_TSTRING,	0x0E1D)
  IdTNEF_PR_RTF_IN_SYNC								= $0E1F; //PROP_TAG( PT_BOOLEAN,	0x0E1F)
  IdTNEF_PR_MAPPING_SIGNATURE						= $0FF8; //PROP_TAG( PT_BINARY,	    0x0FF8)
  IdTNEF_PR_STORE_RECORD_KEY						= $0FFA; //PROP_TAG( PT_BINARY,	    0x0FFA)
  IdTNEF_PR_STORE_ENTRYID							= $0FFB; //PROP_TAG( PT_BINARY,	    0x0FFB)
  IdTNEF_PR_OBJECT_TYPE								= $0FFE; //PROP_TAG( PT_LONG,		0x0FFE)
  IdTNEF_PR_STORE_SUPPORT_MASK						= $340D; //PROP_TAG( PT_LONG,		0x340D)
  IdTNEF_PR_TNEF_CORRELATION_KEY					= $007F; //PROP_TAG(PT_BINARY,		0x007F)
  IdTNEF_PR_BODY									= $1000; //PROP_TAG( PT_TSTRING,	0x1000)
  IdTNEF_PR_RTF_SYNC_BODY_CRC						= $1006; //PROP_TAG( PT_LONG,		0x1006)
  IdTNEF_PR_RTF_SYNC_BODY_COUNT						= $1007; //PROP_TAG( PT_LONG,		0x1007)
  IdTNEF_PR_RTF_SYNC_BODY_TAG						= $1008; //PROP_TAG( PT_TSTRING,	0x1008)
  IdTNEF_PR_RTF_COMPRESSED							= $1009; //PROP_TAG( PT_BINARY,	    0x1009)
  IdTNEF_PR_RTF_SYNC_PREFIX_COUNT					= $1010; //PROP_TAG( PT_LONG,		0x1010)
  IdTNEF_PR_RTF_SYNC_TRAILING_COUNT					= $1011; //PROP_TAG( PT_LONG,		0x1011)
  IdTNEF_PR_ORIGINALLY_INTENDED_RECIP_ENTRYID		= $1012; //PROP_TAG( PT_BINARY,	    0x1012)
{
#define PR_ACKNOWLEDGEMENT_MODE						PROP_TAG( PT_LONG,		0x0001)
//#define PR_ALTERNATE_RECIPIENT_ALLOWED				PROP_TAG( PT_BOOLEAN,	0x0002)
#define PR_AUTHORIZING_USERS						PROP_TAG( PT_BINARY,	0x0003)
#define PR_AUTO_FORWARD_COMMENT						PROP_TAG( PT_TSTRING,	0x0004)
#define PR_AUTO_FORWARD_COMMENT_W					PROP_TAG( PT_UNICODE,	0x0004)
#define PR_AUTO_FORWARD_COMMENT_A					PROP_TAG( PT_STRING8,	0x0004)
#define PR_AUTO_FORWARDED							PROP_TAG( PT_BOOLEAN,	0x0005)
#define PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID		PROP_TAG( PT_BINARY,	0x0006)
#define PR_CONTENT_CORRELATOR						PROP_TAG( PT_BINARY,	0x0007)
#define PR_CONTENT_IDENTIFIER						PROP_TAG( PT_TSTRING,	0x0008)
#define PR_CONTENT_IDENTIFIER_W						PROP_TAG( PT_UNICODE,	0x0008)
#define PR_CONTENT_IDENTIFIER_A						PROP_TAG( PT_STRING8,	0x0008)
#define PR_CONTENT_LENGTH							PROP_TAG( PT_LONG,		0x0009)
#define PR_CONTENT_RETURN_REQUESTED					PROP_TAG( PT_BOOLEAN,	0x000A)



#define PR_CONVERSATION_KEY							PROP_TAG( PT_BINARY,	0x000B)

#define PR_CONVERSION_EITS							PROP_TAG( PT_BINARY,	0x000C)
#define PR_CONVERSION_WITH_LOSS_PROHIBITED			PROP_TAG( PT_BOOLEAN,	0x000D)
#define PR_CONVERTED_EITS							PROP_TAG( PT_BINARY,	0x000E)
#define PR_DEFERRED_DELIVERY_TIME					PROP_TAG( PT_SYSTIME,	0x000F)
#define PR_DELIVER_TIME								PROP_TAG( PT_SYSTIME,	0x0010)
#define PR_DISCARD_REASON							PROP_TAG( PT_LONG,		0x0011)
#define PR_DISCLOSURE_OF_RECIPIENTS					PROP_TAG( PT_BOOLEAN,	0x0012)
#define PR_DL_EXPANSION_HISTORY						PROP_TAG( PT_BINARY,	0x0013)
#define PR_DL_EXPANSION_PROHIBITED					PROP_TAG( PT_BOOLEAN,	0x0014)
#define PR_EXPIRY_TIME								PROP_TAG( PT_SYSTIME,	0x0015)
#define PR_IMPLICIT_CONVERSION_PROHIBITED			PROP_TAG( PT_BOOLEAN,	0x0016)
#define PR_IMPORTANCE								PROP_TAG( PT_LONG,		0x0017)
#define PR_IPM_ID									PROP_TAG( PT_BINARY,	0x0018)
#define PR_LATEST_DELIVERY_TIME						PROP_TAG( PT_SYSTIME,	0x0019)
#define PR_MESSAGE_CLASS							PROP_TAG( PT_TSTRING,	0x001A)
#define PR_MESSAGE_CLASS_W							PROP_TAG( PT_UNICODE,	0x001A)
#define PR_MESSAGE_CLASS_A							PROP_TAG( PT_STRING8,	0x001A)
#define PR_MESSAGE_DELIVERY_ID						PROP_TAG( PT_BINARY,	0x001B)





#define PR_MESSAGE_SECURITY_LABEL					PROP_TAG( PT_BINARY,	0x001E)
#define PR_OBSOLETED_IPMS							PROP_TAG( PT_BINARY,	0x001F)
#define PR_ORIGINALLY_INTENDED_RECIPIENT_NAME		PROP_TAG( PT_BINARY,	0x0020)
#define PR_ORIGINAL_EITS							PROP_TAG( PT_BINARY,	0x0021)
#define PR_ORIGINATOR_CERTIFICATE					PROP_TAG( PT_BINARY,	0x0022)
//#define PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED		PROP_TAG( PT_BOOLEAN,	0x0023)
#define PR_ORIGINATOR_RETURN_ADDRESS				PROP_TAG( PT_BINARY,	0x0024)



#define PR_PARENT_KEY								PROP_TAG( PT_BINARY,	0x0025)
//#define PR_PRIORITY									PROP_TAG( PT_LONG,		0x0026)



#define PR_ORIGIN_CHECK								PROP_TAG( PT_BINARY,	0x0027)
#define PR_PROOF_OF_SUBMISSION_REQUESTED			PROP_TAG( PT_BOOLEAN,	0x0028)
#define PR_READ_RECEIPT_REQUESTED					PROP_TAG( PT_BOOLEAN,	0x0029)
#define PR_RECEIPT_TIME								PROP_TAG( PT_SYSTIME,	0x002A)
#define PR_RECIPIENT_REASSIGNMENT_PROHIBITED		PROP_TAG( PT_BOOLEAN,	0x002B)
#define PR_REDIRECTION_HISTORY						PROP_TAG( PT_BINARY,	0x002C)
#define PR_RELATED_IPMS								PROP_TAG( PT_BINARY,	0x002D)
//#define PR_ORIGINAL_SENSITIVITY						PROP_TAG( PT_LONG,		0x002E)
#define	PR_LANGUAGES								PROP_TAG( PT_TSTRING,	0x002F)
#define	PR_LANGUAGES_W								PROP_TAG( PT_UNICODE,	0x002F)
#define	PR_LANGUAGES_A								PROP_TAG( PT_STRING8,	0x002F)
#define PR_REPLY_TIME								PROP_TAG( PT_SYSTIME,	0x0030)
#define PR_REPORT_TAG								PROP_TAG( PT_BINARY,	0x0031)
#define PR_REPORT_TIME								PROP_TAG( PT_SYSTIME,	0x0032)
#define PR_RETURNED_IPM								PROP_TAG( PT_BOOLEAN,	0x0033)
#define PR_SECURITY									PROP_TAG( PT_LONG,		0x0034)
#define	PR_INCOMPLETE_COPY							PROP_TAG( PT_BOOLEAN,	0x0035)
#define PR_SENSITIVITY								PROP_TAG( PT_LONG,		0x0036)
#define PR_SUBJECT									PROP_TAG( PT_TSTRING,	0x0037)
#define PR_SUBJECT_W								PROP_TAG( PT_UNICODE,	0x0037)
#define PR_SUBJECT_A								PROP_TAG( PT_STRING8,	0x0037)
#define PR_SUBJECT_IPM								PROP_TAG( PT_BINARY,	0x0038)
//#define PR_CLIENT_SUBMIT_TIME						PROP_TAG( PT_SYSTIME,	0x0039)
#define PR_REPORT_NAME								PROP_TAG( PT_TSTRING,	0x003A)
#define PR_REPORT_NAME_W							PROP_TAG( PT_UNICODE,	0x003A)
#define PR_REPORT_NAME_A							PROP_TAG( PT_STRING8,	0x003A)
#define	PR_SENT_REPRESENTING_SEARCH_KEY				PROP_TAG( PT_BINARY,	0x003B)
#define PR_X400_CONTENT_TYPE						PROP_TAG( PT_BINARY,	0x003C)
//#define PR_SUBJECT_PREFIX							PROP_TAG( PT_TSTRING,	0x003D)
//#define PR_SUBJECT_PREFIX_W	 						PROP_TAG( PT_UNICODE,	0x003D)
//#define PR_SUBJECT_PREFIX_A	 						PROP_TAG( PT_STRING8,	0x003D)
#define PR_NON_RECEIPT_REASON						PROP_TAG( PT_LONG,		0x003E)
#define PR_RECEIVED_BY_ENTRYID						PROP_TAG( PT_BINARY,	0x003F)
#define PR_RECEIVED_BY_NAME							PROP_TAG( PT_TSTRING,	0x0040)
#define PR_RECEIVED_BY_NAME_W						PROP_TAG( PT_UNICODE,	0x0040)
#define PR_RECEIVED_BY_NAME_A						PROP_TAG( PT_STRING8,	0x0040)
#define	PR_SENT_REPRESENTING_ENTRYID				PROP_TAG( PT_BINARY,	0x0041)
#define PR_SENT_REPRESENTING_NAME					PROP_TAG( PT_TSTRING,	0x0042)
#define PR_SENT_REPRESENTING_NAME_W					PROP_TAG( PT_UNICODE,	0x0042)
#define PR_SENT_REPRESENTING_NAME_A					PROP_TAG( PT_STRING8,	0x0042)
#define PR_RCVD_REPRESENTING_ENTRYID				PROP_TAG( PT_BINARY,	0x0043)
#define PR_RCVD_REPRESENTING_NAME					PROP_TAG( PT_TSTRING,	0x0044)
#define PR_RCVD_REPRESENTING_NAME_W					PROP_TAG( PT_UNICODE,	0x0044)
#define PR_RCVD_REPRESENTING_NAME_A					PROP_TAG( PT_STRING8,	0x0044)
#define PR_REPORT_ENTRYID							PROP_TAG( PT_BINARY,	0x0045)
#define PR_READ_RECEIPT_ENTRYID						PROP_TAG( PT_BINARY,	0x0046)
#define PR_MESSAGE_SUBMISSION_ID					PROP_TAG( PT_BINARY,	0x0047)
#define PR_PROVIDER_SUBMIT_TIME						PROP_TAG( PT_SYSTIME,	0x0048)
//#define	PR_ORIGINAL_SUBJECT							PROP_TAG( PT_TSTRING,	0x0049)
#define	PR_ORIGINAL_SUBJECT_W						PROP_TAG( PT_UNICODE,	0x0049)
#define	PR_ORIGINAL_SUBJECT_A						PROP_TAG( PT_STRING8,	0x0049)
#define PR_DISC_VAL									PROP_TAG( PT_BOOLEAN,	0x004A)
#define PR_ORIG_MESSAGE_CLASS						PROP_TAG( PT_TSTRING,	0x004B)
#define PR_ORIG_MESSAGE_CLASS_W						PROP_TAG( PT_UNICODE,	0x004B)
#define PR_ORIG_MESSAGE_CLASS_A						PROP_TAG( PT_STRING8,	0x004B)
#define PR_ORIGINAL_AUTHOR_ENTRYID					PROP_TAG( PT_BINARY,	0x004C)
#define PR_ORIGINAL_AUTHOR_NAME						PROP_TAG( PT_TSTRING,	0x004D)
#define PR_ORIGINAL_AUTHOR_NAME_W					PROP_TAG( PT_UNICODE,	0x004D)
#define PR_ORIGINAL_AUTHOR_NAME_A					PROP_TAG( PT_STRING8,	0x004D)
//#define PR_ORIGINAL_SUBMIT_TIME						PROP_TAG( PT_SYSTIME,	0x004E)
#define PR_REPLY_RECIPIENT_ENTRIES					PROP_TAG( PT_BINARY,	0x004F)
#define PR_REPLY_RECIPIENT_NAMES					PROP_TAG( PT_TSTRING,	0x0050)
#define PR_REPLY_RECIPIENT_NAMES_W					PROP_TAG( PT_UNICODE,	0x0050)
#define PR_REPLY_RECIPIENT_NAMES_A					PROP_TAG( PT_STRING8,	0x0050)

#define PR_RECEIVED_BY_SEARCH_KEY					PROP_TAG( PT_BINARY,	0x0051)
#define PR_RCVD_REPRESENTING_SEARCH_KEY				PROP_TAG( PT_BINARY,	0x0052)
#define PR_READ_RECEIPT_SEARCH_KEY					PROP_TAG( PT_BINARY,	0x0053)
#define PR_REPORT_SEARCH_KEY						PROP_TAG( PT_BINARY,	0x0054)
#define	PR_ORIGINAL_DELIVERY_TIME					PROP_TAG( PT_SYSTIME,	0x0055)
#define PR_ORIGINAL_AUTHOR_SEARCH_KEY				PROP_TAG( PT_BINARY,	0x0056)

#define	PR_MESSAGE_TO_ME							PROP_TAG( PT_BOOLEAN,	0x0057)
#define	PR_MESSAGE_CC_ME							PROP_TAG( PT_BOOLEAN,	0x0058)
#define	PR_MESSAGE_RECIP_ME							PROP_TAG( PT_BOOLEAN,	0x0059)

//#define PR_ORIGINAL_SENDER_NAME						PROP_TAG( PT_TSTRING,	0x005A)
//#define PR_ORIGINAL_SENDER_NAME_W					PROP_TAG( PT_UNICODE,	0x005A)
//#define PR_ORIGINAL_SENDER_NAME_A					PROP_TAG( PT_STRING8,	0x005A)
//#define	PR_ORIGINAL_SENDER_ENTRYID					PROP_TAG( PT_BINARY,	0x005B)
//#define	PR_ORIGINAL_SENDER_SEARCH_KEY				PROP_TAG( PT_BINARY,	0x005C)
//#define PR_ORIGINAL_SENT_REPRESENTING_NAME			PROP_TAG( PT_TSTRING,	0x005D)
//#define PR_ORIGINAL_SENT_REPRESENTING_NAME_W		PROP_TAG( PT_UNICODE,	0x005D)
//#define PR_ORIGINAL_SENT_REPRESENTING_NAME_A		PROP_TAG( PT_STRING8,	0x005D)
//#define	PR_ORIGINAL_SENT_REPRESENTING_ENTRYID		PROP_TAG( PT_BINARY,	0x005E)
//#define	PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY	PROP_TAG( PT_BINARY,	0x005F)

#define PR_START_DATE								PROP_TAG( PT_SYSTIME,	0x0060)
#define PR_END_DATE									PROP_TAG( PT_SYSTIME,	0x0061)
#define PR_OWNER_APPT_ID							PROP_TAG( PT_LONG,		0x0062)
#define PR_RESPONSE_REQUESTED						PROP_TAG( PT_BOOLEAN,	0x0063)

#define PR_SENT_REPRESENTING_ADDRTYPE				PROP_TAG( PT_TSTRING,	0x0064)
#define PR_SENT_REPRESENTING_ADDRTYPE_W				PROP_TAG( PT_UNICODE,	0x0064)
#define PR_SENT_REPRESENTING_ADDRTYPE_A				PROP_TAG( PT_STRING8,	0x0064)
#define PR_SENT_REPRESENTING_EMAIL_ADDRESS			PROP_TAG( PT_TSTRING,	0x0065)
#define PR_SENT_REPRESENTING_EMAIL_ADDRESS_W		PROP_TAG( PT_UNICODE,	0x0065)
#define PR_SENT_REPRESENTING_EMAIL_ADDRESS_A		PROP_TAG( PT_STRING8,	0x0065)

//#define PR_ORIGINAL_SENDER_ADDRTYPE					PROP_TAG( PT_TSTRING,	0x0066)
//#define PR_ORIGINAL_SENDER_ADDRTYPE_W				PROP_TAG( PT_UNICODE,	0x0066)
//#define PR_ORIGINAL_SENDER_ADDRTYPE_A				PROP_TAG( PT_STRING8,	0x0066)
//#define PR_ORIGINAL_SENDER_EMAIL_ADDRESS			PROP_TAG( PT_TSTRING,	0x0067)
//#define PR_ORIGINAL_SENDER_EMAIL_ADDRESS_W			PROP_TAG( PT_UNICODE,	0x0067)
//#define PR_ORIGINAL_SENDER_EMAIL_ADDRESS_A			PROP_TAG( PT_STRING8,	0x0067)

//#define PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE		PROP_TAG( PT_TSTRING,	0x0068)
//#define PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE_W	PROP_TAG( PT_UNICODE,	0x0068)
//#define PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE_A	PROP_TAG( PT_STRING8,	0x0068)
//#define PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS	PROP_TAG( PT_TSTRING,	0x0069)
//#define PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS_W	PROP_TAG( PT_UNICODE,	0x0069)
//#define PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS_A	PROP_TAG( PT_STRING8,	0x0069)

//#define	PR_CONVERSATION_TOPIC						PROP_TAG( PT_TSTRING,	0x0070)
#define	PR_CONVERSATION_TOPIC_W						PROP_TAG( PT_UNICODE,	0x0070)
#define	PR_CONVERSATION_TOPIC_A						PROP_TAG( PT_STRING8,	0x0070)
//#define	PR_CONVERSATION_INDEX						PROP_TAG( PT_BINARY,	0x0071)

#define PR_ORIGINAL_DISPLAY_BCC						PROP_TAG( PT_TSTRING,	0x0072)
#define PR_ORIGINAL_DISPLAY_BCC_W					PROP_TAG( PT_UNICODE,	0x0072)
#define PR_ORIGINAL_DISPLAY_BCC_A					PROP_TAG( PT_STRING8,	0x0072)
//#define PR_ORIGINAL_DISPLAY_CC						PROP_TAG( PT_TSTRING,	0x0073)
//#define PR_ORIGINAL_DISPLAY_CC_W					PROP_TAG( PT_UNICODE,	0x0073)
//#define PR_ORIGINAL_DISPLAY_CC_A					PROP_TAG( PT_STRING8,	0x0073)
//#define PR_ORIGINAL_DISPLAY_TO						PROP_TAG( PT_TSTRING,	0x0074)
//#define PR_ORIGINAL_DISPLAY_TO_W					PROP_TAG( PT_UNICODE,	0x0074)
//#define PR_ORIGINAL_DISPLAY_TO_A					PROP_TAG( PT_STRING8,	0x0074)

#define PR_RECEIVED_BY_ADDRTYPE						PROP_TAG( PT_TSTRING,	0x0075)
#define PR_RECEIVED_BY_ADDRTYPE_W					PROP_TAG( PT_UNICODE,	0x0075)
#define PR_RECEIVED_BY_ADDRTYPE_A					PROP_TAG( PT_STRING8,	0x0075)
#define	PR_RECEIVED_BY_EMAIL_ADDRESS				PROP_TAG( PT_TSTRING,	0x0076)
#define	PR_RECEIVED_BY_EMAIL_ADDRESS_W				PROP_TAG( PT_UNICODE,	0x0076)
#define	PR_RECEIVED_BY_EMAIL_ADDRESS_A				PROP_TAG( PT_STRING8,	0x0076)

#define PR_RCVD_REPRESENTING_ADDRTYPE				PROP_TAG( PT_TSTRING,	0x0077)
#define PR_RCVD_REPRESENTING_ADDRTYPE_W				PROP_TAG( PT_UNICODE,	0x0077)
#define PR_RCVD_REPRESENTING_ADDRTYPE_A				PROP_TAG( PT_STRING8,	0x0077)
#define PR_RCVD_REPRESENTING_EMAIL_ADDRESS			PROP_TAG( PT_TSTRING,	0x0078)
#define PR_RCVD_REPRESENTING_EMAIL_ADDRESS_W		PROP_TAG( PT_UNICODE,	0x0078)
#define PR_RCVD_REPRESENTING_EMAIL_ADDRESS_A		PROP_TAG( PT_STRING8,	0x0078)

#define PR_ORIGINAL_AUTHOR_ADDRTYPE					PROP_TAG( PT_TSTRING,	0x0079)
#define PR_ORIGINAL_AUTHOR_ADDRTYPE_W				PROP_TAG( PT_UNICODE,	0x0079)
#define PR_ORIGINAL_AUTHOR_ADDRTYPE_A				PROP_TAG( PT_STRING8,	0x0079)
#define PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS			PROP_TAG( PT_TSTRING,	0x007A)
#define PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS_W			PROP_TAG( PT_UNICODE,	0x007A)
#define PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS_A			PROP_TAG( PT_STRING8,	0x007A)

#define PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE		PROP_TAG( PT_TSTRING,	0x007B)
#define PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE_W		PROP_TAG( PT_UNICODE,	0x007B)
#define PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE_A		PROP_TAG( PT_STRING8,	0x007B)
#define PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS	PROP_TAG( PT_TSTRING,	0x007C)
#define PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS_W	PROP_TAG( PT_UNICODE,	0x007C)
#define PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS_A	PROP_TAG( PT_STRING8,	0x007C)

#define PR_TRANSPORT_MESSAGE_HEADERS				PROP_TAG(PT_TSTRING,	0x007D)
#define PR_TRANSPORT_MESSAGE_HEADERS_W				PROP_TAG(PT_UNICODE,	0x007D)
#define PR_TRANSPORT_MESSAGE_HEADERS_A				PROP_TAG(PT_STRING8,	0x007D)

#define PR_DELEGATION								PROP_TAG(PT_BINARY,		0x007E)

#define PR_TNEF_CORRELATION_KEY						PROP_TAG(PT_BINARY,		0x007F)



/*
 *	Message content properties
 */

//#define PR_BODY										PROP_TAG( PT_TSTRING,	0x1000)
//#define PR_BODY_W									PROP_TAG( PT_UNICODE,	0x1000)
//#define PR_BODY_A									PROP_TAG( PT_STRING8,	0x1000)
#define PR_REPORT_TEXT								PROP_TAG( PT_TSTRING,	0x1001)
#define PR_REPORT_TEXT_W							PROP_TAG( PT_UNICODE,	0x1001)
#define PR_REPORT_TEXT_A							PROP_TAG( PT_STRING8,	0x1001)
#define PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY		PROP_TAG( PT_BINARY,	0x1002)
#define PR_REPORTING_DL_NAME						PROP_TAG( PT_BINARY,	0x1003)
#define PR_REPORTING_MTA_CERTIFICATE				PROP_TAG( PT_BINARY,	0x1004)

/*  Removed PR_REPORT_ORIGIN_AUTHENTICATION_CHECK with DCR 3865, use PR_ORIGIN_CHECK */

#define PR_RTF_SYNC_BODY_CRC						PROP_TAG( PT_LONG,		0x1006)
#define PR_RTF_SYNC_BODY_COUNT						PROP_TAG( PT_LONG,		0x1007)
#define PR_RTF_SYNC_BODY_TAG						PROP_TAG( PT_TSTRING,	0x1008)
#define PR_RTF_SYNC_BODY_TAG_W						PROP_TAG( PT_UNICODE,	0x1008)
#define PR_RTF_SYNC_BODY_TAG_A						PROP_TAG( PT_STRING8,	0x1008)
#define PR_RTF_COMPRESSED							PROP_TAG( PT_BINARY,	0x1009)
#define PR_RTF_SYNC_PREFIX_COUNT					PROP_TAG( PT_LONG,		0x1010)
#define PR_RTF_SYNC_TRAILING_COUNT					PROP_TAG( PT_LONG,		0x1011)
#define PR_ORIGINALLY_INTENDED_RECIP_ENTRYID		PROP_TAG( PT_BINARY,	0x1012)

/*
 *  Reserved 0x1100-0x1200
 */


/*
 *	Message recipient properties
 */

#define PR_CONTENT_INTEGRITY_CHECK					PROP_TAG( PT_BINARY,	0x0C00)
#define PR_EXPLICIT_CONVERSION						PROP_TAG( PT_LONG,		0x0C01)
#define PR_IPM_RETURN_REQUESTED						PROP_TAG( PT_BOOLEAN,	0x0C02)
#define PR_MESSAGE_TOKEN							PROP_TAG( PT_BINARY,	0x0C03)
#define PR_NDR_REASON_CODE							PROP_TAG( PT_LONG,		0x0C04)
#define PR_NDR_DIAG_CODE							PROP_TAG( PT_LONG,		0x0C05)
#define PR_NON_RECEIPT_NOTIFICATION_REQUESTED		PROP_TAG( PT_BOOLEAN,	0x0C06)
#define PR_DELIVERY_POINT							PROP_TAG( PT_LONG,		0x0C07)

#define PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED	PROP_TAG( PT_BOOLEAN,	0x0C08)
#define PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT	PROP_TAG( PT_BINARY,	0x0C09)
#define PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY	PROP_TAG( PT_BOOLEAN,	0x0C0A)
#define PR_PHYSICAL_DELIVERY_MODE					PROP_TAG( PT_LONG,		0x0C0B)
#define PR_PHYSICAL_DELIVERY_REPORT_REQUEST			PROP_TAG( PT_LONG,		0x0C0C)
#define PR_PHYSICAL_FORWARDING_ADDRESS				PROP_TAG( PT_BINARY,	0x0C0D)
#define PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED	PROP_TAG( PT_BOOLEAN,	0x0C0E)
#define PR_PHYSICAL_FORWARDING_PROHIBITED			PROP_TAG( PT_BOOLEAN,	0x0C0F)
#define PR_PHYSICAL_RENDITION_ATTRIBUTES			PROP_TAG( PT_BINARY,	0x0C10)
#define PR_PROOF_OF_DELIVERY						PROP_TAG( PT_BINARY,	0x0C11)
#define PR_PROOF_OF_DELIVERY_REQUESTED				PROP_TAG( PT_BOOLEAN,	0x0C12)
#define PR_RECIPIENT_CERTIFICATE					PROP_TAG( PT_BINARY,	0x0C13)
#define PR_RECIPIENT_NUMBER_FOR_ADVICE				PROP_TAG( PT_TSTRING,	0x0C14)
#define PR_RECIPIENT_NUMBER_FOR_ADVICE_W			PROP_TAG( PT_UNICODE,	0x0C14)
#define PR_RECIPIENT_NUMBER_FOR_ADVICE_A			PROP_TAG( PT_STRING8,	0x0C14)
#define PR_RECIPIENT_TYPE							PROP_TAG( PT_LONG,		0x0C15)
#define PR_REGISTERED_MAIL_TYPE						PROP_TAG( PT_LONG,		0x0C16)
//#define PR_REPLY_REQUESTED							PROP_TAG( PT_BOOLEAN,	0x0C17)
#define PR_REQUESTED_DELIVERY_METHOD				PROP_TAG( PT_LONG,		0x0C18)
#define PR_SENDER_ENTRYID							PROP_TAG( PT_BINARY,	0x0C19)
//#define PR_SENDER_NAME								PROP_TAG( PT_TSTRING,	0x0C1A)
//#define PR_SENDER_NAME_W							PROP_TAG( PT_UNICODE,	0x0C1A)
//#define PR_SENDER_NAME_A							PROP_TAG( PT_STRING8,	0x0C1A)
#define PR_SUPPLEMENTARY_INFO						PROP_TAG( PT_TSTRING,	0x0C1B)
#define PR_SUPPLEMENTARY_INFO_W						PROP_TAG( PT_UNICODE,	0x0C1B)
#define PR_SUPPLEMENTARY_INFO_A						PROP_TAG( PT_STRING8,	0x0C1B)
#define PR_TYPE_OF_MTS_USER							PROP_TAG( PT_LONG,		0x0C1C)
//#define PR_SENDER_SEARCH_KEY						PROP_TAG( PT_BINARY,	0x0C1D)
#define PR_SENDER_ADDRTYPE							PROP_TAG( PT_TSTRING,	0x0C1E)
#define PR_SENDER_ADDRTYPE_W						PROP_TAG( PT_UNICODE,	0x0C1E)
#define PR_SENDER_ADDRTYPE_A						PROP_TAG( PT_STRING8,	0x0C1E)
#define PR_SENDER_EMAIL_ADDRESS						PROP_TAG( PT_TSTRING,	0x0C1F)
#define PR_SENDER_EMAIL_ADDRESS_W					PROP_TAG( PT_UNICODE,	0x0C1F)
#define PR_SENDER_EMAIL_ADDRESS_A					PROP_TAG( PT_STRING8,	0x0C1F)

/*
 *	Message non-transmittable properties
 */

/*
 * The two tags, PR_MESSAGE_RECIPIENTS and PR_MESSAGE_ATTACHMENTS,
 * are to be used in the exclude list passed to
 * IMessage::CopyTo when the caller wants either the recipients or attachments
 * of the message to not get copied.  It is also used in the ProblemArray
 * return from IMessage::CopyTo when an error is encountered copying them
 */

#define PR_CURRENT_VERSION							PROP_TAG( PT_I8,		0x0E00)
//#define PR_DELETE_AFTER_SUBMIT						PROP_TAG( PT_BOOLEAN,	0x0E01)
#define PR_DISPLAY_BCC								PROP_TAG( PT_TSTRING,	0x0E02)
#define PR_DISPLAY_BCC_W							PROP_TAG( PT_UNICODE,	0x0E02)
#define PR_DISPLAY_BCC_A							PROP_TAG( PT_STRING8,	0x0E02)
#define PR_DISPLAY_CC								PROP_TAG( PT_TSTRING,	0x0E03)
#define PR_DISPLAY_CC_W								PROP_TAG( PT_UNICODE,	0x0E03)
#define PR_DISPLAY_CC_A								PROP_TAG( PT_STRING8,	0x0E03)
#define PR_DISPLAY_TO								PROP_TAG( PT_TSTRING,	0x0E04)
#define PR_DISPLAY_TO_W								PROP_TAG( PT_UNICODE,	0x0E04)
#define PR_DISPLAY_TO_A								PROP_TAG( PT_STRING8,	0x0E04)
#define PR_PARENT_DISPLAY							PROP_TAG( PT_TSTRING,	0x0E05)
#define PR_PARENT_DISPLAY_W							PROP_TAG( PT_UNICODE,	0x0E05)
#define PR_PARENT_DISPLAY_A							PROP_TAG( PT_STRING8,	0x0E05)
#define PR_MESSAGE_DELIVERY_TIME					PROP_TAG( PT_SYSTIME,	0x0E06)
#define PR_MESSAGE_FLAGS							PROP_TAG( PT_LONG,		0x0E07)
#define PR_MESSAGE_SIZE								PROP_TAG( PT_LONG,		0x0E08)
#define PR_PARENT_ENTRYID							PROP_TAG( PT_BINARY,	0x0E09)
#define PR_SENTMAIL_ENTRYID							PROP_TAG( PT_BINARY,	0x0E0A)
#define PR_CORRELATE								PROP_TAG( PT_BOOLEAN,	0x0E0C)
#define PR_CORRELATE_MTSID							PROP_TAG( PT_BINARY,	0x0E0D)
#define PR_DISCRETE_VALUES							PROP_TAG( PT_BOOLEAN,	0x0E0E)
#define PR_RESPONSIBILITY							PROP_TAG( PT_BOOLEAN,	0x0E0F)
#define	PR_SPOOLER_STATUS							PROP_TAG( PT_LONG,		0x0E10)
#define	PR_TRANSPORT_STATUS							PROP_TAG( PT_LONG,		0x0E11)
#define PR_MESSAGE_RECIPIENTS						PROP_TAG( PT_OBJECT,	0x0E12)
#define PR_MESSAGE_ATTACHMENTS						PROP_TAG( PT_OBJECT,	0x0E13)
#define PR_SUBMIT_FLAGS								PROP_TAG( PT_LONG,		0x0E14)
#define PR_RECIPIENT_STATUS							PROP_TAG( PT_LONG,		0x0E15)
#define	PR_TRANSPORT_KEY							PROP_TAG( PT_LONG,		0x0E16)
#define PR_MSG_STATUS								PROP_TAG( PT_LONG,		0x0E17)
#define	PR_MESSAGE_DOWNLOAD_TIME					PROP_TAG( PT_LONG,		0x0E18)
#define PR_CREATION_VERSION							PROP_TAG( PT_I8,		0x0E19)
#define PR_MODIFY_VERSION							PROP_TAG( PT_I8,		0x0E1A)
#define PR_HASATTACH								PROP_TAG( PT_BOOLEAN,	0x0E1B)
#define PR_BODY_CRC									PROP_TAG( PT_LONG,      0x0E1C)
//#define PR_NORMALIZED_SUBJECT						PROP_TAG( PT_TSTRING,	0x0E1D)
//#define PR_NORMALIZED_SUBJECT_W						PROP_TAG( PT_UNICODE,	0x0E1D)
//#define PR_NORMALIZED_SUBJECT_A						PROP_TAG( PT_STRING8,	0x0E1D)
#define PR_RTF_IN_SYNC								PROP_TAG( PT_BOOLEAN,	0x0E1F)
#define PR_ATTACH_SIZE								PROP_TAG( PT_LONG,		0x0E20)
#define PR_ATTACH_NUM								PROP_TAG( PT_LONG,		0x0E21)
#define PR_PREPROCESS								PROP_TAG( PT_BOOLEAN,	0x0E22)

/* PR_ORIGINAL_DISPLAY_TO, _CC, and _BCC moved to transmittible range 03/09/95 */

#define PR_ORIGINATING_MTA_CERTIFICATE				PROP_TAG( PT_BINARY,	0x0E25)
#define PR_PROOF_OF_SUBMISSION						PROP_TAG( PT_BINARY,	0x0E26)


/*
 * The range of non-message and non-recipient property IDs (0x3000 - 0x3FFF) is
 * further broken down into ranges to make assigning new property IDs easier.
 *
 *	From	To		Kind of property
 *	--------------------------------
 *	3000	32FF	MAPI_defined common property
 *	3200	33FF	MAPI_defined form property
 *	3400	35FF	MAPI_defined message store property
 *	3600	36FF	MAPI_defined Folder or AB Container property
 *	3700	38FF	MAPI_defined attachment property
 *	3900	39FF	MAPI_defined address book property
 *	3A00	3BFF	MAPI_defined mailuser property
 *	3C00	3CFF	MAPI_defined DistList property
 *	3D00	3DFF	MAPI_defined Profile Section property
 *	3E00	3EFF	MAPI_defined Status property
 *	3F00	3FFF	MAPI_defined display table property
 */

/*
 *	Properties common to numerous MAPI objects.
 *
 *	Those properties that can appear on messages are in the
 *	non-transmittable range for messages. They start at the high
 *	end of that range and work down.
 *
 *	Properties that never appear on messages are defined in the common
 *	property range (see above).
 */

/*
 * properties that are common to multiple objects (including message objects)
 * -- these ids are in the non-transmittable range
 */

#define PR_ENTRYID									PROP_TAG( PT_BINARY,	0x0FFF)
#define PR_OBJECT_TYPE								PROP_TAG( PT_LONG,		0x0FFE)
#define PR_ICON										PROP_TAG( PT_BINARY,	0x0FFD)
#define PR_MINI_ICON								PROP_TAG( PT_BINARY,	0x0FFC)
#define PR_STORE_ENTRYID							PROP_TAG( PT_BINARY,	0x0FFB)
#define PR_STORE_RECORD_KEY							PROP_TAG( PT_BINARY,	0x0FFA)
#define PR_RECORD_KEY								PROP_TAG( PT_BINARY,	0x0FF9)
#define PR_MAPPING_SIGNATURE						PROP_TAG( PT_BINARY,	0x0FF8)
#define PR_ACCESS_LEVEL								PROP_TAG( PT_LONG,		0x0FF7)
#define PR_INSTANCE_KEY								PROP_TAG( PT_BINARY,	0x0FF6)
#define PR_ROW_TYPE									PROP_TAG( PT_LONG,		0x0FF5)
#define PR_ACCESS									PROP_TAG( PT_LONG,		0x0FF4)

/*
 * properties that are common to multiple objects (usually not including message objects)
 * -- these ids are in the transmittable range
 */

#define PR_ROWID									PROP_TAG( PT_LONG,		0x3000)
#define PR_DISPLAY_NAME								PROP_TAG( PT_TSTRING,	0x3001)
#define PR_DISPLAY_NAME_W							PROP_TAG( PT_UNICODE,	0x3001)
#define PR_DISPLAY_NAME_A							PROP_TAG( PT_STRING8,	0x3001)
#define PR_ADDRTYPE									PROP_TAG( PT_TSTRING,	0x3002)
#define PR_ADDRTYPE_W								PROP_TAG( PT_UNICODE,	0x3002)
#define PR_ADDRTYPE_A								PROP_TAG( PT_STRING8,	0x3002)
#define PR_EMAIL_ADDRESS							PROP_TAG( PT_TSTRING,	0x3003)
#define PR_EMAIL_ADDRESS_W							PROP_TAG( PT_UNICODE,	0x3003)
#define PR_EMAIL_ADDRESS_A							PROP_TAG( PT_STRING8,	0x3003)
#define PR_COMMENT									PROP_TAG( PT_TSTRING,	0x3004)
#define PR_COMMENT_W								PROP_TAG( PT_UNICODE,	0x3004)
#define PR_COMMENT_A								PROP_TAG( PT_STRING8,	0x3004)
#define PR_DEPTH									PROP_TAG( PT_LONG,		0x3005)
#define PR_PROVIDER_DISPLAY							PROP_TAG( PT_TSTRING,	0x3006)
#define PR_PROVIDER_DISPLAY_W						PROP_TAG( PT_UNICODE,	0x3006)
#define PR_PROVIDER_DISPLAY_A						PROP_TAG( PT_STRING8,	0x3006)
#define PR_CREATION_TIME							PROP_TAG( PT_SYSTIME,	0x3007)
#define PR_LAST_MODIFICATION_TIME					PROP_TAG( PT_SYSTIME,	0x3008)
#define PR_RESOURCE_FLAGS							PROP_TAG( PT_LONG,		0x3009)
#define PR_PROVIDER_DLL_NAME						PROP_TAG( PT_TSTRING,	0x300A)
#define PR_PROVIDER_DLL_NAME_W						PROP_TAG( PT_UNICODE,	0x300A)
#define PR_PROVIDER_DLL_NAME_A						PROP_TAG( PT_STRING8,	0x300A)
#define PR_SEARCH_KEY								PROP_TAG( PT_BINARY,	0x300B)
#define PR_PROVIDER_UID								PROP_TAG( PT_BINARY,	0x300C)
#define PR_PROVIDER_ORDINAL							PROP_TAG( PT_LONG,		0x300D)

/*
 *  MAPI Form properties
 */
#define PR_FORM_VERSION								PROP_TAG(PT_TSTRING,	0x3301)
#define PR_FORM_VERSION_W							PROP_TAG(PT_UNICODE,	0x3301)
#define PR_FORM_VERSION_A							PROP_TAG(PT_STRING8,	0x3301)
#define PR_FORM_CLSID								PROP_TAG(PT_CLSID,		0x3302)
#define PR_FORM_CONTACT_NAME						PROP_TAG(PT_TSTRING,	0x3303)
#define PR_FORM_CONTACT_NAME_W						PROP_TAG(PT_UNICODE,	0x3303)
#define PR_FORM_CONTACT_NAME_A						PROP_TAG(PT_STRING8,	0x3303)
#define PR_FORM_CATEGORY							PROP_TAG(PT_TSTRING,	0x3304)
#define PR_FORM_CATEGORY_W							PROP_TAG(PT_UNICODE,	0x3304)
#define PR_FORM_CATEGORY_A							PROP_TAG(PT_STRING8,	0x3304)
#define PR_FORM_CATEGORY_SUB						PROP_TAG(PT_TSTRING,	0x3305)
#define PR_FORM_CATEGORY_SUB_W						PROP_TAG(PT_UNICODE,	0x3305)
#define PR_FORM_CATEGORY_SUB_A						PROP_TAG(PT_STRING8,	0x3305)
#define PR_FORM_HOST_MAP							PROP_TAG(PT_MV_LONG,	0x3306)
#define PR_FORM_HIDDEN								PROP_TAG(PT_BOOLEAN,	0x3307)
#define PR_FORM_DESIGNER_NAME						PROP_TAG(PT_TSTRING,	0x3308)
#define PR_FORM_DESIGNER_NAME_W						PROP_TAG(PT_UNICODE,	0x3308)
#define PR_FORM_DESIGNER_NAME_A						PROP_TAG(PT_STRING8,	0x3308)
#define PR_FORM_DESIGNER_GUID						PROP_TAG(PT_CLSID,		0x3309)
#define PR_FORM_MESSAGE_BEHAVIOR					PROP_TAG(PT_LONG,		0x330A)

/*
 *	Message store properties
 */

#define PR_DEFAULT_STORE							PROP_TAG( PT_BOOLEAN,	0x3400)
#define PR_STORE_SUPPORT_MASK						PROP_TAG( PT_LONG,		0x340D)
#define PR_STORE_STATE								PROP_TAG( PT_LONG,		0x340E)

#define PR_IPM_SUBTREE_SEARCH_KEY					PROP_TAG( PT_BINARY,	0x3410)
#define PR_IPM_OUTBOX_SEARCH_KEY					PROP_TAG( PT_BINARY,	0x3411)
#define PR_IPM_WASTEBASKET_SEARCH_KEY				PROP_TAG( PT_BINARY,	0x3412)
#define PR_IPM_SENTMAIL_SEARCH_KEY					PROP_TAG( PT_BINARY,	0x3413)
#define PR_MDB_PROVIDER								PROP_TAG( PT_BINARY,	0x3414)
#define PR_RECEIVE_FOLDER_SETTINGS					PROP_TAG( PT_OBJECT,	0x3415)

#define PR_VALID_FOLDER_MASK						PROP_TAG( PT_LONG,		0x35DF)
#define PR_IPM_SUBTREE_ENTRYID						PROP_TAG( PT_BINARY,	0x35E0)

#define PR_IPM_OUTBOX_ENTRYID						PROP_TAG( PT_BINARY,	0x35E2)
#define PR_IPM_WASTEBASKET_ENTRYID					PROP_TAG( PT_BINARY,	0x35E3)
#define PR_IPM_SENTMAIL_ENTRYID						PROP_TAG( PT_BINARY,	0x35E4)
#define PR_VIEWS_ENTRYID							PROP_TAG( PT_BINARY,	0x35E5)
#define PR_COMMON_VIEWS_ENTRYID						PROP_TAG( PT_BINARY,	0x35E6)
#define PR_FINDER_ENTRYID							PROP_TAG( PT_BINARY,	0x35E7)

/* Proptags 0x35E8-0x35FF reserved for folders "guaranteed" by PR_VALID_FOLDER_MASK */


/*
 *	Folder and AB Container properties
 */

#define PR_CONTAINER_FLAGS							PROP_TAG( PT_LONG,		0x3600)
#define PR_FOLDER_TYPE								PROP_TAG( PT_LONG,		0x3601)
#define PR_CONTENT_COUNT							PROP_TAG( PT_LONG,		0x3602)
#define PR_CONTENT_UNREAD							PROP_TAG( PT_LONG,		0x3603)
#define PR_CREATE_TEMPLATES							PROP_TAG( PT_OBJECT,	0x3604)
#define PR_DETAILS_TABLE							PROP_TAG( PT_OBJECT,	0x3605)
#define PR_SEARCH									PROP_TAG( PT_OBJECT,	0x3607)
#define PR_SELECTABLE								PROP_TAG( PT_BOOLEAN,	0x3609)
#define PR_SUBFOLDERS								PROP_TAG( PT_BOOLEAN,	0x360A)
#define PR_STATUS									PROP_TAG( PT_LONG,		0x360B)
#define PR_ANR										PROP_TAG( PT_TSTRING,	0x360C)
#define PR_ANR_W									PROP_TAG( PT_UNICODE,	0x360C)
#define PR_ANR_A									PROP_TAG( PT_STRING8,	0x360C)
#define PR_CONTENTS_SORT_ORDER						PROP_TAG( PT_MV_LONG,	0x360D)
#define PR_CONTAINER_HIERARCHY						PROP_TAG( PT_OBJECT,	0x360E)
#define PR_CONTAINER_CONTENTS						PROP_TAG( PT_OBJECT,	0x360F)
#define PR_FOLDER_ASSOCIATED_CONTENTS				PROP_TAG( PT_OBJECT,	0x3610)
#define PR_DEF_CREATE_DL							PROP_TAG( PT_BINARY,	0x3611)
#define PR_DEF_CREATE_MAILUSER						PROP_TAG( PT_BINARY,	0x3612)
#define	PR_CONTAINER_CLASS							PROP_TAG( PT_TSTRING,	0x3613)
#define	PR_CONTAINER_CLASS_W						PROP_TAG( PT_UNICODE,	0x3613)
#define	PR_CONTAINER_CLASS_A						PROP_TAG( PT_STRING8,	0x3613)
#define	PR_CONTAINER_MODIFY_VERSION					PROP_TAG( PT_I8,		0x3614)
#define PR_AB_PROVIDER_ID							PROP_TAG( PT_BINARY,	0x3615)
#define PR_DEFAULT_VIEW_ENTRYID						PROP_TAG( PT_BINARY,	0x3616)
#define	PR_ASSOC_CONTENT_COUNT						PROP_TAG( PT_LONG,		0x3617)

/* Reserved 0x36C0-0x36FF */

/*
 *	Attachment properties
 */

#define PR_ATTACHMENT_X400_PARAMETERS				PROP_TAG( PT_BINARY,	0x3700)
#define PR_ATTACH_DATA_OBJ							PROP_TAG( PT_OBJECT,	0x3701)
#define PR_ATTACH_DATA_BIN							PROP_TAG( PT_BINARY,	0x3701)
#define PR_ATTACH_ENCODING							PROP_TAG( PT_BINARY,	0x3702)
#define PR_ATTACH_EXTENSION							PROP_TAG( PT_TSTRING,	0x3703)
#define PR_ATTACH_EXTENSION_W						PROP_TAG( PT_UNICODE,	0x3703)
#define PR_ATTACH_EXTENSION_A						PROP_TAG( PT_STRING8,	0x3703)
#define PR_ATTACH_FILENAME							PROP_TAG( PT_TSTRING,	0x3704)
#define PR_ATTACH_FILENAME_W						PROP_TAG( PT_UNICODE,	0x3704)
#define PR_ATTACH_FILENAME_A						PROP_TAG( PT_STRING8,	0x3704)
#define PR_ATTACH_METHOD							PROP_TAG( PT_LONG,		0x3705)
#define PR_ATTACH_LONG_FILENAME	 					PROP_TAG( PT_TSTRING,	0x3707)
#define PR_ATTACH_LONG_FILENAME_W					PROP_TAG( PT_UNICODE,	0x3707)
#define PR_ATTACH_LONG_FILENAME_A					PROP_TAG( PT_STRING8,	0x3707)
#define PR_ATTACH_PATHNAME							PROP_TAG( PT_TSTRING,	0x3708)
#define PR_ATTACH_PATHNAME_W						PROP_TAG( PT_UNICODE,	0x3708)
#define PR_ATTACH_PATHNAME_A						PROP_TAG( PT_STRING8,	0x3708)
#define PR_ATTACH_RENDERING							PROP_TAG( PT_BINARY,    0x3709)
#define PR_ATTACH_TAG								PROP_TAG( PT_BINARY,	0x370A)
#define PR_RENDERING_POSITION						PROP_TAG( PT_LONG,		0x370B)
#define PR_ATTACH_TRANSPORT_NAME					PROP_TAG( PT_TSTRING,	0x370C)
#define PR_ATTACH_TRANSPORT_NAME_W					PROP_TAG( PT_UNICODE,	0x370C)
#define PR_ATTACH_TRANSPORT_NAME_A					PROP_TAG( PT_STRING8,	0x370C)
#define PR_ATTACH_LONG_PATHNAME	 					PROP_TAG( PT_TSTRING,	0x370D)
#define PR_ATTACH_LONG_PATHNAME_W					PROP_TAG( PT_UNICODE,	0x370D)
#define PR_ATTACH_LONG_PATHNAME_A					PROP_TAG( PT_STRING8,	0x370D)
#define PR_ATTACH_MIME_TAG							PROP_TAG( PT_TSTRING,	0x370E)
#define PR_ATTACH_MIME_TAG_W						PROP_TAG( PT_UNICODE,	0x370E)
#define PR_ATTACH_MIME_TAG_A						PROP_TAG( PT_STRING8,	0x370E)
#define	PR_ATTACH_ADDITIONAL_INFO					PROP_TAG( PT_BINARY,	0x370F)

/*
 *  AB Object properties
 */

#define PR_DISPLAY_TYPE								PROP_TAG( PT_LONG,		0x3900)
#define PR_TEMPLATEID								PROP_TAG( PT_BINARY,	0x3902)
#define PR_PRIMARY_CAPABILITY						PROP_TAG( PT_BINARY,	0x3904)


/*
 *	Mail user properties
 */
#define PR_7BIT_DISPLAY_NAME						PROP_TAG( PT_STRING8,	0x39FF)
#define PR_ACCOUNT									PROP_TAG( PT_TSTRING,	0x3A00)
#define PR_ACCOUNT_W								PROP_TAG( PT_UNICODE,	0x3A00)
#define PR_ACCOUNT_A								PROP_TAG( PT_STRING8,	0x3A00)
#define PR_ALTERNATE_RECIPIENT						PROP_TAG( PT_BINARY,	0x3A01)
#define PR_CALLBACK_TELEPHONE_NUMBER				PROP_TAG( PT_TSTRING,	0x3A02)
#define PR_CALLBACK_TELEPHONE_NUMBER_W				PROP_TAG( PT_UNICODE,	0x3A02)
#define PR_CALLBACK_TELEPHONE_NUMBER_A				PROP_TAG( PT_STRING8,	0x3A02)
#define PR_CONVERSION_PROHIBITED					PROP_TAG( PT_BOOLEAN,	0x3A03)
#define PR_DISCLOSE_RECIPIENTS						PROP_TAG( PT_BOOLEAN,	0x3A04)
#define PR_GENERATION								PROP_TAG( PT_TSTRING,	0x3A05)
#define PR_GENERATION_W								PROP_TAG( PT_UNICODE,	0x3A05)
#define PR_GENERATION_A								PROP_TAG( PT_STRING8,	0x3A05)
#define PR_GIVEN_NAME								PROP_TAG( PT_TSTRING,	0x3A06)
#define PR_GIVEN_NAME_W								PROP_TAG( PT_UNICODE,	0x3A06)
#define PR_GIVEN_NAME_A								PROP_TAG( PT_STRING8,	0x3A06)
#define PR_GOVERNMENT_ID_NUMBER						PROP_TAG( PT_TSTRING,	0x3A07)
#define PR_GOVERNMENT_ID_NUMBER_W					PROP_TAG( PT_UNICODE,	0x3A07)
#define PR_GOVERNMENT_ID_NUMBER_A					PROP_TAG( PT_STRING8,	0x3A07)
#define PR_BUSINESS_TELEPHONE_NUMBER				PROP_TAG( PT_TSTRING,	0x3A08)
#define PR_BUSINESS_TELEPHONE_NUMBER_W				PROP_TAG( PT_UNICODE,	0x3A08)
#define PR_BUSINESS_TELEPHONE_NUMBER_A				PROP_TAG( PT_STRING8,	0x3A08)
#define PR_OFFICE_TELEPHONE_NUMBER					PR_BUSINESS_TELEPHONE_NUMBER
#define PR_OFFICE_TELEPHONE_NUMBER_W				PR_BUSINESS_TELEPHONE_NUMBER_W
#define PR_OFFICE_TELEPHONE_NUMBER_A				PR_BUSINESS_TELEPHONE_NUMBER_A
#define PR_HOME_TELEPHONE_NUMBER					PROP_TAG( PT_TSTRING,	0x3A09)
#define PR_HOME_TELEPHONE_NUMBER_W					PROP_TAG( PT_UNICODE,	0x3A09)
#define PR_HOME_TELEPHONE_NUMBER_A					PROP_TAG( PT_STRING8,	0x3A09)
#define PR_INITIALS									PROP_TAG( PT_TSTRING,	0x3A0A)
#define PR_INITIALS_W								PROP_TAG( PT_UNICODE,	0x3A0A)
#define PR_INITIALS_A								PROP_TAG( PT_STRING8,	0x3A0A)
#define PR_KEYWORD									PROP_TAG( PT_TSTRING,	0x3A0B)
#define PR_KEYWORD_W								PROP_TAG( PT_UNICODE,	0x3A0B)
#define PR_KEYWORD_A								PROP_TAG( PT_STRING8,	0x3A0B)
#define PR_LANGUAGE									PROP_TAG( PT_TSTRING,	0x3A0C)
#define PR_LANGUAGE_W								PROP_TAG( PT_UNICODE,	0x3A0C)
#define PR_LANGUAGE_A								PROP_TAG( PT_STRING8,	0x3A0C)
#define PR_LOCATION									PROP_TAG( PT_TSTRING,	0x3A0D)
#define PR_LOCATION_W								PROP_TAG( PT_UNICODE,	0x3A0D)
#define PR_LOCATION_A								PROP_TAG( PT_STRING8,	0x3A0D)
#define PR_MAIL_PERMISSION							PROP_TAG( PT_BOOLEAN,	0x3A0E)
#define PR_MHS_COMMON_NAME							PROP_TAG( PT_TSTRING,	0x3A0F)
#define PR_MHS_COMMON_NAME_W						PROP_TAG( PT_UNICODE,	0x3A0F)
#define PR_MHS_COMMON_NAME_A						PROP_TAG( PT_STRING8,	0x3A0F)
#define PR_ORGANIZATIONAL_ID_NUMBER					PROP_TAG( PT_TSTRING,	0x3A10)
#define PR_ORGANIZATIONAL_ID_NUMBER_W				PROP_TAG( PT_UNICODE,	0x3A10)
#define PR_ORGANIZATIONAL_ID_NUMBER_A				PROP_TAG( PT_STRING8,	0x3A10)
#define PR_SURNAME									PROP_TAG( PT_TSTRING,	0x3A11)
#define PR_SURNAME_W								PROP_TAG( PT_UNICODE,	0x3A11)
#define PR_SURNAME_A								PROP_TAG( PT_STRING8,	0x3A11)
#define PR_ORIGINAL_ENTRYID							PROP_TAG( PT_BINARY,	0x3A12)
#define PR_ORIGINAL_DISPLAY_NAME					PROP_TAG( PT_TSTRING,	0x3A13)
#define PR_ORIGINAL_DISPLAY_NAME_W					PROP_TAG( PT_UNICODE,	0x3A13)
#define PR_ORIGINAL_DISPLAY_NAME_A					PROP_TAG( PT_STRING8,	0x3A13)
#define PR_ORIGINAL_SEARCH_KEY						PROP_TAG( PT_BINARY,	0x3A14)
#define PR_POSTAL_ADDRESS							PROP_TAG( PT_TSTRING,	0x3A15)
#define PR_POSTAL_ADDRESS_W							PROP_TAG( PT_UNICODE,	0x3A15)
#define PR_POSTAL_ADDRESS_A							PROP_TAG( PT_STRING8,	0x3A15)
#define PR_COMPANY_NAME								PROP_TAG( PT_TSTRING,	0x3A16)
#define PR_COMPANY_NAME_W							PROP_TAG( PT_UNICODE,	0x3A16)
#define PR_COMPANY_NAME_A							PROP_TAG( PT_STRING8,	0x3A16)
#define PR_TITLE									PROP_TAG( PT_TSTRING,	0x3A17)
#define PR_TITLE_W									PROP_TAG( PT_UNICODE,	0x3A17)
#define PR_TITLE_A									PROP_TAG( PT_STRING8,	0x3A17)
#define PR_DEPARTMENT_NAME							PROP_TAG( PT_TSTRING,	0x3A18)
#define PR_DEPARTMENT_NAME_W						PROP_TAG( PT_UNICODE,	0x3A18)
#define PR_DEPARTMENT_NAME_A						PROP_TAG( PT_STRING8,	0x3A18)
#define PR_OFFICE_LOCATION							PROP_TAG( PT_TSTRING,	0x3A19)
#define PR_OFFICE_LOCATION_W						PROP_TAG( PT_UNICODE,	0x3A19)
#define PR_OFFICE_LOCATION_A						PROP_TAG( PT_STRING8,	0x3A19)
#define PR_PRIMARY_TELEPHONE_NUMBER					PROP_TAG( PT_TSTRING,	0x3A1A)
#define PR_PRIMARY_TELEPHONE_NUMBER_W				PROP_TAG( PT_UNICODE,	0x3A1A)
#define PR_PRIMARY_TELEPHONE_NUMBER_A				PROP_TAG( PT_STRING8,	0x3A1A)
#define PR_BUSINESS2_TELEPHONE_NUMBER				PROP_TAG( PT_TSTRING,	0x3A1B)
#define PR_BUSINESS2_TELEPHONE_NUMBER_W				PROP_TAG( PT_UNICODE,	0x3A1B)
#define PR_BUSINESS2_TELEPHONE_NUMBER_A				PROP_TAG( PT_STRING8,	0x3A1B)
#define PR_OFFICE2_TELEPHONE_NUMBER					PR_BUSINESS2_TELEPHONE_NUMBER
#define PR_OFFICE2_TELEPHONE_NUMBER_W				PR_BUSINESS2_TELEPHONE_NUMBER_W
#define PR_OFFICE2_TELEPHONE_NUMBER_A				PR_BUSINESS2_TELEPHONE_NUMBER_A
#define PR_MOBILE_TELEPHONE_NUMBER					PROP_TAG( PT_TSTRING,	0x3A1C)
#define PR_MOBILE_TELEPHONE_NUMBER_W				PROP_TAG( PT_UNICODE,	0x3A1C)
#define PR_MOBILE_TELEPHONE_NUMBER_A				PROP_TAG( PT_STRING8,	0x3A1C)
#define PR_CELLULAR_TELEPHONE_NUMBER				PR_MOBILE_TELEPHONE_NUMBER
#define PR_CELLULAR_TELEPHONE_NUMBER_W				PR_MOBILE_TELEPHONE_NUMBER_W
#define PR_CELLULAR_TELEPHONE_NUMBER_A				PR_MOBILE_TELEPHONE_NUMBER_A
#define PR_RADIO_TELEPHONE_NUMBER					PROP_TAG( PT_TSTRING,	0x3A1D)
#define PR_RADIO_TELEPHONE_NUMBER_W					PROP_TAG( PT_UNICODE,	0x3A1D)
#define PR_RADIO_TELEPHONE_NUMBER_A					PROP_TAG( PT_STRING8,	0x3A1D)
#define PR_CAR_TELEPHONE_NUMBER						PROP_TAG( PT_TSTRING,	0x3A1E)
#define PR_CAR_TELEPHONE_NUMBER_W					PROP_TAG( PT_UNICODE,	0x3A1E)
#define PR_CAR_TELEPHONE_NUMBER_A					PROP_TAG( PT_STRING8,	0x3A1E)
#define PR_OTHER_TELEPHONE_NUMBER					PROP_TAG( PT_TSTRING,	0x3A1F)
#define PR_OTHER_TELEPHONE_NUMBER_W					PROP_TAG( PT_UNICODE,	0x3A1F)
#define PR_OTHER_TELEPHONE_NUMBER_A					PROP_TAG( PT_STRING8,	0x3A1F)
#define PR_TRANSMITABLE_DISPLAY_NAME				PROP_TAG( PT_TSTRING,	0x3A20)
#define PR_TRANSMITABLE_DISPLAY_NAME_W				PROP_TAG( PT_UNICODE,	0x3A20)
#define PR_TRANSMITABLE_DISPLAY_NAME_A				PROP_TAG( PT_STRING8,	0x3A20)
#define PR_PAGER_TELEPHONE_NUMBER					PROP_TAG( PT_TSTRING,	0x3A21)
#define PR_PAGER_TELEPHONE_NUMBER_W					PROP_TAG( PT_UNICODE,	0x3A21)
#define PR_PAGER_TELEPHONE_NUMBER_A					PROP_TAG( PT_STRING8,	0x3A21)
#define PR_BEEPER_TELEPHONE_NUMBER					PR_PAGER_TELEPHONE_NUMBER
#define PR_BEEPER_TELEPHONE_NUMBER_W				PR_PAGER_TELEPHONE_NUMBER_W
#define PR_BEEPER_TELEPHONE_NUMBER_A				PR_PAGER_TELEPHONE_NUMBER_A
#define PR_USER_CERTIFICATE							PROP_TAG( PT_BINARY,	0x3A22)
#define PR_PRIMARY_FAX_NUMBER						PROP_TAG( PT_TSTRING,	0x3A23)
#define PR_PRIMARY_FAX_NUMBER_W						PROP_TAG( PT_UNICODE,	0x3A23)
#define PR_PRIMARY_FAX_NUMBER_A						PROP_TAG( PT_STRING8,	0x3A23)
#define PR_BUSINESS_FAX_NUMBER						PROP_TAG( PT_TSTRING,	0x3A24)
#define PR_BUSINESS_FAX_NUMBER_W					PROP_TAG( PT_UNICODE,	0x3A24)
#define PR_BUSINESS_FAX_NUMBER_A					PROP_TAG( PT_STRING8,	0x3A24)
#define PR_HOME_FAX_NUMBER							PROP_TAG( PT_TSTRING,	0x3A25)
#define PR_HOME_FAX_NUMBER_W						PROP_TAG( PT_UNICODE,	0x3A25)
#define PR_HOME_FAX_NUMBER_A						PROP_TAG( PT_STRING8,	0x3A25)
#define PR_COUNTRY									PROP_TAG( PT_TSTRING,	0x3A26)
#define PR_COUNTRY_W								PROP_TAG( PT_UNICODE,	0x3A26)
#define PR_COUNTRY_A								PROP_TAG( PT_STRING8,	0x3A26)
#define PR_BUSINESS_ADDRESS_COUNTRY					PR_COUNTRY
#define PR_BUSINESS_ADDRESS_COUNTRY_W				PR_COUNTRY_W
#define PR_BUSINESS_ADDRESS_COUNTRY_A				PR_COUNTRY_A

#define PR_LOCALITY									PROP_TAG( PT_TSTRING,	0x3A27)
#define PR_LOCALITY_W								PROP_TAG( PT_UNICODE,	0x3A27)
#define PR_LOCALITY_A								PROP_TAG( PT_STRING8,	0x3A27)
#define PR_BUSINESS_ADDRESS_CITY					PR_LOCALITY
#define PR_BUSINESS_ADDRESS_CITY_W					PR_LOCALITY_W
#define PR_BUSINESS_ADDRESS_CITY_A					PR_LOCALITY_A

#define PR_STATE_OR_PROVINCE						PROP_TAG( PT_TSTRING,	0x3A28)
#define PR_STATE_OR_PROVINCE_W						PROP_TAG( PT_UNICODE,	0x3A28)
#define PR_STATE_OR_PROVINCE_A						PROP_TAG( PT_STRING8,	0x3A28)
#define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE		PR_STATE_OR_PROVINCE
#define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_W		PR_STATE_OR_PROVINCE_W
#define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_A		PR_STATE_OR_PROVINCE_A

#define PR_STREET_ADDRESS							PROP_TAG( PT_TSTRING,	0x3A29)
#define PR_STREET_ADDRESS_W							PROP_TAG( PT_UNICODE,	0x3A29)
#define PR_STREET_ADDRESS_A							PROP_TAG( PT_STRING8,	0x3A29)
#define PR_BUSINESS_ADDRESS_STREET					PR_STREET_ADDRESS
#define PR_BUSINESS_ADDRESS_STREET_W				PR_STREET_ADDRESS_W
#define PR_BUSINESS_ADDRESS_STREET_A				PR_STREET_ADDRESS_A

#define PR_POSTAL_CODE								PROP_TAG( PT_TSTRING,	0x3A2A)
#define PR_POSTAL_CODE_W							PROP_TAG( PT_UNICODE,	0x3A2A)
#define PR_POSTAL_CODE_A							PROP_TAG( PT_STRING8,	0x3A2A)
#define PR_BUSINESS_ADDRESS_POSTAL_CODE				PR_POSTAL_CODE
#define PR_BUSINESS_ADDRESS_POSTAL_CODE_W			PR_POSTAL_CODE_W
#define PR_BUSINESS_ADDRESS_POSTAL_CODE_A			PR_POSTAL_CODE_A


#define PR_POST_OFFICE_BOX							PROP_TAG( PT_TSTRING,	0x3A2B)
#define PR_POST_OFFICE_BOX_W						PROP_TAG( PT_UNICODE,	0x3A2B)
#define PR_POST_OFFICE_BOX_A						PROP_TAG( PT_STRING8,	0x3A2B)
#define PR_BUSINESS_ADDRESS_POST_OFFICE_BOX			PR_POST_OFFICE_BOX
#define PR_BUSINESS_ADDRESS_POST_OFFICE_BOX_W		PR_POST_OFFICE_BOX_W
#define PR_BUSINESS_ADDRESS_POST_OFFICE_BOX_A		PR_POST_OFFICE_BOX_A


#define PR_TELEX_NUMBER								PROP_TAG( PT_TSTRING,	0x3A2C)
#define PR_TELEX_NUMBER_W							PROP_TAG( PT_UNICODE,	0x3A2C)
#define PR_TELEX_NUMBER_A							PROP_TAG( PT_STRING8,	0x3A2C)
#define PR_ISDN_NUMBER								PROP_TAG( PT_TSTRING,	0x3A2D)
#define PR_ISDN_NUMBER_W							PROP_TAG( PT_UNICODE,	0x3A2D)
#define PR_ISDN_NUMBER_A							PROP_TAG( PT_STRING8,	0x3A2D)
#define PR_ASSISTANT_TELEPHONE_NUMBER				PROP_TAG( PT_TSTRING,	0x3A2E)
#define PR_ASSISTANT_TELEPHONE_NUMBER_W				PROP_TAG( PT_UNICODE,	0x3A2E)
#define PR_ASSISTANT_TELEPHONE_NUMBER_A				PROP_TAG( PT_STRING8,	0x3A2E)
#define PR_HOME2_TELEPHONE_NUMBER					PROP_TAG( PT_TSTRING,	0x3A2F)
#define PR_HOME2_TELEPHONE_NUMBER_W					PROP_TAG( PT_UNICODE,	0x3A2F)
#define PR_HOME2_TELEPHONE_NUMBER_A					PROP_TAG( PT_STRING8,	0x3A2F)
#define PR_ASSISTANT								PROP_TAG( PT_TSTRING,	0x3A30)
#define PR_ASSISTANT_W								PROP_TAG( PT_UNICODE,	0x3A30)
#define PR_ASSISTANT_A								PROP_TAG( PT_STRING8,	0x3A30)
#define PR_SEND_RICH_INFO							PROP_TAG( PT_BOOLEAN,	0x3A40)

#define PR_WEDDING_ANNIVERSARY						PROP_TAG( PT_SYSTIME, 0x3A41)
#define PR_BIRTHDAY									PROP_TAG( PT_SYSTIME, 0x3A42)


#define PR_HOBBIES									PROP_TAG( PT_TSTRING, 0x3A43)
#define PR_HOBBIES_W								PROP_TAG( PT_UNICODE, 0x3A43)
#define PR_HOBBIES_A								PROP_TAG( PT_STRING8, 0x3A43)

#define PR_MIDDLE_NAME								PROP_TAG( PT_TSTRING, 0x3A44)
#define PR_MIDDLE_NAME_W							PROP_TAG( PT_UNICODE, 0x3A44)
#define PR_MIDDLE_NAME_A							PROP_TAG( PT_STRING8, 0x3A44)

#define PR_DISPLAY_NAME_PREFIX						PROP_TAG( PT_TSTRING, 0x3A45)
#define PR_DISPLAY_NAME_PREFIX_W					PROP_TAG( PT_UNICODE, 0x3A45)
#define PR_DISPLAY_NAME_PREFIX_A					PROP_TAG( PT_STRING8, 0x3A45)

#define PR_PROFESSION								PROP_TAG( PT_TSTRING, 0x3A46)
#define PR_PROFESSION_W								PROP_TAG( PT_UNICODE, 0x3A46)
#define PR_PROFESSION_A								PROP_TAG( PT_STRING8, 0x3A46)

#define PR_PREFERRED_BY_NAME						PROP_TAG( PT_TSTRING, 0x3A47)
#define PR_PREFERRED_BY_NAME_W						PROP_TAG( PT_UNICODE, 0x3A47)
#define PR_PREFERRED_BY_NAME_A						PROP_TAG( PT_STRING8, 0x3A47)

#define PR_SPOUSE_NAME								PROP_TAG( PT_TSTRING, 0x3A48)
#define PR_SPOUSE_NAME_W							PROP_TAG( PT_UNICODE, 0x3A48)
#define PR_SPOUSE_NAME_A							PROP_TAG( PT_STRING8, 0x3A48)

#define PR_COMPUTER_NETWORK_NAME					PROP_TAG( PT_TSTRING, 0x3A49)
#define PR_COMPUTER_NETWORK_NAME_W					PROP_TAG( PT_UNICODE, 0x3A49)
#define PR_COMPUTER_NETWORK_NAME_A					PROP_TAG( PT_STRING8, 0x3A49)

#define PR_CUSTOMER_ID								PROP_TAG( PT_TSTRING, 0x3A4A)
#define PR_CUSTOMER_ID_W							PROP_TAG( PT_UNICODE, 0x3A4A)
#define PR_CUSTOMER_ID_A							PROP_TAG( PT_STRING8, 0x3A4A)

#define PR_TTYTDD_PHONE_NUMBER						PROP_TAG( PT_TSTRING, 0x3A4B)
#define PR_TTYTDD_PHONE_NUMBER_W					PROP_TAG( PT_UNICODE, 0x3A4B)
#define PR_TTYTDD_PHONE_NUMBER_A					PROP_TAG( PT_STRING8, 0x3A4B)

#define PR_FTP_SITE									PROP_TAG( PT_TSTRING, 0x3A4C)
#define PR_FTP_SITE_W								PROP_TAG( PT_UNICODE, 0x3A4C)
#define PR_FTP_SITE_A								PROP_TAG( PT_STRING8, 0x3A4C)

#define PR_GENDER									PROP_TAG( PT_SHORT, 0x3A4D)

#define PR_MANAGER_NAME								PROP_TAG( PT_TSTRING, 0x3A4E)
#define PR_MANAGER_NAME_W							PROP_TAG( PT_UNICODE, 0x3A4E)
#define PR_MANAGER_NAME_A							PROP_TAG( PT_STRING8, 0x3A4E)

#define PR_NICKNAME									PROP_TAG( PT_TSTRING, 0x3A4F)
#define PR_NICKNAME_W								PROP_TAG( PT_UNICODE, 0x3A4F)
#define PR_NICKNAME_A								PROP_TAG( PT_STRING8, 0x3A4F)

#define PR_PERSONAL_HOME_PAGE						PROP_TAG( PT_TSTRING, 0x3A50)
#define PR_PERSONAL_HOME_PAGE_W						PROP_TAG( PT_UNICODE, 0x3A50)
#define PR_PERSONAL_HOME_PAGE_A						PROP_TAG( PT_STRING8, 0x3A50)


#define PR_BUSINESS_HOME_PAGE						PROP_TAG( PT_TSTRING, 0x3A51)
#define PR_BUSINESS_HOME_PAGE_W						PROP_TAG( PT_UNICODE, 0x3A51)
#define PR_BUSINESS_HOME_PAGE_A						PROP_TAG( PT_STRING8, 0x3A51)

#define PR_CONTACT_VERSION							PROP_TAG( PT_CLSID, 0x3A52)
#define PR_CONTACT_ENTRYIDS							PROP_TAG( PT_MV_BINARY, 0x3A53)

#define PR_CONTACT_ADDRTYPES						PROP_TAG( PT_MV_TSTRING, 0x3A54)
#define PR_CONTACT_ADDRTYPES_W						PROP_TAG( PT_MV_UNICODE, 0x3A54)
#define PR_CONTACT_ADDRTYPES_A						PROP_TAG( PT_MV_STRING8, 0x3A54)

#define PR_CONTACT_DEFAULT_ADDRESS_INDEX			PROP_TAG( PT_LONG, 0x3A55)

#define PR_CONTACT_EMAIL_ADDRESSES					PROP_TAG( PT_MV_TSTRING, 0x3A56)
#define PR_CONTACT_EMAIL_ADDRESSES_W				PROP_TAG( PT_MV_UNICODE, 0x3A56)
#define PR_CONTACT_EMAIL_ADDRESSES_A				PROP_TAG( PT_MV_STRING8, 0x3A56)


#define PR_COMPANY_MAIN_PHONE_NUMBER				PROP_TAG( PT_TSTRING, 0x3A57)
#define PR_COMPANY_MAIN_PHONE_NUMBER_W				PROP_TAG( PT_UNICODE, 0x3A57)
#define PR_COMPANY_MAIN_PHONE_NUMBER_A				PROP_TAG( PT_STRING8, 0x3A57)

#define PR_CHILDRENS_NAMES							PROP_TAG( PT_MV_TSTRING, 0x3A58)
#define PR_CHILDRENS_NAMES_W						PROP_TAG( PT_MV_UNICODE, 0x3A58)
#define PR_CHILDRENS_NAMES_A						PROP_TAG( PT_MV_STRING8, 0x3A58)



#define PR_HOME_ADDRESS_CITY						PROP_TAG( PT_TSTRING, 0x3A59)
#define PR_HOME_ADDRESS_CITY_W						PROP_TAG( PT_UNICODE, 0x3A59)
#define PR_HOME_ADDRESS_CITY_A						PROP_TAG( PT_STRING8, 0x3A59)

#define PR_HOME_ADDRESS_COUNTRY						PROP_TAG( PT_TSTRING, 0x3A5A)
#define PR_HOME_ADDRESS_COUNTRY_W					PROP_TAG( PT_UNICODE, 0x3A5A)
#define PR_HOME_ADDRESS_COUNTRY_A					PROP_TAG( PT_STRING8, 0x3A5A)

#define PR_HOME_ADDRESS_POSTAL_CODE					PROP_TAG( PT_TSTRING, 0x3A5B)
#define PR_HOME_ADDRESS_POSTAL_CODE_W				PROP_TAG( PT_UNICODE, 0x3A5B)
#define PR_HOME_ADDRESS_POSTAL_CODE_A				PROP_TAG( PT_STRING8, 0x3A5B)

#define PR_HOME_ADDRESS_STATE_OR_PROVINCE			PROP_TAG( PT_TSTRING, 0x3A5C)
#define PR_HOME_ADDRESS_STATE_OR_PROVINCE_W			PROP_TAG( PT_UNICODE, 0x3A5C)
#define PR_HOME_ADDRESS_STATE_OR_PROVINCE_A			PROP_TAG( PT_STRING8, 0x3A5C)

#define PR_HOME_ADDRESS_STREET						PROP_TAG( PT_TSTRING, 0x3A5D)
#define PR_HOME_ADDRESS_STREET_W					PROP_TAG( PT_UNICODE, 0x3A5D)
#define PR_HOME_ADDRESS_STREET_A					PROP_TAG( PT_STRING8, 0x3A5D)

#define PR_HOME_ADDRESS_POST_OFFICE_BOX				PROP_TAG( PT_TSTRING, 0x3A5E)
#define PR_HOME_ADDRESS_POST_OFFICE_BOX_W			PROP_TAG( PT_UNICODE, 0x3A5E)
#define PR_HOME_ADDRESS_POST_OFFICE_BOX_A			PROP_TAG( PT_STRING8, 0x3A5E)

#define PR_OTHER_ADDRESS_CITY						PROP_TAG( PT_TSTRING, 0x3A5F)
#define PR_OTHER_ADDRESS_CITY_W						PROP_TAG( PT_UNICODE, 0x3A5F)
#define PR_OTHER_ADDRESS_CITY_A						PROP_TAG( PT_STRING8, 0x3A5F)

#define PR_OTHER_ADDRESS_COUNTRY					PROP_TAG( PT_TSTRING, 0x3A60)
#define PR_OTHER_ADDRESS_COUNTRY_W					PROP_TAG( PT_UNICODE, 0x3A60)
#define PR_OTHER_ADDRESS_COUNTRY_A					PROP_TAG( PT_STRING8, 0x3A60)

#define PR_OTHER_ADDRESS_POSTAL_CODE				PROP_TAG( PT_TSTRING, 0x3A61)
#define PR_OTHER_ADDRESS_POSTAL_CODE_W				PROP_TAG( PT_UNICODE, 0x3A61)
#define PR_OTHER_ADDRESS_POSTAL_CODE_A				PROP_TAG( PT_STRING8, 0x3A61)

#define PR_OTHER_ADDRESS_STATE_OR_PROVINCE			PROP_TAG( PT_TSTRING, 0x3A62)
#define PR_OTHER_ADDRESS_STATE_OR_PROVINCE_W		PROP_TAG( PT_UNICODE, 0x3A62)
#define PR_OTHER_ADDRESS_STATE_OR_PROVINCE_A		PROP_TAG( PT_STRING8, 0x3A62)

#define PR_OTHER_ADDRESS_STREET						PROP_TAG( PT_TSTRING, 0x3A63)
#define PR_OTHER_ADDRESS_STREET_W					PROP_TAG( PT_UNICODE, 0x3A63)
#define PR_OTHER_ADDRESS_STREET_A					PROP_TAG( PT_STRING8, 0x3A63)

#define PR_OTHER_ADDRESS_POST_OFFICE_BOX			PROP_TAG( PT_TSTRING, 0x3A64)
#define PR_OTHER_ADDRESS_POST_OFFICE_BOX_W			PROP_TAG( PT_UNICODE, 0x3A64)
#define PR_OTHER_ADDRESS_POST_OFFICE_BOX_A			PROP_TAG( PT_STRING8, 0x3A64)


/*
 *	Profile section properties
 */

#define PR_STORE_PROVIDERS							PROP_TAG( PT_BINARY,	0x3D00)
#define PR_AB_PROVIDERS								PROP_TAG( PT_BINARY,	0x3D01)
#define PR_TRANSPORT_PROVIDERS						PROP_TAG( PT_BINARY,	0x3D02)

#define PR_DEFAULT_PROFILE							PROP_TAG( PT_BOOLEAN,	0x3D04)
#define PR_AB_SEARCH_PATH							PROP_TAG( PT_MV_BINARY,	0x3D05)
#define PR_AB_DEFAULT_DIR							PROP_TAG( PT_BINARY,	0x3D06)
#define PR_AB_DEFAULT_PAB							PROP_TAG( PT_BINARY,	0x3D07)

#define PR_FILTERING_HOOKS                          PROP_TAG( PT_BINARY,    0x3D08)
#define PR_SERVICE_NAME								PROP_TAG( PT_TSTRING,	0x3D09)
#define PR_SERVICE_NAME_W							PROP_TAG( PT_UNICODE,	0x3D09)
#define PR_SERVICE_NAME_A							PROP_TAG( PT_STRING8,	0x3D09)
#define PR_SERVICE_DLL_NAME							PROP_TAG( PT_TSTRING,	0x3D0A)
#define PR_SERVICE_DLL_NAME_W						PROP_TAG( PT_UNICODE,	0x3D0A)
#define PR_SERVICE_DLL_NAME_A						PROP_TAG( PT_STRING8,	0x3D0A)
#define PR_SERVICE_ENTRY_NAME						PROP_TAG( PT_STRING8,	0x3D0B)
#define PR_SERVICE_UID								PROP_TAG( PT_BINARY,	0x3D0C)
#define PR_SERVICE_EXTRA_UIDS						PROP_TAG( PT_BINARY,	0x3D0D)
#define PR_SERVICES									PROP_TAG( PT_BINARY,	0x3D0E)
#define PR_SERVICE_SUPPORT_FILES   					PROP_TAG( PT_MV_TSTRING, 0x3D0F)
#define PR_SERVICE_SUPPORT_FILES_W					PROP_TAG( PT_MV_UNICODE, 0x3D0F)
#define PR_SERVICE_SUPPORT_FILES_A					PROP_TAG( PT_MV_STRING8, 0x3D0F)
#define PR_SERVICE_DELETE_FILES   					PROP_TAG( PT_MV_TSTRING, 0x3D10)
#define PR_SERVICE_DELETE_FILES_W					PROP_TAG( PT_MV_UNICODE, 0x3D10)
#define PR_SERVICE_DELETE_FILES_A					PROP_TAG( PT_MV_STRING8, 0x3D10)
#define PR_AB_SEARCH_PATH_UPDATE   					PROP_TAG( PT_BINARY, 	 0x3D11)
#define PR_PROFILE_NAME								PROP_TAG( PT_TSTRING,	0x3D12)
#define PR_PROFILE_NAME_A							PROP_TAG( PT_STRING8,	0x3D12)
#define PR_PROFILE_NAME_W							PROP_TAG( PT_UNICODE,	0x3D12)

/*
 *	Status object properties
 */

#define PR_IDENTITY_DISPLAY							PROP_TAG( PT_TSTRING,	0x3E00)
#define PR_IDENTITY_DISPLAY_W						PROP_TAG( PT_UNICODE,	0x3E00)
#define PR_IDENTITY_DISPLAY_A						PROP_TAG( PT_STRING8,	0x3E00)
#define PR_IDENTITY_ENTRYID							PROP_TAG( PT_BINARY,	0x3E01)
#define PR_RESOURCE_METHODS							PROP_TAG( PT_LONG,		0x3E02)
#define PR_RESOURCE_TYPE							PROP_TAG( PT_LONG,		0x3E03)
#define PR_STATUS_CODE								PROP_TAG( PT_LONG,		0x3E04)
#define PR_IDENTITY_SEARCH_KEY						PROP_TAG( PT_BINARY,	0x3E05)
#define PR_OWN_STORE_ENTRYID						PROP_TAG( PT_BINARY,	0x3E06)
#define PR_RESOURCE_PATH							PROP_TAG( PT_TSTRING,   0x3E07)
#define PR_RESOURCE_PATH_W							PROP_TAG( PT_UNICODE,   0x3E07)
#define PR_RESOURCE_PATH_A							PROP_TAG( PT_STRING8,   0x3E07)
#define PR_STATUS_STRING							PROP_TAG( PT_TSTRING,	0x3E08)
#define PR_STATUS_STRING_W							PROP_TAG( PT_UNICODE,	0x3E08)
#define PR_STATUS_STRING_A							PROP_TAG( PT_STRING8,	0x3E08)
#define PR_X400_DEFERRED_DELIVERY_CANCEL			PROP_TAG( PT_BOOLEAN,	0x3E09)
#define PR_HEADER_FOLDER_ENTRYID					PROP_TAG( PT_BINARY,	0x3E0A)
#define PR_REMOTE_PROGRESS							PROP_TAG( PT_LONG,		0x3E0B)
#define PR_REMOTE_PROGRESS_TEXT						PROP_TAG( PT_TSTRING,	0x3E0C)
#define PR_REMOTE_PROGRESS_TEXT_W					PROP_TAG( PT_UNICODE,	0x3E0C)
#define PR_REMOTE_PROGRESS_TEXT_A					PROP_TAG( PT_STRING8,	0x3E0C)
#define PR_REMOTE_VALIDATE_OK						PROP_TAG( PT_BOOLEAN,	0x3E0D)

/*
 * Display table properties
 */

#define PR_CONTROL_FLAGS							PROP_TAG( PT_LONG,		0x3F00)
#define PR_CONTROL_STRUCTURE						PROP_TAG( PT_BINARY,	0x3F01)
#define PR_CONTROL_TYPE								PROP_TAG( PT_LONG,		0x3F02)
#define PR_DELTAX									PROP_TAG( PT_LONG,		0x3F03)
#define PR_DELTAY									PROP_TAG( PT_LONG,		0x3F04)
#define PR_XPOS										PROP_TAG( PT_LONG,		0x3F05)
#define PR_YPOS										PROP_TAG( PT_LONG,		0x3F06)
#define PR_CONTROL_ID								PROP_TAG( PT_BINARY,	0x3F07)
#define PR_INITIAL_DETAILS_PANE						PROP_TAG( PT_LONG,		0x3F08)

}

procedure TIdCoderTNEF.DoLog(const AMsg: String; const AAppendSize: Boolean = True);
begin
  if AAppendSize then begin
    FLog := FLog + IntToStr(FData.Size - FData.Position) + ':' + AMsg + EOL;  {Do not localize}
  end else begin
    FLog := FLog + AMsg + EOL;
  end;
end;

procedure TIdCoderTNEF.DoLogFmt(const AFormat: string; const Args: array of const; AAppendSize: Boolean = True);
begin
  DoLog(IndyFormat(AFormat, Args), AAppendSize);
end;

function TIdCoderTNEF.GetStringForMapiType(AType: Word): string;
begin
  case AType of
    IdTNEF_PT_UNSPECIFIED: begin
      Result := 'Unspecified';  {Do not localize}
    end;
    IdTNEF_PT_NULL: begin
      Result := 'Null';  {Do not localize}
    end;
    IdTNEF_PT_I2: begin
      Result := 'Short';  {Do not localize}
    end;
    IdTNEF_PT_LONG: begin
      Result := 'Long';  {Do not localize}
    end;
    IdTNEF_PT_R4: begin
      Result := 'Float';  {Do not localize}
    end;
    IdTNEF_PT_DOUBLE: begin
      Result := 'Double';  {Do not localize}
    end;
    IdTNEF_PT_CURRENCY: begin
      Result := 'Currency';  {Do not localize}
    end;
    IdTNEF_PT_APPTIME: begin
      Result := 'Application time';  {Do not localize}
    end;
    IdTNEF_PT_ERROR: begin
      Result := 'Error code';  {Do not localize}
    end;
    IdTNEF_PT_BOOLEAN: begin
      Result := 'Boolean';  {Do not localize}
    end;
    IdTNEF_PT_OBJECT: begin
      Result := 'Object';  {Do not localize}
    end;
    IdTNEF_PT_I8: begin
      Result := '64-bit integer';  {Do not localize}
    end;
    IdTNEF_PT_STRING8: begin
      Result := 'String8';  {Do not localize}
    end;
    IdTNEF_PT_UNICODE: begin
      Result := 'Unicode';  {Do not localize}
    end;
    IdTNEF_PT_SYSTIME: begin
      Result := 'SysTime';  {Do not localize}
    end;
    IdTNEF_PT_CLSID: begin
      Result := 'ClsId';  {Do not localize}
    end;
    IdTNEF_PT_BINARY: begin
      Result := 'Binary';  {Do not localize}
    end;
  else
    Result := 'Unknown';  {Do not localize}
  end;
end;

function TIdCoderTNEF.GetStringForType(AType: Word): string;
begin
  case AType of
    IdTNEFAtpTriples: begin
      Result := 'Triples';  {Do not localize}
    end;
    IdTNEFAtpString: begin
      Result := 'String';  {Do not localize}
    end;
    IdTNEFAtpText: begin
      Result := 'Text';  {Do not localize}
    end;
    IdTNEFAtpDate: begin
      Result := 'Date';  {Do not localize}
    end;
    IdTNEFAtpShort: begin
      Result := 'Short';  {Do not localize}
    end;
    IdTNEFAtpLong: begin
      Result := 'Long';  {Do not localize}
    end;
    IdTNEFAtpByte: begin
      Result := 'Byte';  {Do not localize}
    end;
    IdTNEFAtpWord: begin
      Result := 'Word';  {Do not localize}
    end;
    IdTNEFAtpDWord: begin
      Result := 'DWord';  {Do not localize}
    end;
    IdTNEFAtpMax: begin
      Result := 'Max';  {Do not localize}
    end;
  else
    Result := 'Unknown';  {Do not localize}
  end;
end;

function TIdCoderTNEF.GetStringForAttribute(AAttribute: Word): string;
begin
  case AAttribute of
    IdTNEFattNull: begin
      Result := 'Null';  {Do not localize}
    end;
    IdTNEFattFrom: begin
      Result := 'From';  {Do not localize}
    end;
    IdTNEFattSubject: begin
      Result := 'Subject';  {Do not localize}
    end;
    IdTNEFattDateSent: begin
      Result := 'DateSent';  {Do not localize}
    end;
    IdTNEFattDateRecd: begin
      Result := 'DateRecd';  {Do not localize}
    end;
    IdTNEFattMessageStatus: begin
      Result := 'MessageStatus';  {Do not localize}
    end;
    IdTNEFattMessageClass: begin
      Result := 'MessageClass';  {Do not localize}
    end;
    IdTNEFattMessageID: begin
      Result := 'MessageID';  {Do not localize}
    end;
    IdTNEFattParentID: begin
      Result := 'ParentID';  {Do not localize}
    end;
    IdTNEFattConversationID: begin
      Result := 'ConversationID';  {Do not localize}
    end;
    IdTNEFattPriority: begin
      Result := 'Priority';  {Do not localize}
    end;
    IdTNEFattAttachData: begin
      Result := 'AttachData';  {Do not localize}
    end;
    IdTNEFattAttachTitle: begin
      Result := 'AttachTitle';  {Do not localize}
    end;
    IdTNEFattAttachMetaFile: begin
      Result := 'AttachMetaFile';  {Do not localize}
    end;
    IdTNEFattAttachCreateDate: begin
      Result := 'AttachCreateDate';  {Do not localize}
    end;
    IdTNEFattAttachModifyDate: begin
      Result := 'AttachModifyDate';  {Do not localize}
    end;
    IdTNEFattDateModified: begin
      Result := 'DateModified';  {Do not localize}
    end;
    IdTNEFattAttachTransportFilename: begin
      Result := 'AttachTransportFilename';  {Do not localize}
    end;
    IdTNEFattAttachRenddata: begin
      Result := 'AttachRenddata';  {Do not localize}
    end;
    IdTNEFattMAPIProps: begin
      Result := 'MAPIProps';  {Do not localize}
    end;
    IdTNEFattRecipTable: begin
      Result := 'RecipTable';  {Do not localize}
    end;
    IdTNEFattAttachment: begin
      Result := 'Null';  {Do not localize}
    end;
    IdTNEFattTnefVersion: begin
      Result := 'TnefVersion';  {Do not localize}
    end;
    IdTNEFattOemCodepage: begin
      Result := 'OemCodepage';  {Do not localize}
    end;
    IdTNEFattOriginalMessageClass: begin
      Result := 'OriginalMessageClass';  {Do not localize}
    end;
    //IdTNEFattOwner: begin
    //  Result := 'Owner';  {Do not localize}
    //end;
    IdTNEFattSentFor: begin
      Result := 'SentFor';  {Do not localize}
    end;
    IdTNEFattDelegate: begin
      Result := 'Delegate';  {Do not localize}
    end;
    //IdTNEFattDateStart: begin
    //  Result := 'DateStart';  {Do not localize}
    //end;
    IdTNEFattDateEnd: begin
      Result := 'DateEnd';  {Do not localize}
    end;
    IdTNEFattAidOwner: begin
      Result := 'OwnerAID';  {Do not localize}
    end;
    IdTNEFattRequestRes: begin
      Result := 'ResponseRequested';  {Do not localize}
    end;
  else
    Result := 'Unknown';  {Do not localize}
  end;
end;

class function TIdCoderTNEF.IsFilenameTnef(const AFilename: string): Boolean;
begin
  Result := IdCoderTNEF.IsFilenameTnef(AFilename);
end;

function IsFilenameTnef(const AFilename: string): Boolean;
begin
  if TextIsSame(AFilename, 'winmail.dat') then begin
    Result := True;
  end
  else if TextStartsWith(AFilename, 'att') and TextEndsWith(AFilename, '.dat') then begin
    Result := IndyStrToInt(Copy(AFilename, 4, Length(AFilename)-7), -1) > -1;
  end else begin
    Result := False;
  end;
end;

function TIdCoderTNEF.GetMultipleUnicodeOrString8String(AType: Word): TIdUnicodeString;
var
  LIndex, LCount: LongWord;
begin
  //Usually this will only contain one string, but if there are more, return
  //them as a single string concatenated with semicolons.
  LCount := GetLongWord;
  if FDoLogging then begin
    DoLogFmt('     Found %d %s String(s):', [LCount, GetStringForMapiType(AType)]);  {Do not localize}
  end;
  if LCount = 0 then begin  //Very unlikely, just paranoia
    Result := '';
    Exit;
  end;
  Result := GetUnicodeOrString8String(AType);
  for LIndex := 2 to LCount do begin
    Result := ';' + GetUnicodeOrString8String(AType);    {Do not localize}
  end;
end;

function TIdCoderTNEF.GetUnicodeOrString8String(AType: Word): TIdUnicodeString;
var
  LLength: LongWord;
  LsTemp: AnsiString;
  LBuf: TIdBytes;
begin
  Result := '';
  LLength := GetLongWord;
  if LLength = 0 then begin
    Exit;
  end;
  //Note the length count includes a terminating null.
  case AType of
    IdTNEF_PT_UNICODE: begin
      LBuf := GetBytes(LLength);
      SetString(Result, PWideChar(LBuf), (LLength div SizeOf(TIdWideChar))-1);
    end;
    IdTNEF_PT_STRING8: begin
      LBuf := GetBytes(LLength);
      // TODO: use the value from the attOemCodepage attribute to decode the data:
      // Result := IndyTextEncoding(attOemCodepage).GetString(LBuf, 0, Length(LBuf)-1);
      SetString(LsTemp, PAnsiChar(LBuf), LLength-1);
      Result := TIdUnicodeString(LsTemp);
    end;
    else begin
      Skip(LLength);
    end;
  end;
  if FDoLogging then begin
    DoLog('     Found string value: ' + Result);  {Do not localize}
  end;
  //Note the strings are padded to 4-byte boundaries...
  if (LLength mod 4) > 0 then begin
    Skip(4 - (LLength mod 4));
  end;
end;

function TIdCoderTNEF.PadWithZeroes(const AStr: string; ACount: integer): string;
begin
  if Length(AStr) < ACount then begin
    Result := StringOfChar('0', ACount-Length(AStr)) + AStr;
  end else begin
    Result := AStr;
  end;
end;

function TIdCoderTNEF.GetByteAsChar(AByte: Byte): Char;
begin
  //Return a displayable char or '.' if not displayable.
  if (Ord(AByte) > 31) and (Ord(AByte) < 127) then begin
    Result := Chr(AByte);
  end else begin
    Result := '.';  {Do not localize}
  end;
end;

function TIdCoderTNEF.GetByteAsHexString(AByte: Byte): string;
var
  LsTemp: string;
begin
  LsTemp := IndyFormat('%x', [AByte]);    {Do not localize}
  Result := PadWithZeroes(LsTemp, 2);
end;

function TIdCoderTNEF.GetByteAsHexString: string;
var
  LnTemp: Byte;
  LsTemp: string;
begin
  LnTemp := GetByte;
  LsTemp := IndyFormat('%x', [LnTemp]);  {Do not localize}
  Result := PadWithZeroes(LsTemp, 2);
end;

function TIdCoderTNEF.GetBytesAsHexString(ACount: integer): string;
var
  i: integer;
begin
  Result := '';
  for i := 0 to ACount-1 do begin
    Result := Result + GetByteAsHexString + ' ';  {Do not localize}
  end;
end;

function TIdCoderTNEF.GetByte: Byte;
var
  LTemp: TIdBytes;
begin
  LTemp := GetBytes(SizeOf(Byte));
  Result := LTemp[0];
end;

function TIdCoderTNEF.GetBytes(ALength: Integer; APeek: Boolean = False): TIdBytes;
var
  LPos: TIdStreamSize;
begin
  Result := nil;
  CheckForEof(ALength);
  LPos := FData.Position;
  try
    //Note the length count includes a terminating null.
    ReadTIdBytesFromStream(FData, Result, ALength);
  finally
    if APeek then begin
      FData.Position := LPos;
    end;
  end;
end;

function TIdCoderTNEF.GetWord: Word;
var
  LTemp: TIdBytes;
begin
  LTemp := GetBytes(SizeOf(Word));
  Result := PWord(LTemp)^;
end;

function TIdCoderTNEF.GetLongWord: LongWord;
var
  LTemp: TIdBytes;
begin
  LTemp := GetBytes(SizeOf(LongWord));
  Result := PLongWord(LTemp)^;
end;

function TIdCoderTNEF.GetInt64: Int64;
var
  LTemp: TIdBytes;
begin
  LTemp := GetBytes(SizeOf(Int64));
  Result := PInt64(@LTemp[0])^;
end;

function TIdCoderTNEF.GetString(ALength: Word): string;
begin
  if ALength > 0 then begin
    Result := ReadStringFromStream(FData, ALength-1, IndyTextEncoding_8Bit{$IFDEF STRING_IS_ANSI}, IndyTextEncoding_8Bit{$ENDIF});
    Skip(1) //Skip terminating null
  end else begin
    Result := '';
  end;
end;

function  TIdCoderTNEF.GetDate(ALength: Word): TDateTime;
var
  LYear, LMonth, LDay, LHour, LMinute, LSecond: Word;
begin
  LYear   := GetWord;
  LMonth  := GetWord;
  LDay    := GetWord;
  LHour   := GetWord;
  LMinute := GetWord;
  LSecond := GetWord;
  Skip(SizeOf(Word));  //Day-of-week
  Result := EncodeDateTime(LYear, LMonth, LDay, LHour, LMinute, LSecond, 0);
end;

procedure TIdCoderTNEF.Skip(ACount: integer);
begin
  CheckForEof(ACount);
  FData.Seek(ACount, soFromCurrent);
end;

procedure TIdCoderTNEF.Checksum(ANumBytesToCheck: integer);
var
  LChecksum: Word;
  i: integer;
  LBytes: TIdBytes;
  LTNEFChecksum: Word;
begin
  //Do a checksum on ANumBytesToCheck bytes from the current position.
  //Compare to the recorded TNEF value in the word after these bytes.
  //DONT move our stream pointer forward.
  LBytes := GetBytes(ANumBytesToCheck + SizeOf(Word), True);
  LChecksum := 0;
  for i := 0 to ANumBytesToCheck-1 do begin
    Inc(LChecksum, LBytes[i]);
  end;
  LTNEFChecksum := PWord(@LBytes[ANumBytesToCheck])^;
  if LChecksum <> LTNEFChecksum then begin
    raise EIdTnefChecksumFailure.Create('Checksum failure - TNEF is corrupt or truncated');  {Do not localize}
  end;
end;

procedure TIdCoderTNEF.CheckForEof(ANumBytesRequested: integer);
begin
  //See if you have enough bytes left to satisfy the request for nNumBytesRequested...
  if (FData.Size - FData.Position) < TIdStreamSize(ANumBytesRequested) then begin
    raise EIdTnefRanOutOfBytes.Create('Hit end of file prematurely - TNEF is corrupt or truncated');  {Do not localize}
  end;
end;

procedure TIdCoderTNEF.Parse(const AIn: string; AMsg: TIdMessage; ALog: Boolean = False);
var
  LIn: TMemoryStream;
begin
  LIn := TMemoryStream.Create;
  try
    WriteStringToStream(LIn, AIn, IndyTextEncoding_8Bit{$IFDEF STRING_IS_ANSI}, IndyTextEncoding_8Bit{$ENDIF});
    LIn.Position := 0;
    Parse(LIn, AMsg, ALog);
  finally
    LIn.Free;
  end;
end;

procedure TIdCoderTNEF.Parse(const AIn: TIdAttachment; AMsg: TIdMessage; ALog: Boolean = False);
var
  LTempStream: TStream;
begin
  LTempStream := AIn.OpenLoadStream;
  try
    Parse(LTempStream, AMsg, ALog);
  finally
    AIn.CloseLoadStream;
  end;
end;

procedure TIdCoderTNEF.Parse(const AIn: TIdBytes; AMsg: TIdMessage; ALog: Boolean = False);
var
  LIn: TMemoryStream;
begin
  LIn := TMemoryStream.Create;
  try
    WriteTIdBytesToStream(LIn, AIn);
    LIn.Position := 0;
    Parse(LIn, AMsg, ALog);
  finally
    LIn.Free;
  end;
end;

procedure TIdCoderTNEF.ParseMessageBlock;
var
  LType, LAttribute: Word;
begin
  LAttribute := GetWord;
  LType := GetWord;
  ParseAttribute(LAttribute, LType);
end;

procedure TIdCoderTNEF.IsCurrentAttachmentValid;
begin
  if FCurrentAttachment = nil then begin
    raise EIdTnefCurrentAttachmentInvalid.Create('Attempt to access invalid attachment - invalid TNEF missing attAttachRenddata attribute at start of attachment?');  {Do not localize}
  end;
end;

function  TIdCoderTNEF.GetAttributeString(const AAttributeName: string; AType: Word): string;
var
  LLength: LongWord;
begin
  LLength := GetLongWord;
  if FDoLogging then begin
    DoLogFmt('   ParseAttachmentBlock found %s type, length: %d', [AAttributeName, LLength]);  {Do not localize}
  end;
  if AType <> IdTNEFAtpString then begin
    raise EIdTnefAttributeUnexpectedType.Create(AAttributeName + ' not a String');  {Do not localize}
  end;
  Checksum(LLength);
  Result := GetString(LLength);
  Skip(2);  //Checksum
end;

procedure TIdCoderTNEF.ParseAttachmentBlock;
var
  LType, LAttribute: Word;
  LLength: LongWord;
  LDestStream: TStream;
begin
  LAttribute := GetWord;
  LType := GetWord;
  if FDoLogging then begin
    DoLogFmt('   ParseAttachmentBlock passed a %s type %s', [GetStringForAttribute(LAttribute), GetStringForType(LType)]);  {Do not localize}
  end;
  case LAttribute of
    IdTNEFattAttachRenddata: begin
      //Per Microsoft, you get this first, at the start of every attachment,
      //create a new attachment when you encounter this.
      LLength := GetLongWord;
      if FDoLogging then begin
        DoLog('   ParseAttachmentBlock found IdTNETattAttachRenddata type, length: ' + IntToStr(LLength));  {Do not localize}
      end;
      Checksum(LLength);
      Skip(LLength);
      Skip(2);  //Checksum
      if FDoLogging then begin
        DoLog('   Adding attachment to decoded message.');  {Do not localize}
      end;
      FMsg.DoCreateAttachment(nil, FCurrentAttachment);
      FCurrentAttachment.ParentPart := -1;
    end;
    IdTNEFattAttachTitle: begin
      //This is the filename of the attachment, set the already-created attachment's
      //filename to this.
      IsCurrentAttachmentValid;
      FCurrentAttachment.FileName := GetAttributeString('IdTNEFattAttachTitle', LType);  {Do not localize}
      if FDoLogging then begin
        DoLog('   ParseAttachmentBlock parsed attachment filename: ' + FCurrentAttachment.FileName);  {Do not localize}
      end;
    end;
    IdTNEFattAttachData: begin
      //This is the attachment file contents, set it for the already-created
      //attachment.
      LLength := GetLongWord;
      if FDoLogging then begin
        DoLog('   ParseAttachmentBlock found IdTNEFattAttachData type, length: ' + IntToStr(LLength));  {Do not localize}
      end;
      if LType <> IdTNEFAtpByte then begin
        raise EIdTnefAttributeUnexpectedType.Create('TNEF AttachmentData not a Byte');  {Do not localize}
      end;
      Checksum(LLength);
      if LLength > 0 then begin
        IsCurrentAttachmentValid;
        LDestStream := FCurrentAttachment.PrepareTempStream;
        try
          LDestStream.CopyFrom(FData, LLength);
        finally
          FCurrentAttachment.FinishTempStream;
        end;
      end;
      if FDoLogging then begin
        DoLogFmt('   ParseAttachmentBlock copied %d bytes to attachment.', [LLength]);  {Do not localize}
      end;
      Skip(2);  //Checksum
      end;
    else
      if FDoLogging then begin
        DoLogFmt('   ParseAttachmentBlock found unknown attribute: %d, type: %d, passing to ParseAttribute.', [LAttribute, LType]);  {Do not localize}
      end;
      ParseAttribute(LAttribute, LType);
  end;
end;

function TIdCoderTNEF.GetMapiBoolean(AType: Word; const AText: string): Smallint;
begin
  if AType <> IdTNEF_PT_BOOLEAN then begin
    raise EIdTnefUnexpectedType.CreateFmt('Expected Boolean for %s', [AText]);  {Do not localize}
  end;
  Result := GetWord;
  Skip(SizeOf(Word));  //Skip next two bytes (padded to 4 bytes)
  if FDoLogging then begin
    DoLogFmt('     ParseMapiProp found %s Boolean, value: %d', [AText, Result]);  {Do not localize}
  end;
end;

function TIdCoderTNEF.GetMapiLong(AType: Word; const AText: string): Longint;
begin
  if AType <> IdTNEF_PT_LONG then begin
    raise EIdTnefUnexpectedType.CreateFmt('Expected Long for %s', [AText]);  {Do not localize}
  end;
  Result := GetLongWord;
  if FDoLogging then begin
    DoLogFmt('     ParseMapiProp found %s Long, value: %d', [AText, Result]);  {Do not localize}
  end;
end;

function  TIdCoderTNEF.GetMapiSysTime(AType: Word; const AText: string): TDateTime;
var
  LHour, LMinute, LSecond, LMilliSecond: Word;
  LVal: Int64;
  LTime: Double;
begin
  //MAPI's SysTime is a 64-bit integer holding the number of 100ns intervals
  //since 1st Jan 1601.
  if AType <> IdTNEF_PT_SYSTIME then begin
    raise EIdTnefUnexpectedType.CreateFmt('Expected SysTime for %s', [AText]);  {Do not localize}
  end;
  LVal := GetInt64;
  //I am sure there is a better way of doing the following...
  LVal := LVal div 10;  //Ditch the 100ns
  LVal := LVal div 1000;  //Ditch the ms
  LMilliSecond := LVal mod 1000;
  LVal := LVal div 1000;
  LSecond := LVal mod 60;
  LVal := LVal div 60;
  LMinute := LVal mod 60;
  LVal := LVal div 60;
  LHour := LVal mod 24;
  LVal := LVal div 24;
  //LVal is now the days since 1/1/1601.  Subtract Delphi's 300-year offset...
  Result := LVal;
  Result := Result + EncodeDate(1601, 1, 1);  {Do not localize}
  //Is the hour out by 1 or is it WET vs GMT time?  Or is it GetDate that is the hour out?
  LTime := ((((((LHour*60)+LMinute)*60)+LSecond)*1000)+LMilliSecond)/(24*60*60*1000);
  Result := Result + LTime;
  if FDoLogging then begin
    DoLogFmt('     ParseMapiProp found %s SysTime, value: %s', [AText, DateTimeToStr(Result)]);  {Do not localize}
  end;
end;

function TIdCoderTNEF.GetMapiStrings(AType: Word; const AText: string): string;
begin
  //May be PT_UNICODE or PT_STRING8 (PT_TSTRING will be aliased to one of these)...
  if (AType <> IdTNEF_PT_UNICODE) and (AType <> IdTNEF_PT_STRING8) then begin
    raise EIdTnefUnexpectedType.CreateFmt('Expected Unicode or String8 for %s', [AText]);  {Do not localize}
  end;
  Result := GetMultipleUnicodeOrString8String(AType);
  if FDoLogging then begin
    DoLogFmt('     ParseMapiProp found %s String, value: %s', [AText, Result]);  {Do not localize}
  end;
end;

{ GetMapiObject was previously needed, may be needed later in development...
function TIdCoderTNEF.GetMapiObject(AType: Word; const AText: string): TIdBytes;
begin
  if AType <> IdTNEF_PT_OBJECT then begin
    raise EIdTnefUnexpectedType.CreateFmt('Expected Object for %s', [AText]);  {Do not localize}
{  end;
  Result := GetMapiItemAsBytes(AType, AText);
end;
}

function TIdCoderTNEF.GetMapiBinaryAsString(AType: Word; const AText: string): string;
var
  LBinary: TIdBytes;
  LStrLen: integer;
  LIndex: integer;
begin
  //You MUST know that the binary data is really a null-terminated string.
  LBinary := GetMapiBinary(AType, AText);
  LStrLen := Length(LBinary)-1;
  Result := '';
  for LIndex := 0 to LStrLen-1 do begin
    Result := Result + Chr(LBinary[LIndex]);
  end;
end;

function TIdCoderTNEF.GetMapiBinaryAsEmailName(AType: Word; const AText: string): string;
begin
  Result := GetMapiBinaryAsString(AType, AText);
  //If it starts SMTP: then remove SMTP:, but leave anything else (e.g. FAX:)
  if TextStartsWith(Result, 'SMTP:') then begin  {Do not localize}
    Result := Trim(Copy(Result, 6, MaxInt));
  end;
  Result := LowerCase(Result);
end;

function TIdCoderTNEF.GetMapiBinary(AType: Word; const AText: string): TIdBytes;
begin
  if AType <> IdTNEF_PT_BINARY then begin
    raise EIdTnefUnexpectedType.CreateFmt('Expected Binary for %s', [AText]);  {Do not localize}
  end;
  Result := GetMapiItemAsBytesPossiblyCompressed(AType, AText);
end;

function TIdCoderTNEF.GetMapiItemAsBytesPossiblyCompressed(AType: Word; const AText: string): TIdBytes;
var
  LCount, LLength: LongWord;
  LMagicNumber: LongWord;
  LCompressedSize, LUncompressedSize: LongWord;
  LPos: TIdStreamSize;
begin
  SetLength(Result, 0);
  LCount := GetLongWord;
  if FDoLogging then begin
    DoLogFmt('     Found %d %s:', [LCount, GetStringForMapiType(AType)]);  {Do not localize}
  end;
  if LCount = 0 then begin  //Very unlikely, just paranoia
    Exit;
  end;
  if LCount <> 1 then begin
    raise EIdTnefNotSupported.Create('Binary/Object not supported with a count > 1');  {Do not localize}
  end;
  LLength := GetLongWord;
  if LLength >= 12 then begin
    //Peek ahead to see if it has an optional magic number, indicating that it
    //has another header here.
    //If it has a valid magic number, then the next long is the
    //uncompressed size, then the compressed size, next is the magic (long) number,
    //next is a CRC.
    //We initially only want to see if it has a magic number...
    LPos := FData.Position;
    try
      Skip(8);
      LMagicNumber := GetLongWord;
    finally
      FData.Position := LPos;
    end;
    if LMagicNumber = $414C454D then begin
      //It has a header, but this magic number means it is NOT compressed.
      //Note: I have never seen this option existing in reality.
      LCompressedSize := GetLongWord;
      LUncompressedSize := GetLongWord;
      Skip(SizeOf(LongWord));  //Magic word
      Skip(SizeOf(LongWord));  //Checksum, ignore this, this block was crc-checked already
      if FDoLogging then begin
        DoLogFmt('     Is uncompressed, uncompressed size %d, compressed size %d',   {Do not localize}
          [LUncompressedSize, LCompressedSize]);
      end;
      Result := InternalGetMapiItemAsBytes(LCount, LLength-16, AType, AText);
      Exit;
    end
    else if LMagicNumber = $75465A4C then begin
      //It is compressed.  Decompress it.
      Result := DecompressRtf(LCount, LLength, AType, AText);
      Exit;
    end;
  end;
  //Not compressed (or not compressed in a format we recognise)...
  Result := InternalGetMapiItemAsBytes(LCount, LLength, AType, AText);
end;

function TIdCoderTNEF.DecompressRtf(ACount, ALength: LongWord; AType: Word; const AText: string): TIdBytes;
var
  LCompressedSize, LUncompressedSize: LongWord;
  LData: TIdBytes;
  LInIndex, LOutIndex: LongWord;
  LFlags: Byte;
  LShifts: integer;
  LFlag: Byte;
  LCodePosition: integer;
  LCodeLength: integer;
  LDecodeString: string;
  LDecodeStringLength: LongWord;
  LTemp: Integer;
  LOutBufferSize: LongWord;
begin
  //Read header...
  LCompressedSize := GetLongWord;  //Length AFTER this field (LLength - 4)
  LUncompressedSize := GetLongWord;
  GetLongWord;  //Magic number
  GetLongWord;     //Checksum, ignore this, this block was crc-checked already
  if FDoLogging then begin
    DoLogFmt('     Is compressed, uncompressed size %d, compressed size %d',  {Do not localize}
      [LUncompressedSize, LCompressedSize]);
  end;
  //Get the compressed bytes...
  LData := InternalGetMapiItemAsBytes(ACount, ALength-16, AType, AText);
  LDecodeStringLength := Length(IdTNEF_decode_string);
  LOutBufferSize := LDecodeStringLength + LUncompressedSize;
  SetLength(Result, LOutBufferSize);
  //Copy the preload decode string into the output...
  LDecodeString := IdTNEF_decode_string;
  for LTemp := 0 to LDecodeStringLength-1 do begin
    Result[LTemp] := Byte(LDecodeString[LTemp+1]);
  end;
  LInIndex := 0;
  LOutIndex := LDecodeStringLength;
  Dec(ALength, 16);  //Adjust for the header.
  LShifts := 8;  //Force an initial load of the first flag byte
  LFlags := 0;  //Stop warning
  while LInIndex < ALength do begin
    //This scheme blocks that contain a starting byte of eight flags followed by
    //eight 1 or 2-byte entries.  If the flag is 0, its entry is a single
    //literal byte, if 1 then its entry is a two-byte compression code.
    if LShifts = 8 then begin
      LFlags := LData[LInIndex];
      LShifts := 0;
      Inc(LInIndex);
    end;
    LFlag := LFlags and 1;
    LFlags := LFlags shr 1;
    Inc(LShifts);
    if LFlag = 0 then begin
      //A single literal byte...
      Result[LOutIndex] := LData[LInIndex];
      Inc(LInIndex);
      Inc(LOutIndex);
    end else begin
      //A two-byte code telling us that the bytes we want to output are
      //a copy of bytes that were previously outputted.  The position of
      //the previous bytes is the first three nibbles, the number of
      //bytes to copy is the last nibble.
      LCodePosition := LData[LInIndex];
      Inc(LInIndex);
      LCodeLength := LData[LInIndex];
      Inc(LInIndex);
      LCodePosition := LCodePosition shl 4;
      LCodePosition := LCodePosition or (LCodeLength shr 4);
      LCodeLength := LCodeLength and $F;   //The low nibble
      //Since repetitions of 0 or 1 byte would be a waste of time, the
      //length runs from 2 to 17 instead of 0 to 15...
      LCodeLength := LCodeLength + 2;
      //LCodePosition points to the byte sequence we are to copy from the
      //previously-decoded output data.  The output data is viewed as a
      //4096-byte circular buffer into which LCodePosition points.  It is
      //further complicated by the fact that the buffer is preloaded with
      //the string IdTNEF_decode_string.  Rather than using a real buffer,
      //we just calculate the corresponding position in the output data.
      LCodePosition := ((Integer(LOutIndex) div 4096) * 4096) + LCodePosition;
      //The flag byte always has 8 bits, but at the end of the data, the
      //last bits may be padding.  In this case, both LOutPosition and
      //LOutIndex will equal LOutBufferSize (1 byte past our output).
      //If we don't filter them out, we will get an access violation.
      if LOutIndex >= LOutBufferSize then begin
        if FDoLogging then begin
          DoLogFmt('  Ignoring EOD padding: %d bytes from offset %d to %d destlen %d',   {Do not localize}
            [LCodeLength, LCodePosition, LOutIndex, LOutBufferSize],
            False);
        end;
      end else begin
        if LCodePosition >= Integer(LOutIndex) then begin
          //The buffer is supposed to be a circular buffer.  Since we
          //made it a linear buffer, we need to wrap around...
          LCodePosition := LCodePosition - 4096;
        end;
        if LCodePosition < 0 then begin
          //This should never happen, would cause an AV...
          raise EIdTnefCorruptData.Create('Corrupt compressed rtf: negative code position');  {Do not localize}
        end;
        if FDoLogging then begin
          DoLogFmt('  Copying %d bytes from offset %d to %d destlen %d',   {Do not localize}
            [LCodeLength, LCodePosition, LOutIndex, LOutBufferSize], False);
        end;
        for LTemp := 0 to LCodeLength-1 do begin
          Result[LOutIndex] := Result[LCodePosition+LTemp];         //GPFs here
          Inc(LOutIndex);
        end;
      end;
    end;
  end;
  //Remove the decode string from the output...
  for LOutIndex := 0 to LUncompressedSize-1 do begin
    Result[LOutIndex] := Result[LOutIndex+LDecodeStringLength];
  end;
  SetLength(Result, LUncompressedSize);
  if FDoLogging then begin
    DoLog('     Uncompressed bytes:');  {Do not localize}
    DumpBytes(Result);
  end;
end;

function TIdCoderTNEF.GetMapiItemAsBytes(AType: Word; const AText: string): TIdBytes;
var
  LCount, LLength: LongWord;
begin
  SetLength(Result, 0);
  LCount := GetLongWord;
  if FDoLogging then begin
    DoLogFmt('     Found %d %s:', [LCount, GetStringForMapiType(AType)]);  {Do not localize}
  end;
  if LCount = 0 then begin  //Very unlikely, just paranoia
    Exit;
  end;
  if LCount <> 1 then begin
    raise EIdTnefNotSupported.Create('Binary/Object not supported with a count > 1');  {Do not localize}
  end;
  LLength := GetLongWord;
  Result := InternalGetMapiItemAsBytes(LCount, LLength, AType, AText);
end;

function TIdCoderTNEF.InternalGetMapiItemAsBytes(ACount, ALength: LongWord; AType: Word; const AText: string): TIdBytes;
var
  LPos: TIdStreamSize;
begin
  if FDoLogging then begin
    DoLogFmt('     Item had %d bytes.', [ALength]);  {Do not localize}
  end;
  LPos := FData.Position;
  Result := GetBytes(ALength);
  if FDoLogging then begin
    DumpBytes(Result);
  end;
  //Note the bytes are padded to 4-byte boundaries...
  if (ALength mod 4) > 0 then begin
    ALength := ((ALength div 4) + 1) * 4;
  end;
  FData.Position := LPos + ALength;
  if FDoLogging then begin
    DoLogFmt('     ParseMapiProp found %s Bytes, count: %d', [AText, ACount]);  {Do not localize}
  end;
end;

procedure TIdCoderTNEF.DumpBytes(const ABytes: TIdBytes);
var
  LIndex: integer;
  LLHS, LRHS: string;
begin
  LLHS := '';
  LRHS := '';
  for LIndex := 0 to Length(ABytes)-1 do begin
    LLHS := LLHS + GetByteAsHexString(ABytes[LIndex])+' ';  {Do not localize}
    LRHS := LRHS + GetByteAsChar(ABytes[LIndex]);
    if ((LIndex+1) mod 16) = 0 then begin
      DoLog('      ' + LLHS + '  ' + LRHS, False);  {Do not localize}
      LLHS := '';
      LRHS := '';
    end;
  end;
  if LLHS <> '' then begin
    while Length(LLHS) < 48 do begin
      LLHS := LLHS + ' ';  {Do not localize}
    end;
    DoLog('      ' + LLHS + '  ' + LRHS, False);  {Do not localize}
  end;
end;

procedure TIdCoderTNEF.ParseMapiProp;
var
  LType, LAttribute: Word;
  LLength, LCount, I: LongWord;
  //LGUIDType: LongWord;
  LShort: Smallint;
  LStr: string;
  LGUID: string;
  LTextPart: TIdText;
  LDate: TDateTime;
begin
  LType := GetWord;
  LAttribute := GetWord;
  //Initially, just parse out the common attributes and the ones we are interested in.
  if LAttribute >= $8000 then begin
    //A named property: this has a GUID and some other optional stuff...
    LGUID := GetBytesAsHexString(16);
    if FDoLogging then begin
      DoLog('     MAPI item has a named property, GUID: ' + LGUID);  {Do not localize}
    end;
    LLength := GetLongWord;
    if LLength = 0 then begin
      //In this case, the named property uses an identifier...
      //TODO: What is the LGUIDType below?
      {LGUIDType :=} GetLongWord;
    end else begin
      //In this case, the named property uses a string...
      //TODO: Following code not tested...
      //Skip strings for now
      LCount := LLength;
      for I := 1 to LCount do begin
        LLength := GetLongWord;
        if LLength = 0 then begin
          Continue;
        end;
        Skip(LLength);
        //Note the strings are padded to 4-byte boundaries...
        if (LLength mod 4) > 0 then begin
          Skip(4 - (LLength mod 4));
        end;
      end;
    end;
  end;
  case LAttribute of
    IdTNEF_PR_ALTERNATE_RECIPIENT_ALLOWED: begin
      {LShort := } GetMapiBoolean(LType, 'ALTERNATE_RECIPIENT_ALLOWED');  {Do not localize}
    end;
    IdTNEF_PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED: begin
      //A delivery receipt, not supported by most systems, implement as a read receipt
      LShort := GetMapiBoolean(LType, 'ORIGINATOR_DELIVERY_REPORT_REQUESTED');  {Do not localize}
      if LShort > 0 then begin
        //Have we already parsed the sender?
        if FMsg.From.Address <> '' then begin
          FMsg.ReceiptRecipient.Address := FMsg.From.Address;
        end else begin
          FReceiptRequested := True;
        end;
        if FDoLogging then begin
          DoLog('     Delivery receipt requested.');  {Do not localize}
        end;
      end else begin
        if FDoLogging then begin
          DoLog('     Delivery receipt not requested.');  {Do not localize}
        end;
      end;
    end;
    IdTNEF_PR_PRIORITY: begin
      {LLong := } GetMapiLong(LType, 'PRIORITY');  {Do not localize}
    end;
    IdTNEF_PR_READ_RECEIPT_REQUESTED: begin
      LShort := GetMapiBoolean(LType, 'READ_RECEIPT_REQUESTED');  {Do not localize}
      if LShort > 0 then begin
        //Have we already parsed the sender?
        if FMsg.From.Address <> '' then begin
          FMsg.ReceiptRecipient.Address := FMsg.From.Address;
        end else begin
          FReceiptRequested := True;
        end;
        if FDoLogging then begin
          DoLog('     Read receipt requested.');  {Do not localize}
        end;
      end else begin
        if FDoLogging then begin
          DoLog('     Read receipt not requested.');  {Do not localize}
        end;
      end;
    end;
    IdTNEF_PR_ORIGINAL_SENSITIVITY: begin
      {LLong := } GetMapiLong(LType, 'ORIGINAL_SENSITIVITY');  {Do not localize}
    end;
    IdTNEF_PR_SENSITIVITY: begin
      {LLong := } GetMapiLong(LType, 'SENSITIVITY');  {Do not localize}
    end;
    IdTNEF_PR_SUBJECT_PREFIX: begin
      {LStr := } GetMapiStrings(LType, 'SUBJECT_PREFIX');  {Do not localize}
    end;
    IdTNEF_PR_ORIGINAL_AUTHOR_NAME: begin
      {LStr := } GetMapiStrings(LType, 'ORIGINAL_AUTHOR_NAME');  {Do not localize}
    end;
    IdTNEF_PR_CONVERSATION_TOPIC: begin
      LStr := GetMapiStrings(LType, 'CONVERSATION_TOPIC');  {Do not localize}
      if FMsg.Subject = '' then begin //Only use this as subject if did not find subject at a higher level
        FMsg.Subject := LStr;
        if FDoLogging then begin
          DoLog('     Message has subject (from CONVERSATION_TOPIC): ' + LStr);  {Do not localize}
        end;
      end else begin
        if FDoLogging then begin
          DoLog('     CONVERSATION_TOPIC ignored, already have subject.  CONVERSATION_TOPIC: ' + LStr);  {Do not localize}
        end;
      end;
    end;
    IdTNEF_PR_CLIENT_SUBMIT_TIME: begin
      LDate := GetMapiSysTime(LType, 'CLIENT_SUBMIT_TIME');  {Do not localize}
      if FMsg.Date = 0 then begin //Only use this as date if did not find date at a higher level
        FMsg.Date := LDate;
        if FDoLogging then begin
          DoLog('     Message has date (from CLIENT_SUBMIT_TIME): ' + DateTimeToStr(LDate));  {Do not localize}
        end;
      end else begin
        if FDoLogging then begin
          DoLog('     CLIENT_SUBMIT_TIME ignored, already have date.  CLIENT_SUBMIT_TIME: ' + DateTimeToStr(LDate));  {Do not localize}
        end;
      end;
    end;
    IdTNEF_PR_CONVERSATION_INDEX: begin
      GetMapiBinary(LType, 'CONVERSATION_INDEX');  {Do not localize}
    end;
    IdTNEF_PR_MESSAGE_SUBMISSION_ID: begin
      GetMapiBinary(LType, 'MESSAGE_SUBMISSION_ID');  {Do not localize}
    end;
    IdTNEF_PR_ORIGINAL_SUBJECT: begin
      {LStr :=} GetMapiStrings(LType, 'ORIGINAL_SUBJECT');  {Do not localize}
    end;
    IdTNEF_PR_REPLY_REQUESTED: begin
      {LShort :=} GetMapiBoolean(LType, 'REPLY_REQUESTED');  {Do not localize}
    end;
    IdTNEF_PR_SENDER_SEARCH_KEY: begin
      LStr := GetMapiBinaryAsEmailName(LType, 'SENDER_SEARCH_KEY');  {Do not localize}
      FMsg.From.Address := LStr;
      if FReceiptRequested = True then begin
        FMsg.ReceiptRecipient.Address := FMsg.From.Address;
      end;
      if FDoLogging then begin
        DoLog('     Message sender: ' + LStr);  {Do not localize}
      end;
    end;
    IdTNEF_PR_DELETE_AFTER_SUBMIT: begin
      GetMapiBoolean(LType, 'DELETE_AFTER_SUBMIT');  {Do not localize}
    end;
    IdTNEF_PR_MESSAGE_DELIVERY_TIME: begin
      GetMapiSysTime(LType, 'MESSAGE_DELIVERY_TIME');  {Do not localize}
    end;
    IdTNEF_PR_ORIGINAL_SUBMIT_TIME: begin
      GetMapiSysTime(LType, 'ORIGINAL_SUBMIT_TIME');  {Do not localize}
    end;
    IdTNEF_PR_SENTMAIL_ENTRYID: begin
      GetMapiBinary(LType, 'SENTMAIL_ENTRYID');  {Do not localize}
    end;
    IdTNEF_PR_RTF_IN_SYNC: begin
      GetMapiBoolean(LType, 'RTF_IN_SYNC');  {Do not localize}
    end;
    IdTNEF_PR_MAPPING_SIGNATURE: begin
      GetMapiBinary(LType, 'MAPPING_SIGNATURE');  {Do not localize}
    end;
    IdTNEF_PR_STORE_RECORD_KEY: begin
      GetMapiBinary(LType, 'STORE_RECORD_KEY');  {Do not localize}
    end;
    IdTNEF_PR_STORE_ENTRYID: begin
      GetMapiBinary(LType, 'STORE_ENTRYID');  {Do not localize}
    end;
    IdTNEF_PR_ORIGINAL_SENDER_NAME: begin
      LStr := GetMapiStrings(LType, 'ORIGINAL_SENDER_NAME');  {Do not localize}
      if FMsg.From.Address = '' then begin
        FMsg.From.Address := LStr;
        if FDoLogging then begin
          DoLog('     Message has From (from ORIGINAL_SENDER_NAME): ' + LStr);  {Do not localize}
        end;
      end else begin
        if FDoLogging then begin
          DoLog('     ORIGINAL_SENDER_NAME ignored, already have From.  ORIGINAL_SENDER_NAME: ' + LStr);  {Do not localize}
        end;
      end;
      if FDoLogging then begin
        DoLog('     Message From: ' + FMsg.From.Address);  {Do not localize}
      end;
    end;
    IdTNEF_PR_ORIGINAL_SENDER_ENTRYID: begin
      GetMapiBinary(LType, 'ORIGINAL_SENDER_ENTRYID');  {Do not localize}
    end;
    IdTNEF_PR_ORIGINAL_SENDER_SEARCH_KEY: begin
      GetMapiBinary(LType, 'ORIGINAL_SENDER_SEARCH_KEY');  {Do not localize}
    end;
    IdTNEF_PR_ORIGINAL_SENT_REPRESENTING_NAME: begin
      GetMapiStrings(LType, 'ORIGINAL_SENT_REPRESENTING_NAME');  {Do not localize}
    end;
    IdTNEF_PR_ORIGINAL_SENT_REPRESENTING_ENTRYID: begin
      GetMapiBinary(LType, 'ORIGINAL_SENT_REPRESENTING_ENTRYID');  {Do not localize}
    end;
    IdTNEF_PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY: begin
      GetMapiBinary(LType, 'ORIGINAL_SENT_REPRESENTING_SEARCH_KEY');  {Do not localize}
    end;
    IdTNEF_PR_ORIGINAL_SENDER_ADDRTYPE: begin
      GetMapiStrings(LType, 'ORIGINAL_SENDER_ADDRTYPE');  {Do not localize}
    end;
    IdTNEF_PR_ORIGINAL_SENDER_EMAIL_ADDRESS: begin
      GetMapiStrings(LType, 'ORIGINAL_SENDER_EMAIL_ADDRESS');  {Do not localize}
    end;
    IdTNEF_PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE: begin
      GetMapiStrings(LType, 'ORIGINAL_SENT_REPRESENTING_ADDRTYPE');  {Do not localize}
    end;
    IdTNEF_PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS: begin
      GetMapiStrings(LType, 'ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS');  {Do not localize}
    end;
    IdTNEF_PR_SENDER_NAME: begin
      GetMapiStrings(LType, 'SENDER_NAME');  {Do not localize}
    end;
    IdTNEF_PR_NORMALIZED_SUBJECT: begin
      GetMapiStrings(LType, 'NORMALIZED_SUBJECT');  {Do not localize}
    end;
    IdTNEF_PR_ORIGINAL_DISPLAY_CC: begin
      GetMapiStrings(LType, 'ORIGINAL_DISPLAY_CC');  {Do not localize}
    end;
    IdTNEF_PR_ORIGINAL_DISPLAY_TO: begin
      GetMapiStrings(LType, 'ORIGINAL_DISPLAY_TO');  {Do not localize}
    end;
    IdTNEF_PR_OBJECT_TYPE: begin
      {LLong := } GetMapiLong(LType, 'OBJECT_TYPE');  {Do not localize}
    end;
    IdTNEF_PR_STORE_SUPPORT_MASK: begin
      {LLong := } GetMapiLong(LType, 'STORE_SUPPORT_MASK');  {Do not localize}
    end;
    IdTNEF_PR_TNEF_CORRELATION_KEY: begin
      LStr := GetMapiBinaryAsString(LType, 'CORRELATION_KEY');  {Do not localize}
      if FMsg.MsgId = '' then begin
        FMsg.MsgId := LStr;
        if FDoLogging then begin
          DoLog('     Message has message ID (from CORRELATION_KEY): ' + LStr);  {Do not localize}
        end;
      end else begin
        if FDoLogging then begin
          DoLog('     CORRELATION_KEY ignored, already have message ID.  CORRELATION_KEY: ' + LStr);  {Do not localize}
        end;
      end;
      if FDoLogging then begin
        DoLog('     Message ID: ' + FMsg.MsgId);  {Do not localize}
      end;
    end;
    IdTNEF_PR_RTF_SYNC_BODY_CRC: begin
      {LLong := } GetMapiLong(LType, 'RTF_SYNC_BODY_CRC');  {Do not localize}
    end;
    IdTNEF_PR_RTF_SYNC_BODY_COUNT: begin
      {LLong := } GetMapiLong(LType, 'RTF_SYNC_BODY_COUNT');  {Do not localize}
    end;
    IdTNEF_PR_RTF_SYNC_BODY_TAG: begin
      {LStr :=} GetMapiStrings(LType, 'RTF_SYNC_BODY_TAG');  {Do not localize}
    end;
    IdTNEF_PR_BODY: begin
      LStr := GetMapiStrings(LType, 'BODY');  {Do not localize}
      FMsg.Body.Text := LStr;
    end;
    IdTNEF_PR_RTF_COMPRESSED: begin
      LStr := GetMapiBinaryAsString(LType, 'RTF_COMPRESSED');  {Do not localize}
      //Add this as a TIdText part of type text/rtf...
      LTextPart := TIdText.Create(FMsg.MessageParts);
      LTextPart.ContentType := 'text/rtf';  {Do not localize}
      LTextPart.Body.Text := LStr;
    end;
    IdTNEF_PR_RTF_SYNC_PREFIX_COUNT: begin
      {LLong := } GetMapiLong(LType, 'RTF_SYNC_PREFIX_COUNT');  {Do not localize}
    end;
    IdTNEF_PR_RTF_SYNC_TRAILING_COUNT: begin
      {LLong := } GetMapiLong(LType, 'RTF_SYNC_TRAILING_COUNT');  {Do not localize}
    end;
    IdTNEF_PR_ORIGINALLY_INTENDED_RECIP_ENTRYID: begin
      GetMapiBinary(LType, 'ORIGINALLY_INTENDED_RECIP_ENTRYID');  {Do not localize}
    end;
  else
    //For the types we are not interested in, skip past them...
    case LType of
      IdTNEF_PT_BOOLEAN,
      IdTNEF_PT_LONG,
      IdTNEF_PT_I2,
      IdTNEF_PT_R4,
      IdTNEF_PT_ERROR,
      IdTNEF_PT_APPTIME: begin
        if FDoLogging then begin
          DoLogFmt('     Skipping MAPI attribute 0x%x of type %s', [LAttribute, GetStringForMapiType(LType)]);  {Do not localize}
        end;
        Skip(4);  //Only 2 bytes used, but padded to 4
      end;
      IdTNEF_PT_BINARY,
      IdTNEF_PT_OBJECT: begin
        if FDoLogging then begin
          DoLogFmt('     Skipping MAPI attribute 0x%x of type %s', [LAttribute, GetStringForMapiType(LType)]);  {Do not localize}
        end;
        GetMapiItemAsBytes(LType, 'ignored data');
      end;
      IdTNEF_PT_UNICODE,
      IdTNEF_PT_STRING8: begin
        if FDoLogging then begin
          DoLogFmt('     Skipping MAPI attribute 0x%x of type %s', [LAttribute, GetStringForMapiType(LType)]);  {Do not localize}
        end;
        GetMapiStrings(LType, 'ignored data');
      end;
      IdTNEF_PT_SYSTIME,
      IdTNEF_PT_DOUBLE,
      IdTNEF_PT_I8,
      IdTNEF_PT_CURRENCY: begin
        if FDoLogging then begin
          DoLogFmt('     Skipping MAPI attribute 0x%x of type %s', [LAttribute, GetStringForMapiType(LType)]);  {Do not localize}
        end;
        Skip(8);
      end;
    else
      raise EIdTnefUnknownMapiType.CreateFmt('Encountered unknown MAPI type: %d, attribute: %d', [LType, LAttribute]);  {Do not localize}
    end;
  end;
end;

procedure TIdCoderTNEF.ParseMapiProps(ALength: LongWord);
var
  LNumEntries: LongWord;
  LIndex: LongWord;
begin
  if FDoLogging then begin
    DoLogFmt('   Parsing MAPI block, %d bytes.', [ALength]);  {Do not localize}
  end;
  LNumEntries := GetLongWord;
  if FDoLogging then begin
    DoLogFmt('   Contains %d entries.', [LNumEntries]);  {Do not localize}
  end;
  if LNumEntries > 0 then begin
    for LIndex := 0 to LNumEntries-1 do begin
      if FDoLogging then begin
        DoLogFmt('    Entry %d:', [LIndex]);  {Do not localize}
      end;
      ParseMapiProp;
    end;
  end;
end;

procedure TIdCoderTNEF.ParseAttribute(AAttribute, AType: Word);
var
  LLength: LongWord;
  LMajor, LMinor: Word;
  LShort: Smallint;
begin
  LLength := GetLongWord;
  Checksum(LLength);
  case AAttribute of
    IdTNEFattTnefVersion: begin
      if AType <> IdTNEFAtpDWord then begin
        raise EIdTnefUnexpectedType.Create('Expected DWord for TnefVersion');  {Do not localize}
      end;
      LMinor := GetWord;
      LMajor := GetWord;
      if FDoLogging then begin
        DoLogFmt('     ParseAttribute found TNef Version DWord.  Major version: %d Minor version: %d', [LMajor, LMinor]);  {Do not localize}
      end;
      if (LMajor <> 1) and (LMinor <> 0) then begin
        if FDoLogging then begin
          DoLog('     Expected a version with Major = 1, Minor = 0.  Some elements may not parse correctly.');  {Do not localize}
        end;
      end else begin
        if FDoLogging then begin
          DoLog('     This is the expected version.');  {Do not localize}
        end;
      end;
    end;
    IdTNEFattSubject: begin
      if AType <> IdTNEFAtpString then begin
        raise EIdTnefUnexpectedType.Create('Expected String for TnefSubject');  {Do not localize}
      end;
      FMsg.Subject := GetString(LLength);
      if FDoLogging then begin
        DoLog('     ParseAttribute found TNef Subject String: ' + FMsg.Subject);  {Do not localize}
        DoLog('     Message has subject: ' + FMsg.Subject);  {Do not localize}
      end;
    end;
    IdTNEFattDateSent: begin
      if AType <> IdTNEFAtpDate then begin
        raise EIdTnefUnexpectedType.Create('Expected Date for TnefDateSent');  {Do not localize}
      end;
      FMsg.Date := GetDate(LLength);
      if FDoLogging then begin
        DoLog('     ParseAttribute found TNef Date Sent.');  {Do not localize}
        DoLog('     Message has date: ' + DateTimeToStr(FMsg.Date));  {Do not localize}
      end;
    end;
    IdTNEFattMessageID: begin
      if AType <> IdTNEFAtpString then begin
        raise EIdTnefUnexpectedType.Create('Expected String for TnefMessageID');  {Do not localize}
      end;
      FMsg.MsgId := GetString(LLength);
      if FDoLogging then begin
        DoLog('     ParseAttribute found TNef Message ID.');  {Do not localize}
        DoLog('     Message has ID: ' + FMsg.MsgId);  {Do not localize}
      end;
    end;
    IdTNEFattPriority: begin
      if AType <> IdTNEFAtpShort then begin
        raise EIdTnefUnexpectedType.Create('Expected Short for TnefPriority');  {Do not localize}
      end;
      LShort := GetWord;
      if FDoLogging then begin
        DoLog('     ParseAttribute found Priority Short.');  {Do not localize}
      end;
      case LShort of
        IdTNEFprioLow: begin
          FMsg.Priority := mpLow;
          if FDoLogging then begin
            DoLog('     Message has low priority.');  {Do not localize}
          end;
        end;
        IdTNEFprioNorm: begin
          FMsg.Priority := mpNormal;
          if FDoLogging then begin
            DoLog('     Message has normal priority.');  {Do not localize}
          end;
        end;
        IdTNEFprioHigh: begin
          FMsg.Priority := mpHigh;
          if FDoLogging then begin
            DoLog('     Message has high priority.');  {Do not localize}
          end;
        end;
      else
        raise EIdTnefUnexpectedValue.Create('Unexpected value for priority.');  {Do not localize}
      end;
    end;
    IdTNEFattMAPIProps: begin
      ParseMapiProps(LLength);
    end;
  else
    case AType of
      IdTNEFAtpTriples: begin
        if FDoLogging then begin
          DoLogFmt('     ParseAttribute found AtpTriples type, %s, length: %d',  {Do not localize}
            [GetStringForAttribute(AAttribute), LLength]);
        end;
        Skip(LLength);
      end;
      IdTNEFAtpString: begin
        if FDoLogging then begin
          DoLogFmt('     ParseAttribute found AtpString type, %s, length: %d',  {Do not localize}
            [GetStringForAttribute(AAttribute), LLength]);
        end;
        Skip(LLength);
      end;
      IdTNEFAtpText: begin
        if FDoLogging then begin
          DoLogFmt('     ParseAttribute found AtpText type, %s, length: %d',  {Do not localize}
            [GetStringForAttribute(AAttribute), LLength]);
        end;
        Skip(LLength);
      end;
      IdTNEFAtpDate: begin
        if FDoLogging then begin
          DoLogFmt('     ParseAttribute found AtpDate type, %s, length: %d',  {Do not localize}
            [GetStringForAttribute(AAttribute), LLength]);
         end;
        Skip(LLength);
      end;
      IdTNEFAtpShort: begin
        if FDoLogging then begin
          DoLogFmt('     ParseAttribute found AtpShort type, %s, length: %d', [GetStringForAttribute(AAttribute), LLength]);  {Do not localize}
        end;
        Skip(LLength);
      end;
      IdTNEFAtpLong: begin
        if FDoLogging then begin
          DoLogFmt('     ParseAttribute found AtpLong type, %s, length: %d',  {Do not localize}
            [GetStringForAttribute(AAttribute), LLength]);
        end;
        Skip(LLength);
      end;
      IdTNEFAtpByte: begin
        if FDoLogging then begin
          DoLogFmt('     ParseAttribute found AtpByte type, %s, length: %d',  {Do not localize}
            [GetStringForAttribute(AAttribute), LLength]);
        end;
        Skip(LLength);
      end;
      IdTNEFAtpWord: begin
        if FDoLogging then begin
          DoLogFmt('     ParseAttribute found AtpWord type, %s, length: %d',  {Do not localize}
            [GetStringForAttribute(AAttribute), LLength]);
        end;
        Skip(LLength);
      end;
      IdTNEFAtpDWord: begin
        if FDoLogging then begin
          DoLogFmt('     ParseAttribute found AtpDWord type, %s, length: %d',  {Do not localize}
            [GetStringForAttribute(AAttribute), LLength]);
        end;
        Skip(LLength);
      end;
      IdTNEFAtpMax: begin
        if FDoLogging then begin
          DoLogFmt('     ParseAttribute found AtpMax type, %s, length: %d',  {Do not localize}
            [GetStringForAttribute(AAttribute), LLength]);
        end;
        Skip(LLength);
      end;
    else
      if FDoLogging then begin
        DoLogFmt('     ParseAttribute found unknown type, %s, length: %d',  {Do not localize}
           [GetStringForAttribute(AAttribute), LLength]);
      end;
      Skip(LLength);
    end;
  end;
  Skip(2);  //Checksum
end;

procedure TIdCoderTNEF.Parse(const AIn: TStream; AMsg: TIdMessage; ALog: Boolean = False);
var
  LdwTemp: LongWord;
  LBlockType: Byte;
begin
  FLog := '';
  FDoLogging := ALog;
  FMsg := AMsg;
  FMsg.Clear;
  FMsg.ContentType := 'multipart/mixed';  //Default: improve on this at a later stage.
  FReceiptRequested := False;
  FCurrentAttachment := nil;
  FData := AIn;
  if FDoLogging then begin
    DoLogFmt('Bytes in TNEF: %d', [FData.Size - FData.Position], False);  {Do not localize}
  end;
  //Check for a valid TNEF signature...
  LdwTemp := GetLongWord;
  if LdwTemp <> IdTNEFSignature then begin
    if FDoLogging then begin
      DoLog('Invalid TNEF signature', False);  {Do not localize}
    end;
    raise EIdTnefInvalidTNEFSignature.Create('Invalid TNEF signature');  {Do not localize}
  end;
  FKey := GetWord;
  if FDoLogging then begin
    DoLogFmt('Key: %d' + EOL + 'Bytes left plus message:', [FKey], False);  {Do not localize}
  end;
  repeat
    LBlockType := GetByte;
    case LBlockType of
      IdTNEFLvlMessage: begin
        if FDoLogging then begin
          DoLog(' Calling ParseMessageBlock:');  {Do not localize}
        end;
        ParseMessageBlock;
      end;
      IdTNEFLvlAttachment: begin
        if FDoLogging then begin
          DoLog(' Calling ParseAttachmentBlock:');  {Do not localize}
        end;
        ParseAttachmentBlock;
      end;
    else
      begin
        if FDoLogging then begin
          DoLogFmt(' Hit unknown block type: %d', [LBlockType]);  {Do not localize}
        end;
        raise EIdTnefUnknownBlockType.Create('Hit unknown block type in TNEF - corrupt TNEF?');  {Do not localize}
      end;
    end;
  until FData.Position >= FData.Size;
  if FDoLogging then begin
    DoLog(' Finished processing TNEF.');  {Do not localize}
  end;
end;

end.

