Programmer » DBX Utils

Outlook Express example code for getting a list of profiles and folders

This code (c) 2002-2006 Lucian Wischik. The code is free and anyone can do with it whatever they like, including incorporating it in commercial products.

Please leave comments and bugfixes at the bottom of this page.


Overview

Outlook Express stores its mailfolders and newsfolders in DBX file format. Arne Schloh has figured out the important bits of the DBX file format. He has written documentation and code on reading DBX files, available at his web site http://oedbx.aroh.de. His code is exceptionally clear and well-written.

Once you have his code, all you need is to figure out which identities are on the computer, and where they're stored. This web page contains code to help with that.

The way it works is that Outlook Express may have several 'Identities'. You manage identities on the File | Identities menu. Furthermore, each identity may serve several different email accounts, managed on the Tools | Accounts menu. Now, each identity stores all of its folders in a single directory somewhere on the hard disk. In this directory, there is a special file called FOLDERS.DBX which contains a list of all the mailfolders. And each mailfolder is stored in its own DBX file.

The code on this page reads from the registry to find out where those folders are located.


Using the dbx_utils library

This program lists the identities on the machine. Then lists the folders in the first identity. Then lists the messages in the first folder. And it obtains the text of each message.

int main()
{
  // Load the list of identities on this machine
  dbx_EnsureStores();
  if (dbx_Stores.size()==0) {printf("No Outlook Express identities\n"); return 0;}
  printf("Identity: user=%s version=%s path=%s\n", dbx_Stores[0].name.c_str(), dbx_Stores[0].ver.c_str(), dbx_Stores[0].path.c_str());

  // Load the list of folders in the first identity
  string ffn; // we'll remember the first normal folder we find, to enumerate its messages
  dbx_EnsureFolders(dbx_Stores[0].name, dbx_Stores[0].ver);
  printf("%i folders\n",dbx_Folders.size());
  for (vector<TDbxFolderInfo>::const_iterator i=dbx_Folders.begin(); i!=dbx_Folders.end(); i++)
  { if (i->type==dftMail && ffn.length()==0) ffn=i->fpath+"\\"+i->fn;
    printf("%s\n",i->full.c_str());
  }
  if (dbx_Folders.size()==0) return 0;

  // Work through all the messages in the first folder
  printf("\nFolder %s:\n",ffn.c_str());
  imemfile inm(ffn.c_str());
  if (!inm.okay()) return 0;
  DbxFileHeader fileHeader(inm);
  int4 address = fileHeader.GetValue(fhTreeRootNodePtr);
  int4 entries = fileHeader.GetValue(fhEntries);
  if (address==0 || entries==0) return 0;
  //
  DbxTree tree(inm,address,entries); // Read in the tree with pointers to each message
  for(int4 j=0; j<entries; ++j) // cycle through messages
  { address = tree.GetValue(j);
    DbxMessageInfo messageInfo(inm,address);
    int4 messageAddress = messageInfo.GetValue(miiMessageAddress);
    // Get message headers and text
    int4 length;
    char *cto=(char*)messageInfo.GetValue(19,&length);
    char *cfrom=(char*)messageInfo.GetValue(13,&length);
    char *csub=(char*)messageInfo.GetValue(5,&length);
    char *ctext=0; unsigned int ctextlen=0;
    if (messageAddress!=0)
    { DbxMessage message(inm, messageAddress);
      ctext=message.GetText(); ctextlen=message.GetLength();
    }
    //
    printf("%s from:%s to:%s\n",csub,cfrom,cto);
  }

  inm.close();

  return 0;
}

To obtain the creating/sending time of a message, assuming DbxMessageInfo messageinfo,

FILETIME *ft=(FILETIME*)messageInfo.GetValue(3,&length);
SYSTEMTIME st; if (ft!=0) FileTimeToSystemTime(ft,&st);

Header-file 'dbx_utils.h'

#ifndef dbx_utilsH
#define dbx_utilsH
#include <string>
#include <list>
#include <vector>
using namespace std;

// dbx_EnsureStores: call this function to build up the global
// variable 'dbx_Stores', which is a vector containing all the
// Outlook Express identities on this machine.
void dbx_EnsureStores();

// dbx_EnsureFolders: call this to build up the global
// variable 'dbx_Folders', a vector containing all the
// folders for a given OE idententy
void dbx_EnsureFolders(const string name,const string ver);

// These functions test whether a file is a valid dbx file
bool IsValidDbxFolderFile(const string fn);
bool IsValidDbxMessageFile(const string fn);



enum TDbxFolderType {dftStructure, dftInbox, dftOutbox, dftSent, dftDeleted, dftDrafts, dftMail};

