unit uoexpress;

interface

uses
  Windows, SysUtils, Registry, classes, dialogs;

type

  THeaderData = record
    position: longint;
    DataLength: longint;
    HeaderLength: WORD;
    FlagCount: WORD;
  end;

  Poe5_FolderInfo = ^Toe5_FolderInfo;
  Toe5_FolderInfo = record
    FolderID: longint;
    ParentFolderID: longint;
    FolderName: string;
    FolderFile: string;
  end;

  Poe5_MessageInfo = ^Toe5_MessageInfo;
  Toe5_MessageInfo = record
    Msg: longint;
    MsgFlags: longint;
    AccountID: string;
    Account: string;
    MessageID: string;
    Sent: TFileTime;
    Received: TFileTime;
    Subject: string;
    From: string;
    From_Reply: string;
    Receipt: string;
    Reply_To: string;
    References: string;
    NewsGroup: string;
    size: longint;
    position: longint;
    HeaderPosition: longint;
  end;

  Toe5_FileHeader = record
    IndexItemsCount: integer;
    Account: string;
    ServerID: string;
  end;

  Toe5_IndexHeader = record
    FilePos: longint;
    Unknown1: longint;
    PrevIndex: longint;
    NextIndex: longint;
    Count: longint;
    Unknown: longint;
  end;

  Poe5_IndexItem = ^Toe5_IndexItem;
  Toe5_IndexItem = record
    HeaderPos: longint;
    ChildIndex: longint;
    Unknown: longint;
  end;

  Toe5_MsgItem = record
    FilePos: longint;
    Unknown: longint;
    ItemSize: longint;
    NextItem: longint;
    MsgContent: array[0..511] of Char;
  end;


function Get_OE_Directory: string;
procedure Read_OE_File(const FileName: string; var flist: TList);
procedure Read_OE_Message(Filename:string; position: longint; var msg: widestring);
function FiletimeToDatetime(const date: TFileTime): TDateTime;
procedure ReadHeaderInfo(Filename:string; position: longint; var str: widestring);

var
  oeFile: TFileStream;
  finfo: Toe5_FileHeader;
  tmpList: TList;

const
  DOWNLOADED = $1;
  MARKED = $20;
  READED = $80;
  DOWNLOAD_LATER = $100;
  NEWS_MSG = $800;  // to verify
  ATTACHMENTS = $4000;
  REPLY = $80000;
  INSPECT_CONVERSATION = $400000;
  IGNORE_CONVERSATION = $800000;
  // IMPORTED = ?

implementation

function FiletimeToDatetime(const date: TFileTime): TDateTime;
var
  st: TSystemTime;
  ft: TFileTime;
begin
  FileTimeToLocalFileTime(date, ft);
  FileTimeToSystemTime(ft, st);
  Result:=SystemTimeToDateTime(st);
end;

function Get_OE_Directory: string;
var
  Reg: TRegistry;
  tmpstr: string;
  userID: string;