typedef struct
{ string id;   // e.g. {CAA16-...}
  string name; // the username, e.g. "Main Identity"
  string ver;  // the version of Outlook Express, e.g. "5.0"
  string path; // the path to the store root, e.g. "c:\\store"
} TDbxStore;


typedef struct
{ string name;    // e.g. "Joseph"
  string path;    // e.g. "Friends"
  string account; // e.g. "Cambridge email";
  string full;    // e.g. "Cambridge Email\\Friends\\Joseph"
  string fn;      // e.g. "cambridge email - inbox.dbx"
  string fpath;   // e.g. "c:\\localmessagestore"
  unsigned int depth; // e.g. 2 (Cam.Email=0, Friends=1, Joseph=2)
  TDbxFolderType type; // see the above enumeration
  bool might_contain_offlines;  // just a hint
  DWORD filesize; // this gets set at a later time, during processing
} TDbxFolderInfo;
bool operator <(const TDbxFolderInfo &a,const TDbxFolderInfo &b);


extern vector<TDbxStore> dbx_Stores;
extern vector<TDbxFolderInfo> dbx_Folders;




// The following dbx-reading code (and in the accompanying oe_utils.cpp)
// is written by Arne Schloh, available at his web page http://oedbx.aroh.de/
// Lucian has modified it to use memory-mapped files

typedef unsigned char   int1;
typedef unsigned short  int2;
typedef unsigned long   int4;

class imemfile
{ public:
  const char *buf; DWORD size;
  imemfile(const char *fn);
  ~imemfile();
  void close();
  void seekg(unsigned int apos);
  void read(char *dst, unsigned int size);
  bool okay();
protected:
  unsigned int pos;
  bool isokay;
  HANDLE hf;
  HANDLE hmap;
};



class DbxException
{ public:
    DbxException(const std::string &text) { Error = text; }
    const char * what() const { return Error.c_str(); }
  private:
    std::string Error;
};


class DbxTree
{ public:
    DbxTree(imemfile &inm, int4 address, int4 values);
    ~DbxTree();
    int4 GetValue(int4 index) const;
  private:
    void readValues(imemfile &inm, int4 parent, int4 address, int4 position, int4 values);
    int4 Address; int4 Values, *Array;
};

const int4 FileHeaderSize    = 0x24bc;
const int4 FileHeaderEntries = FileHeaderSize>>2;
const int4 fhFileInfoLength       = 0x07;
const int4 fhFirstFolderListNode  = 0x1b, fhLastFolderListNode  = 0x1c;
const int4 fhMessageConditionsPtr = 0x22, fhFolderConditionsPtr = 0x23;
const int4 fhEntries              = 0x31, fhTreeRootNodePtr     = 0x39 ;

class DbxFileHeader
{ public:
    DbxFileHeader(imemfile &inm);
    int4 GetValue(int4 index) const{return Buffer[index];    }
    bool isFolders() const { return (Buffer && (Buffer[1]==0x6f74fdc6)); }
  private:
    void readFileHeader(imemfile &inm); // this function is called from the constructor
    int4 *Buffer;                       // stores the data
};

class DbxFileInfo
{ public:
    DbxFileInfo(imemfile &inm, int4 address, int4 length);
    ~DbxFileInfo();
    const char * GetFolderName()   const;
    std::string  GetFileInfoTime() const;
  private:
    void init();
    void readFileInfo(imemfile &inm);
    //data
    int4 Address, Length; int1 *buffer;
};


const int1 DbxFolderListSize    = 0x68;
const int1 DbxFolderListEntries = DbxFolderListSize >> 2;
const int1 flFolderInfo         = 0x15;
const int1 flNextFolderListNode = 0x17, flPreviousFolderListNode = 0x18;



class DbxMessage
{ public:
    DbxMessage(imemfile &inm, int4 address);
    ~DbxMessage();
    int4 GetLength() const { return Length; }
    char * GetText() const { return Text; } 
    void Analyze(int4 & headerLines, int4 & bodyLines) const;
  private:
    void init();
    void readMessageText(imemfile &inm);
    int4 Address, Length; char * Text; // address, length, text of message
};


// The data types I found stored in the indexed info objects
enum IndexedInfoDataType { dtNone=0, dtInt4=1, dtDateTime=2, dtString=4, dtData=8 };
//
const int1 MaxIndex = 0x7F;

class DbxIndexedInfo
{ public:
    DbxIndexedInfo(imemfile &inm, int4 address);
    ~DbxIndexedInfo();
    int4 GetAddress()    const { return Address;    }
    int4 GetBodyLength() const { return BodyLength; }
    int1 GetEntries()    const { return Entries;    }
    int1 GetCounter()    const { return Counter;    }
    int4 GetIndexes()    const { return Indexes;    }
    virtual const char * GetIndexText(int1 index) const {return 0;}
    virtual IndexedInfoDataType GetIndexDataType(int1 index) const {return dtNone;}
    int1 * GetValue(int1 index, int4 * length) const;
    int4 GetValue(int1 index)                const;
  private:
    void init();
    void readIndexedInfo(imemfile &inm);
    //
    void SetIndex(int1 index, int1 * begin, int4 length=0);
    void SetEnd(int1 index, int1 * end);
    // message info data
    int4 Address, BodyLength;
    int2 ObjectLength;
    int1 Entries, Counter, * Buffer;
    int4 Indexes;            // Bit field for the used indexes (MaxIndexe bits)
    int1 * Begin[MaxIndex];
    int4 Length[MaxIndex];
};


const int1 fiiIndex = 0x00, fiiParentIndex = 0x01, fiiName = 0x02, fiiFlags = 0x06;

class DbxFolderInfo : public DbxIndexedInfo
{ public:
    DbxFolderInfo(imemfile &inm, int4 address) : DbxIndexedInfo(inm, address) { }
    const char * GetIndexText(int1 index) const;
    IndexedInfoDataType GetIndexDataType(int1 index) const;
};




const int1 miiIndex = 0x00, miiFlags = 0x01, miiMessageAddress = 0x04;

class DbxMessageInfo : public DbxIndexedInfo
{ public:
    DbxMessageInfo(imemfile &inm, int4 address) : DbxIndexedInfo(inm, address) { }
    const char *        GetIndexText    (int1 index) const;
    IndexedInfoDataType GetIndexDataType(int1 index) const;
};



#endif

Source-code 'dbx_utils.cpp'

#include <windows.h>
#include <string>
#include <list>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
#pragma hdrstop // precompiled headers stop here
#include "dbx_utils.h"

vector<TDbxStore> dbx_Stores;
bool dbx_DoneStores; // have we yet called EnsureStores?


// Here are some boring obvious utility functions

list<string> RegQuerySubkeys(HKEY key)
{ list<string> ss;
  LONG res; DWORD index=0; char buf[256]; FILETIME ft;
  while (true)
  { DWORD size=256;
    res = RegEnumKeyEx(key,index,buf,&size,NULL,NULL,NULL,&ft);
    if (res!=ERROR_SUCCESS) return ss;
    ss.push_back(buf);
    index++;
  }
}

string RegQueryString(HKEY key,const string name)
{ DWORD type, size=0; LONG res;
  res = RegQueryValueEx(key,name.c_str(),NULL,&type,NULL,&size);
  if (res!=ERROR_SUCCESS) return "";
  if (type!=REG_SZ && type!=REG_EXPAND_SZ) return "";
  char *c=new char[size+1];
  res = RegQueryValueEx(key,name.c_str(),NULL,&type,(BYTE*)c,&size);
  if (res!=ERROR_SUCCESS) {delete[] c; return "";}
  if (type==REG_EXPAND_SZ)
  { char dummy[1]; DWORD esize=ExpandEnvironmentStrings(c,dummy,0);
    if (esize!=0)
    { char *d=new char[esize+1];
      ExpandEnvironmentStrings(c,d,esize+1);
      delete[] c; c=d;
    }
  }
  string r(c);
  delete[] c;
  return r;
}

string RemoveTrailingSlash(const string s)
{ if (s[s.length()-1]=='\\' || s[s.length()-1]=='/') return s.substr(0,s.length()-1);
  else return s;
}

string StringLower(string s)
{ for (string::size_type i=0; i<s.length(); i++) s[i] = (char)tolower(s[i]);
  return s;
}

bool FileExists(const string fn)
{ DWORD res = GetFileAttributes(fn.c_str());
  return (res != 0xFFFFFFFF);
}





// And here begins the main code of dbx_utils:


void dbx_EnsureStores()
{ dbx_Stores.clear();

  // We'll build up a list of things in
  // HKEY_CURRENT_USER\Identities\<id>\Software\Microsoft\Outlook Express\<ver>
  // Except we start the default identity first, and the rest in order;
  // and we store the versions in descending order.
  // So: MainIdentity\5.0
  //     MainIdentity\4.0
  //     SecondIdentity\5.0
  //     SecondIdentity\4.0
  // Incidentally, if multiple different versions all point to the same store
  // folder, then we collapse them, and only display the most recent version.
  HKEY key; LONG res;
  res = RegOpenKeyEx(HKEY_CURRENT_USER,"Identities",0,KEY_READ,&key);
  if (res!=ERROR_SUCCESS) return;
  list<string> ids = RegQuerySubkeys(key);
  string defid = RegQueryString(key,"Default User ID");
  RegCloseKey(key);
  if (ids.size()==0) return;
  //
  for (int pass=0; pass<2; pass++)
  { // in pass=0 we add only the default id.
    // in pass=1 we add all other ids
    for (list<string>::iterator i=ids.begin(); i!=ids.end(); i++)
    { string id = *i; string name; string s; list<string> vers; bool okay = true;
      if (okay)
      { if (pass==0 && id!=defid) okay=false;
        else if (pass==1 && id==defid) okay=false;
      }
      if (okay)
      { res = RegOpenKeyEx(HKEY_CURRENT_USER,("Identities\\"+id).c_str(),0,KEY_READ,&key);
        if (res!=ERROR_SUCCESS) okay=false;
        else {name = RegQueryString(key,"Username");  RegCloseKey(key);}
        if (name=="") okay=false;
      }
      if (okay)
      { s = "Identities\\"+id+"\\Software\\Microsoft\\Outlook Express";
        res = RegOpenKeyEx(HKEY_CURRENT_USER,s.c_str(),0,KEY_READ,&key);
        if (res!=ERROR_SUCCESS) okay=false;
        else {vers = RegQuerySubkeys(key); RegCloseKey(key);}
      }
      for (list<string>::reverse_iterator j=vers.rbegin(); okay && j!=vers.rend(); j++)
      { string ver = *j; string path="";
        res = RegOpenKeyEx(HKEY_CURRENT_USER,(s+"\\"+ver).c_str(),0,KEY_READ,&key);
        if (res==ERROR_SUCCESS) {path = RemoveTrailingSlash(RegQueryString(key,"Store Root")); RegCloseKey(key);}
        if (path!="")
        { // If different versions of OE are installed, but all point to the same location,
          // then we won't add them
          bool alreadythere=false;
          if (dbx_Stores.size()>0)
          { TDbxStore &prevdbx = dbx_Stores.back();
            if (prevdbx.id==id && StringLower(prevdbx.path)==StringLower(path)) alreadythere=true;
          }
          if (!alreadythere && FileExists(path+"\\Folders.dbx"))
          { TDbxStore dbxs;
            dbxs.id = id;
            dbxs.name = name;
            dbxs.ver = ver;
            dbxs.path = path;
            dbx_Stores.push_back(dbxs);
          }
        }
      }
    }
  }
  dbx_DoneStores=true;
}


vector<TDbxFolderInfo> dbx_Folders;
string dbx_DoneFoldersName, dbx_DoneFoldersVer;
// This 'TDbxFolderSorting' is used to sort the folders we retrieve
// into an order approximating that of Outlook Express
class TDbxFolderSorting
{ public:
  unsigned int index;
  bool isexplicitpos; unsigned int position;
  TDbxFolderInfo info;
  vector<TDbxFolderSorting*> c;
  TDbxFolderSorting *parent;
};
bool comp(const TDbxFolderSorting *a,const TDbxFolderSorting *b)
{ if (b==NULL) return true; else if (a==NULL) return false;
  if (a->isexplicitpos && !b->isexplicitpos) return true; else if (!a->isexplicitpos && b->isexplicitpos) return false;
  if (a->position<b->position) return true; else if (a->position>b->position) return false;
  return ((void*)a<(void*)b);
}



// RecEnsureFolders -- this function recursively deletes the
// FolderSorting, and at the same time copies bits into the dbx_Folders
// global vector
void RecEnsureFolders(vector<TDbxFolderSorting*> &s,bool addit)
{ sort(s.begin(), s.end(), comp);
  for (vector<TDbxFolderSorting*>::iterator i=s.begin(); i!=s.end(); i++)
  { TDbxFolderSorting *f = *i;
    bool laddit=addit;
    if (f->info.type==dftOutbox || f->info.type==dftDeleted || f->info.type==dftDrafts) laddit=false;
    if (laddit) dbx_Folders.push_back(f->info);
    RecEnsureFolders(f->c,laddit); // we need to call it recursively, even if !addit, since that will also delete stuff
    delete f;
  }
}