begin
  tmpstr:='';
  Reg := TRegistry.Create;
  try
    Reg.RootKey := HKEY_CURRENT_USER;
    if Reg.OpenKey('\Software\Microsoft\Outlook Express', False) then
      tmpstr := Reg.ReadString('Store Root');
    if tmpstr = '' then begin
      if Reg.OpenKey('\Identities', False) then begin
        userID := Reg.ReadString('Default User ID');
        if Reg.OpenKey('\Identities\'+userID+'\Software\Microsoft\Outlook Express\5.0', False) then
          tmpstr := Reg.ReadString('Store Root');
      end;
    end;
  finally
    // added to get the right directory in WinNT and W2000
	

    if (tmpstr <> '') then begin
      SetLength(userID, 1024);
      if (ExpandEnvironmentStrings(pointer(tmpstr), @userID[1], 1024) > 0) then
        result := pchar(userID)
      else
        result := tmpstr;
    end; { if (tmpstr) }
    Reg.CloseKey;
    Reg.Free;
  end; { try }
end;


procedure GetFileInfo;
var
  hcount: integer;
begin
  if oeFile <> nil then begin
    with oeFile do begin
      Seek($C4, soFromBeginning);
      Read(hcount, sizeof(hcount));
      finfo.IndexItemsCount:=hcount;
      //Seek( $24C1, soFromBeginning);
      //read finfo.FServerID;
      //Seek( $25C1, soFromBeginning);
      //Read finfo.FAccount;
    end;
  end;
end;

procedure ReadFolderInfo(position: longint; var flist: TList);
var
  i: integer;
  oe5_FolderInfo: Poe5_FolderInfo;
  HeaderData: THeaderData;
  isFolder: boolean;
  Flags: integer;
  DataSize: integer;
  Size: integer;
  FlagsBuffer: pointer;
  pFlagsBuffer: ^DWORD;
  DataBuffer: pointer;
  pDataBuffer: ^Byte;
begin
  oeFile.Seek(position, soFromBeginning);
  oeFile.Read(HeaderData , sizeof(HeaderData));
  if position = HeaderData.position then begin
    New(oe5_FolderInfo);
    Flags:=HeaderData.FlagCount and $FF;
    DataSize:=HeaderData.DataLength-(Flags * sizeof(DWORD));
    Size:=sizeof(DWORD)*Flags;
    GetMem(FlagsBuffer, Size);
    oeFile.Read(FlagsBuffer^, Size);
    pFlagsBuffer:=FlagsBuffer;
    isFolder:=False;
    for i:= 0 to Flags-1 do begin
      isFolder:=((pFlagsBuffer^ and $FF) = $3) or ((pFlagsBuffer^ and $FF) = $6);
      if isFolder then break;
      inc(pFlagsBuffer);
    end;
    if isFolder then begin
      Size:=sizeof(Byte)*DataSize;
      GetMem(DataBuffer, Size);
      oeFile.Read(DataBuffer ^, Size);
      pFlagsBuffer:=FlagsBuffer;
      for i:= 0 to Flags-1 do begin
        pDataBuffer:=DataBuffer;
        case (pFlagsBuffer^ and $FF) of
          $2: begin
                inc(pDataBuffer, pFlagsBuffer^ shr 8);
                oe5_FolderInfo^.FolderName:=StrPas(PChar(pDataBuffer));
              end;
          $3: begin
                inc(pDataBuffer, pFlagsBuffer^ shr 8);
                oe5_FolderInfo^.FolderFile:=StrPas(PChar(pDataBuffer));
              end;
          $80: begin
                oe5_FolderInfo^.FolderID:= pFlagsBuffer^ shr 8;
              end;
          $81: begin
                oe5_FolderInfo^.ParentFolderID:= pFlagsBuffer^ shr 8;
              end;
        end;
        inc(pFlagsBuffer);
      end;
      FreeMem(DataBuffer);
      flist.Add(oe5_FolderInfo);
    end else
      Dispose(oe5_FolderInfo);
    FreeMem(FlagsBuffer);
  end;
end;
//==============================================================================

procedure ReadHeaderInfo(Filename:string; position: longint; var str: widestring);
const
  CR = #10;
var
  i: integer;
  HeaderData : THeaderData;
  Flags: integer;
  DataSize: integer;
  Size: integer;
  FlagsBuffer: pointer;
  pFlagsBuffer: ^DWORD;
  DataBuffer: pointer;
  pDataBuffer: ^Byte;
  line1, line2: string;
  x: byte;
  count: integer;
begin
  oeFile:=TFileStream.Create(IncludeTrailingBackslash(Get_OE_Directory)+
      FileName, fmOpenRead);
  oeFile.Seek(position, soFromBeginning);
  oeFile.Read(HeaderData , sizeof(HeaderData));
  if position = HeaderData.position then begin
    Flags:=HeaderData.FlagCount and $FF;
    DataSize:=HeaderData.DataLength-(Flags * sizeof(DWORD));
    Size:=sizeof(DWORD)*Flags;
    GetMem(FlagsBuffer, Size);
    oeFile.Read(FlagsBuffer^, Size);
    Size:=sizeof(Byte)*DataSize;
    GetMem(DataBuffer, Size);
    oeFile.Read(DataBuffer ^, Size);
    pFlagsBuffer:=FlagsBuffer;

    str:=str+'Flags = '+ IntToStr(Flags)+CR;
    str:=Str+CR;
    for i:= 0 to Flags-1 do begin
      str:=str+Format('%2.2x', [pFlagsBuffer^ and $FF])+': '+
         Format('%8.8x', [pFlagsBuffer^ shr 8]);
      case (pFlagsBuffer^ and $FF) of
	$1: str:=str+' ==> Offset to Message Flags';
        $2: str:=str+' ==> Sent Date';
        $3: str:=str+' ==> Filename';
        $5: str:=str+' ==> Subject';
        $4: str:=str+' ==> Message position';
        $6: ;
        $7: str:=str+' ==> Message-ID';
        $8: str:=str+' ==> Subject';
        $9: str:=str+' ==> From:';
        $A: str:=str+' ==> References';
        $B: str:=str+' ==> Newsgroup';
        $D: str:=str+' ==> From:';
        $E: str:=str+' ==> Reply-To:';
        $12: str:=str+' ==> Received Date';
        $13: str:=str+' ==> To:';
        $14: ;
        $1A: str:=str+' ==> Account';
        $1B: str:=str+' ==> Account-ID';
        $80: str:=str+' ==> Message Number';
        $81: str:=str+' ==> Message Status';
        $84: str:=str+' ==> Message Position';
        $91: str:=str+' ==> Message size';
      end;
      str:=str+CR;
      inc(pFlagsBuffer);
    end;
    str:=str+CR+'Size of Header = '+IntToStr(Size)+CR;
    line1:='';
    line2:='';
    count:=0;
    pDataBuffer:=DataBuffer;
    for i := 0 to Size-1 do begin
      x:=pDataBuffer^;
      line1:=line1+Format('%2.2x', [x])+' ';
      if x=0 then
        line2:=line2+'   '
      else
        line2:=line2+' '+chr(x)+' ';
      inc(count);
      if count = 16 then begin
        str:=str+line1+cr+line2+cr+cr;
        line1:='';
        line2:='';
        count:=0;
      end;
      inc(pDataBuffer);
    end;

    FreeMem(DataBuffer);

    FreeMem(FlagsBuffer);
  end;
  oeFile.Free;
end;

//==============================================================================

procedure ReadMessageInfo(position: longint; var flist: TList);
var
  i: integer;
  oe5_MessageInfo: Poe5_MessageInfo;
  HeaderData : THeaderData;
  Flags: integer;
  DataSize: integer;
  Size: integer;
  FlagsBuffer: pointer;
  pFlagsBuffer: ^DWORD;
  DataBuffer: pointer;
  pDataBuffer: ^Byte;
begin
  oeFile.Seek(position, soFromBeginning);
  oeFile.Read(HeaderData , sizeof(HeaderData));
  if position = HeaderData.position then begin
    New(oe5_MessageInfo);
    ZeroMemory(oe5_MessageInfo, sizeof(Toe5_MessageInfo));
    oe5_MessageInfo.HeaderPosition:=position;
    Flags:=HeaderData.FlagCount and $FF;
    DataSize:=HeaderData.DataLength-(Flags * sizeof(DWORD));
    Size:=sizeof(DWORD)*Flags;
    GetMem(FlagsBuffer, Size);
    oeFile.Read(FlagsBuffer^, Size);

    Size:=sizeof(Byte)*DataSize;
    GetMem(DataBuffer, Size);
    oeFile.Read(DataBuffer ^, Size);
    pFlagsBuffer:=FlagsBuffer;

    for i:= 0 to Flags-1 do begin
      pDataBuffer:=DataBuffer;
      case (pFlagsBuffer^ and $FF) of
        $1: begin  // Offset to Message Flags
              inc(pDataBuffer, pFlagsBuffer^ shr 8);
              oe5_MessageInfo^.MsgFlags:=pDataBuffer^;
              inc(pDataBuffer);
              oe5_MessageInfo^.MsgFlags:=oe5_MessageInfo^.MsgFlags+((pDataBuffer^)*256);
              inc(pDataBuffer);
              oe5_MessageInfo^.MsgFlags:=oe5_MessageInfo^.MsgFlags+((pDataBuffer^)*65536);
            end;
        $2: begin  // Sent
              inc(pDataBuffer, pFlagsBuffer^ shr 8);
              oe5_MessageInfo^.Sent:=PFileTime(pDataBuffer)^;
            end;
        $4: begin  // message position
              inc(pDataBuffer, pFlagsBuffer^ shr 8);
              oe5_MessageInfo^.position:=PDWORD(pDataBuffer)^;
            end;
        $12: begin  // Received
              inc(pDataBuffer, pFlagsBuffer^ shr 8);
              oe5_MessageInfo^.Received:=PFileTime(pDataBuffer)^;
            end;
        $7: begin  // message-ID
              inc(pDataBuffer, pFlagsBuffer^ shr 8);
              oe5_MessageInfo^.MessageID:=StrPas(PChar(pDataBuffer));
            end;
        $8: begin  // Subject
              inc(pDataBuffer, pFlagsBuffer^ shr 8);
              oe5_MessageInfo^.Subject:=StrPas(PChar(pDataBuffer));
            end;
        $9: begin  // From_reply
              inc(pDataBuffer, pFlagsBuffer^ shr 8);
              oe5_MessageInfo^.From_Reply:=StrPas(PChar(pDataBuffer));
            end;
        $A: begin  // References
              inc(pDataBuffer, pFlagsBuffer^ shr 8);
              oe5_MessageInfo^.References:=StrPas(PChar(pDataBuffer));
            end;
        $B: begin  // Newsgroup
              inc(pDataBuffer, pFlagsBuffer^ shr 8);
              oe5_MessageInfo^.NewsGroup:=StrPas(PChar(pDataBuffer));
            end;
        $D: begin  // From
              inc(pDataBuffer, pFlagsBuffer^ shr 8);
              oe5_MessageInfo^.From:=StrPas(PChar(pDataBuffer));
            end;
        $E: begin  // Reply to
              inc(pDataBuffer, pFlagsBuffer^ shr 8);
              oe5_MessageInfo^.Reply_To:=StrPas(PChar(pDataBuffer));
            end;
        $13: begin  // Receipt (to:)
              inc(pDataBuffer, pFlagsBuffer^ shr 8);
              oe5_MessageInfo^.Receipt:=StrPas(PChar(pDataBuffer));
            end;
        $1A: begin  // Account
              inc(pDataBuffer, pFlagsBuffer^ shr 8);
              oe5_MessageInfo^.Account:=StrPas(PChar(pDataBuffer));
            end;
        $1B: begin  // Account ID
              inc(pDataBuffer, pFlagsBuffer^ shr 8);
              oe5_MessageInfo^.AccountID:=StrPas(PChar(pDataBuffer));
            end;
        $80: oe5_MessageInfo^.Msg:= pFlagsBuffer^ shr 8; //Message number

        $81: oe5_MessageInfo^.MsgFlags:= pFlagsBuffer^ shr 8;  // Message Flags

        $84: oe5_MessageInfo^.position:= pFlagsBuffer^ shr 8;  // Position

        $91: oe5_MessageInfo^.size:= pFlagsBuffer^ shr 8;  // SIZE??

      end;
      inc(pFlagsBuffer);
    end;

    FreeMem(DataBuffer);
    flist.Add(oe5_MessageInfo);

    FreeMem(FlagsBuffer);
  end;
end;

procedure ReadIndex(position: longint; var flist: TList; folders: boolean);
var
  iheader: Toe5_IndexHeader;
  icount: integer;
  pIndexItem: Poe5_IndexItem;
  i: integer;
  Size: Integer;
  Buffer: PChar;
begin
  if oeFile <> nil then begin
    oeFile.Seek(position, soFromBeginning);
    oeFile.Read(iheader, sizeof(iheader));
    if iheader.FilePos <> position then begin
      // break;
    end else begin
      tmpList.Add(Pointer(position));

      if iheader.NextIndex > 0 then
        if tmpList.IndexOf(Pointer(iheader.NextIndex))=-1 then
          ReadIndex(iheader.NextIndex, flist, folders);

      if iheader.PrevIndex > 0 then
        if tmpList.IndexOf(Pointer(iheader.PrevIndex))=-1 then
          ReadIndex(iheader.PrevIndex, flist, folders);

      icount:=iheader.Count shr 8;
      if icount > 0 then begin
        Size:=sizeof(Toe5_IndexItem)*icount;
        GetMem(Buffer, Size);
        oeFile.Seek(iheader.FilePos + sizeof(iheader), soFromBeginning);
        oeFile.Read(Buffer^, Size);
        pIndexItem:=Poe5_IndexItem(buffer);
        for i:=0 to icount-1 do begin
          if pIndexItem^.HeaderPos > 0 then begin
            if folders then
               // get folder information
               ReadFolderInfo(pIndexItem^.HeaderPos, flist)
            else
               // get message information
               ReadMessageInfo(pIndexItem^.HeaderPos, flist);
          end;
          if pIndexItem^.ChildIndex > 0 then
            if tmpList.IndexOf(Pointer(pIndexItem^.ChildIndex))=-1 then
              ReadIndex(pIndexItem^.ChildIndex, flist, folders);
          inc(pIndexItem);
        end;
        FreeMem(Buffer);
      end;
    end;
  end;
end;


//===================================================================================
procedure Read_OE_Message(Filename:string; position: longint; var msg: widestring);
var
  oe5_MsgItem: Toe5_MsgItem;
begin
  if position>0 then begin
    oeFile:=TFileStream.Create(IncludeTrailingBackslash(Get_OE_Directory)+
      FileName, fmOpenRead);
    GetFileInfo;
    msg:='';
    if finfo.IndexItemsCount > 0 then begin
      oeFile.Seek(position, soFromBeginning);
      While True do begin
        oeFile.Read(oe5_MsgItem, sizeof(oe5_MsgItem));
        if oe5_MsgItem.FilePos <> position then break;
        msg:=msg + copy(oe5_MsgItem.MsgContent, 1, oe5_MsgItem.ItemSize);
        position:=oe5_MsgItem.NextItem;
        if position = 0 then break;
        oeFile.Seek(position, soFromBeginning);
      end;
    end;
    oeFile.Free;
  end;
end;

//===================================================================================

procedure Read_OE_File(const FileName: string; var flist: TList);
var
  position: longint;
begin
  if flist <> nil then begin
     oeFile:=TFileStream.Create(IncludeTrailingBackslash(Get_OE_Directory)+
       FileName, fmOpenRead);
     GetFileInfo;
     if finfo.IndexItemsCount > 0 then begin
       tmpList:=TList.Create;
       oeFile.Seek( $30, soFromBeginning);
       oeFile.Read(position, sizeof(position));
       ReadIndex(position, flist, FileName='folders.dbx');
       tmpList.Free;
     end;
     oeFile.Free;
  end;
end;

end.