// dbx_EnsureFolders: first builds up a FolderSorting tree. Then calls
// the above function to copy it into the flat global vector.
//
void dbx_EnsureFolders(const string name,const string ver)
{ string testn=name, testv=ver;
  if (testn=="") testn="_"; if (testv=="") testv="_";
  if (dbx_DoneFoldersName==testn && dbx_DoneFoldersVer==testv) return;
  dbx_DoneFoldersName=testn; dbx_DoneFoldersVer=testv;
  dbx_Folders.clear();
  if (!dbx_DoneStores) dbx_EnsureStores();
  //
  // Now let's find the path for this name/ver
  string path="";
  for (vector<TDbxStore>::iterator i=dbx_Stores.begin(); i!=dbx_Stores.end(); i++)
  { if (name=="" && ver=="") {path=i->path; break;}
    else if (name==i->name && ver==i->ver) {path=i->path; break;}
  }
  if (path=="") return;
  string ffn = path+"\\Folders.dbx";
  if (!FileExists(ffn)) return;

  // Folders.dbx is an index of the folders in this profile.
  // It is a flat list. Each entry has:
  // id, parent-id, fn, name, regstring, subindex, value, id0b
  // The id/parent-id determine the structure of the tree
  // name is the folder name, and fn is the dbx filename (as a sub of path)
  // For top-level ones, we look at regstring to determine the index:
  // "LocalStore" is 1, some others also have numbers, some are empty (in which case we use index in the file)
  // For sub-level ones, some have subindex set, some are empty (in which case we use index in the file)
  // If value is 1, or if id0b is 0x2F0000, then this might be an offlineable folder.
  vector<TDbxFolderSorting*> folders;
  map<unsigned int,TDbxFolderSorting*> i2f;
  //
  // I'm using my specialised memory-mapped class here.
  // Arne's code was written to use an istream instead, so you'll
  // have to either change this to an istream, or change Arne's code.
  imemfile inm(ffn.c_str());
  if (!inm.okay()) return;
  DbxFileHeader fileHeader(inm);

  const int4 treeNumber=2; // This third tree stores pointers to all folder informations
  int4 address = fileHeader.GetValue(fhTreeRootNodePtr+treeNumber);
  int4 entries = fileHeader.GetValue(fhEntries        +treeNumber);
  if(address && entries)
  { DbxTree tree(inm,address,entries);  // Read in the tree with all pointers
    for(int4 filepos=0; filepos<entries; ++filepos)
    { int1 *ptr; int4 length;
      address = tree.GetValue(filepos);   // Get the address of the folder info
      DbxFolderInfo folderInfo(inm,address);  // Read in the folder info
      unsigned int index = folderInfo.GetValue(0);
      unsigned int pindex = folderInfo.GetValue(1);
      string fn; ptr=folderInfo.GetValue(3,&length); if (ptr!=0 && length>0) fn=string((char*)ptr,length-1);
      string name; ptr=folderInfo.GetValue(2,&length); if (ptr!=0 && length>0) name=string((char*)ptr,length-1);
      string reg; ptr=folderInfo.GetValue(5,&length); if (ptr!=0 && length>0) reg=string((char*)ptr,length-1);
      unsigned int pos = folderInfo.GetValue(9);
      unsigned int value = folderInfo.GetValue(10);
      unsigned int id0b = folderInfo.GetValue(11);
      unsigned int num_to_download = folderInfo.GetValue(18);
      //
      bool isexplicitpos=false;
      if (strncmp(reg.c_str(),"LocalStore",10)==0) reg="1";
      if (reg!="" && pindex==0)
      { bool isnum=true; for (unsigned int i=0; i<reg.length(); i++) {if (reg[i]<'0' || reg[i]>'9') isnum=false;}
        if (isnum) sscanf(reg.c_str(),"%lu",&pos);
      }
      if (pos!=0) isexplicitpos=true; else pos=filepos;
      TDbxFolderSorting *parent=0;
      if (pindex!=0)
      { map<unsigned int,TDbxFolderSorting*>::const_iterator i = i2f.find(pindex);
        if (i!=i2f.end()) parent=i->second;
      }
      bool isnews = (value==0);
      //
      if (!isnews)
      { TDbxFolderSorting *f = new TDbxFolderSorting;
        f->index = index;
        f->isexplicitpos = isexplicitpos;
        f->position = pos;
        f->info.name = name;
        f->parent = parent;
        if (parent==0 || parent->parent==0) f->info.path="";
        else if (parent->info.path=="") f->info.path=parent->info.name;
        else f->info.path=parent->info.path+"\\"+parent->info.name;
        if (parent==0) f->info.account="";
        else if (parent->parent==0) f->info.account=parent->info.name;
        else f->info.account=parent->info.account;
        string sf="";
        if (f->info.account!="") sf += f->info.account+"\\";
        if (f->info.path!="") sf += f->info.path+"\\";
        sf += f->info.name;
        f->info.full = sf;
        f->info.fn = fn;
        f->info.fpath = path;
        if (parent==0) f->info.depth=0; else f->info.depth=parent->info.depth+1;
        if (parent==0) f->info.type=dftStructure;
        else if (!f->isexplicitpos) f->info.type=dftMail;
        else if (pos==1) f->info.type=dftInbox;
        else if (pos==2) f->info.type=dftOutbox;
        else if (pos==3) f->info.type=dftSent;
        else if (pos==4) f->info.type=dftDeleted;
        else if (pos==5) f->info.type=dftDrafts;
        else f->info.type=dftMail;
        f->info.might_contain_offlines = (value==0 || value==1 || id0b==0x2F || num_to_download>0);
        // That 'num to download' isn't reliable.
        //
        if (parent==0) folders.push_back(f);
        else parent->c.push_back(f);
        i2f.insert(pair<unsigned int,TDbxFolderSorting*>(index,f));
      }
    }
  }
  inm.close();

  // Now sort them and save copies into the main list ane delete them
  RecEnsureFolders(folders,true);
}

imemfile::imemfile(const char *fn)
{ buf=0; isokay=false; pos=0; hf=0; hmap=0; size=0; DWORD high=0;
  if (fn!=0) hf = CreateFile(fn,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
  if (hf==INVALID_HANDLE_VALUE) isokay=false;
  else size=GetFileSize(hf,&high);
  if (high!=0 || size==0) {CloseHandle(hf);hf=0; isokay=false;}
  else hmap=CreateFileMapping(hf,NULL,PAGE_READONLY,0,0,NULL);
  if (hmap==NULL) {CloseHandle(hf); hf=0; isokay=false;}
  else buf=(const char*)MapViewOfFile(hmap,FILE_MAP_READ,0,0,0);
  if (buf==0) {CloseHandle(hmap); hmap=0; CloseHandle(hf); hf=0; isokay=false;}
  else isokay=true;
}

imemfile::~imemfile()
{ close();
}

void imemfile::seekg(unsigned int apos)
{ if (buf==0 || apos>size) isokay=false; else pos=apos;
}

void imemfile::read(char *dst,unsigned int rsize)
{ if (buf==0 || pos+rsize>size) isokay=false;
  else {memcpy(dst,buf+pos,rsize); pos+=rsize;}
}

bool imemfile::okay()
{ return isokay;
}

void imemfile::close()
{ if (buf!=0) UnmapViewOfFile(buf); buf=0;
  if (hmap!=0) CloseHandle(hmap); hmap=0;
  if (hf!=0) CloseHandle(hf); hf=0;
  size=0; pos=0; isokay=false;
}


// The following is Arne's code:




const int4 TreeNodeSize = 0x27c;

DbxTree::DbxTree(imemfile &inm, int4 address, int4 values)
{ Address = address;
  Values  = values;

  Array = new int4[Values];
  if(!Array) throw DbxException("Error allocating memory !");
  try { readValues(inm, 0, Address, 0, Values); }
  catch(const DbxException &)
    { delete[] Array;
      throw;
    }
}

DbxTree::~DbxTree()
{ delete[] Array; }

void DbxTree::readValues(imemfile &inm, int4 parent,   int4 address,
                                       int4 position, int4 values)
{ int4 buffer[TreeNodeSize>>2], N = 0;

  if(position+values>Values) throw DbxException("To many values to read !");

  inm.seekg(address);
  inm.read((char *)buffer, TreeNodeSize);
  if(!inm.okay()) throw DbxException("Error reading node from input stream !");

  if(buffer[0]!=address) throw DbxException("Wrong object marker !");
  if(buffer[3]!=parent)  throw DbxException("Wrong parent !");

  int1 /* id = buffer[4]&0xff, */ entries = (int1)((buffer[4] >> 8)&0xff);
  if(entries>0x33) throw DbxException("Wrong value for entries !");

  if(buffer[2]!=0) { readValues(inm, address, buffer[2], position, buffer[5]);
                     N += buffer[5];
                   }
  for(int1 i=0;i<entries;++i)
    { int4 * pos = buffer + 6 + i * 3;
      if(pos[0]!=0)
        { int4 value = position + (++N);
          if(value>Values) throw DbxException("To many values !");
          Array[value-1] = pos[0];
        }
      if(pos[1]!=0) { readValues(inm, address, pos[1], position+N, pos[2]);
                      N+=pos[2];
                    }
    }
  if(N!=values) throw DbxException("Wrong number of values found!");
}

int4 DbxTree::GetValue(int4 index) const
{ if(index<Values) return Array[index];
  throw DbxException("Wrong index !");
}









DbxFileHeader::DbxFileHeader(imemfile &inm) { readFileHeader(inm); }

void DbxFileHeader::readFileHeader(imemfile &inm)
{ Buffer=(int4*)inm.buf;
}









DbxFileInfo::DbxFileInfo(imemfile &inm, int4 address, int4 length)
{ init();
  Address = address;
  Length  = length;
  readFileInfo(inm);
}

DbxFileInfo::~DbxFileInfo()
{
}

void DbxFileInfo::init()
{ Address = Length = 0;
  buffer  = 0;
}

void DbxFileInfo::readFileInfo(imemfile &inm)
{ buffer = (int1*)(inm.buf+Address);
}

const char * DbxFileInfo::GetFolderName() const
{ if(buffer && Length==0x618) return (char *)(buffer+0x105);
  return 0;
}
















DbxMessage::DbxMessage(imemfile &inm, int4 address)
{ Address  = address;
  init();

  try { readMessageText(inm); }
  catch(const DbxException &)
     { delete[] Text;
       throw;
     }
}

DbxMessage::~DbxMessage()
{ delete[] Text; }

void DbxMessage::init()
{ Length     = 0;
  Text       = 0;
}

void DbxMessage::readMessageText(imemfile &inm)
{ int4 address = Address, header[4];
  char * pos;

  while(address)
    { inm.seekg(address);
      inm.read((char *)header, 0x10);
      if(!inm.okay()) throw DbxException("Error reading from input stream !");
      if(address!=header[0]) throw DbxException("Wrong object marker !");
      Length += header[2];
      address = header[3];
    }
  if(Length==0) throw DbxException("No text found !");
  pos = Text = new char[Length+1];    // +1 to terminate the text with 0x00
  address = Address;
  while(address)
    { inm.seekg(address);
      inm.read((char *)header, 0x10);
      inm.read(pos, header[2]);
      if(!inm.okay()) throw DbxException("Error reading from input stream !");
      address = header[3];
      pos += header[2];
    }
  *pos = 0;                           // terminate the text with 0x00
}




void DbxMessage::Analyze(int4 & headerLines, int4 & bodyLines) const
{ headerLines = bodyLines = 0;

  if(Length==0) return;

  bool isHeader = true;
  char * pos = Text, * to = Text + Length;
  headerLines = 1;
  while(pos!=to)
    { if(*pos == 0x0a)
        { if(isHeader) if(pos-Text>=2) if(*(pos-2)==0x0a) isHeader = false;
          if(isHeader) ++headerLines;
                  else ++bodyLines;
        }
      ++pos;
    }
}







DbxIndexedInfo::DbxIndexedInfo(imemfile &inm, int4 address)
{ init();
  Address  = address;
  readIndexedInfo(inm);
}

DbxIndexedInfo::~DbxIndexedInfo()
{
}

void DbxIndexedInfo::init()
{ BodyLength   = 0;
  ObjectLength = 0;
  Entries      = Counter = 0;
  Buffer       = 0;
  Indexes      = 0;
  for(int1 i=0;i<MaxIndex;++i) { Begin[i] = 0; Length[i] = 0; }
}

void DbxIndexedInfo::readIndexedInfo(imemfile &inm)
{ int4 *temp = (int4*)(inm.buf+Address);

  if(Address != temp[0]) throw DbxException("Wrong object marker !");

  BodyLength   = temp[1];
  ObjectLength = *( (int2 *)(temp+2)   );       //temp[2]& 0xffff;
  Entries      = *(((int1 *)(temp+2))+2);       //(temp[2]>>16) & 0xff;
  Counter      = *(((int1 *)(temp+2))+3);       //(temp[2]>>24) & 0xff;

  Buffer = (int1*)(inm.buf+Address+12);

  bool isIndirect = false;
  int1 lastIndirect = 0;
  int1 * data   = Buffer + (Entries<<2);

  for(int1 i = 0; i<Entries; ++i)
     { int4 value    = ((int4 *)Buffer)[i];
       bool isDirect = (value & 0x80)!=0;            // Data stored direct
       int1 index    = (int1) (value & 0x7f);   // Low byte is the index
       value >>= 8 ;                            // Value the rest
       if(index>=MaxIndex) throw DbxException("Index to big !");
       if(isDirect) SetIndex(index, Buffer+(i<<2)+1, 3);
             else { SetIndex(index, data+value);
                    if(isIndirect) SetEnd(lastIndirect, data+value);
                    isIndirect   = true;
                    lastIndirect = index;
                  }
       Indexes |= 1<<index;
     }
  if(isIndirect)SetEnd(lastIndirect, Buffer+BodyLength);
}

void DbxIndexedInfo::SetIndex(int1 index, int1 * begin, int4 length)
{ if(index<MaxIndex) { Begin[index] = begin; Length[index] = length; } }

void DbxIndexedInfo::SetEnd(int1 index, int1 * end)
{ if(index<MaxIndex) Length[index] = (int4)(end - Begin[index]); }

int1 * DbxIndexedInfo::GetValue(int1 index, int4 * length) const
{ if(index>=MaxIndex) throw DbxException("Index to big !");
  *length = Length[index];
  return Begin[index];
}

int4 DbxIndexedInfo::GetValue(int1 index) const
{ if(index>=MaxIndex) throw DbxException("Index to big !");
  int4 length = Length[index], value = 0;
  int1 * data = Begin[index];
  if(data) { value = *((int4 *)data);                  // length>4 ignored
             if(length<4) value &= (1<<(length<<3))-1;
           }
  return value;
}






const char * DbxFolderInfo::GetIndexText(int1 index) const
{ const char * text[MaxIndex] = {
     "folder index"                  , "index of the parent folder"              ,
     "folder name (newsgroup name)"  , "dbx file name"                           ,
     "id 04"                         , "registry key of the account"             ,
     "flags"                         , "messages in the folder"                  ,
     "unread messages in the folder" , "index for subfolders of 'local folders'" ,
     "local folder value"            , "id 0b"                                   ,
     "id 0c"                         , "max message index on server"             ,
     "min message index on server"   , "id 0f"                                   ,
     "max message index local"       , "min message index local"                 ,
     "messages to download"          , "id 13"                                   ,
     "id 14"                         , "id 15"                                   ,
     "id 16"                         , "id 17"                                   ,
     "id 18"                         , "id 19"                                   ,
     "id 1a"                         , "id 1b"                                   ,
     "watched messages"              , "id 1d"                                   ,
     "id 1e"                         , "id 1f"                                    };
  if(index<MaxIndex) return text[index];
  throw DbxException("Wrong index !");
}

IndexedInfoDataType DbxFolderInfo::GetIndexDataType(int1 index) const
{ IndexedInfoDataType dataType[MaxIndex] = {
      dtInt4 , dtInt4 , dtString,dtString,dtInt4 , dtString,dtInt4 , dtInt4 ,
      dtInt4 , dtInt4 , dtInt4 , dtNone , dtNone , dtInt4 , dtInt4 , dtInt4 ,
      dtInt4 , dtInt4 , dtInt4 , dtData , dtNone , dtData , dtNone , dtNone ,
      dtNone , dtNone , dtInt4 , dtNone , dtInt4 , dtNone , dtNone , dtNone  };
  if(index<MaxIndex) return dataType[index];
  throw DbxException("Wrong index !");
}






const char * DbxMessageInfo::GetIndexText(int1 index) const
{ const char * text[MaxIndex] = {
     "message index"                , "flags"                          ,
     "time message created/send"    , "body lines"                     ,
     "message address"              , "original subject"               ,
     "time message saved"           , "message id"                     ,
     "subject"                      , "sender eMail address and name"  ,
     "answered to message id"       , "server/newsgroup/message number",
     "server"                       , "sender name"                    ,
     "sender eMail address"         , "id 0f"                          ,
     "message priority"             , "message text length"            ,
     "time message created/received", "receiver name"                  ,
     "receiver eMail address"       , "id 15"                          ,
     "id 16"                        , "id 17"                          ,
     "id 18"                        , "id 19"                          ,
     "OE account name"              , "OE account registry key"        ,
     "message text structure"       , "id 1d"                          ,
     "id 1e"                        , "id 1f"                           };
  if(index<MaxIndex) return text[index];
  throw DbxException("Wrong index !");
}

IndexedInfoDataType DbxMessageInfo::GetIndexDataType(int1 index) const
{ IndexedInfoDataType dataType[MaxIndex] = {
      dtInt4  , dtInt4  , dtDateTime, dtInt4  , dtInt4  , dtString,dtDateTime, dtString,
      dtString, dtString, dtString  , dtString, dtString, dtString, dtString , dtNone  ,
      dtInt4  , dtInt4  , dtDateTime, dtString, dtString, dtNone  , dtInt4   , dtNone  ,
      dtInt4  , dtInt4  , dtString  , dtString, dtData  , dtNone  , dtNone   , dtNone  };
  if(index<MaxIndex) return dataType[index];
  throw DbxException("Wrong index !");
}



bool IsValidDbxFile(const string fn,bool wantfolders)
{ HANDLE hf = CreateFile(fn.c_str(),GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);
  if (hf==INVALID_HANDLE_VALUE) return false;
  DWORD high; DWORD size=GetFileSize(hf,&high);
  CloseHandle(hf);
  if (high!=0 || size<FileHeaderSize) return false;
  bool okay=false;
  imemfile inm(fn.c_str());
  try
  { DbxFileHeader fileHeader(inm);
    int4 length = fileHeader.GetValue(fhFileInfoLength);
    int4 address = FileHeaderSize;
    if (length+FileHeaderSize<=size)
    { DbxFileInfo inf(inm,address,length);
      if (wantfolders) okay=fileHeader.isFolders();
      else okay=!fileHeader.isFolders();
    }
  }
  catch (...)
  {
  }
  inm.close();
  return okay;
}

bool IsValidDbxFolderFile(const string fn) {return IsValidDbxFile(fn,true);}
bool IsValidDbxMessageFile(const string fn) {return IsValidDbxFile(fn,false);}
add comment  edit commentPlease add comments here