Delphi World - Как читать файлы Outlook Express (DBX)
Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Как читать файлы Outlook Express (DBX)

Автор: Samum
WEB-сайт: http://samum2000.narod.ru

Структура файла

Сообщения хранятся в таблицах, расположенных друг за другом, по порядку. Каждая таблица состоит из двух частей - заголовка и _входа_. Смещение первой таблицы от начала файла располагается со смещением $30 от начала файла и представляет собой знаковое 32-х битное число (тип integer или longint). Общее же количество записей (т.е. этих самых таблиц) расположено со смещением $C4 от начала файла, и представляет собой число типа integer (longint).

Заголовок таблицы содержит количество _входов_, собственное смещение, а так же смещения предыдущей и следующей таблиц (все смещения, разумеется, от начала файла). Для чтения заголовка таблицы я использую такую структуру:

Toe5_IndexHeader = record
  FilePos: longint; {Это смещение структуры, используется для контроля}
  Unknown1: longint; { ??? }
  PrevIndex: longint; {Смещение предыдущей таблицы}
  NextIndex: longint; {Смещение следующей таблицы}
  Count: longint; {Количество _входов_, реальное количество находится так: count shr 8}
  Unknown2: longint; { ??? }
end;

Чтобы получить реальное количество _входов_, значение поля count надо конвертировать .

Каждый _вход_ содержит смещение (всегда от начала файла) заголовка письма (Message Header) и позицию другой индексной таблицы (эта таблица используется, чтобы обеспечить поддержку наследования (или связывания) сообщения, то есть любой заголовок сообщения, на который ссылается эта таблица - ребенок текущего сообщения). Вот структура входа:

Toe5_IndexItem = record
  HeaderPos: longint; {Смещение (от начала файла) заголовка письма}
  ChildIndex: longint; {Смещение (от начала файла) дочерней индексной таблицы}
  Unknown: longint;
end;

На мой взгляд, лучшим методом чтения таблиц является рекурсивная функция (см. пример).

Заголовок сообщения содержит некоторую информацию, которую Outlook Express использует, чтобы избежать доступа к сообщению, пока в этом нет особой необходимости. Эта структура разделена на три части: заголовок структуры, таблицу параметров типа DWORD и блок данных.

  • Заголовок структуры - это то, что вам необходимо прочитать в первую очередь, так как он определяет размер оставшихся двух частей. В её состав входят размер всей структуры (все 3 части), суммарный размер таблицы параметров, блока данных и количество параметров в таблице (я называю эти параметры флагами).
    THeaderData = record
      position: longint; {смещение данной структуры от BOF, используется только для контроля}
      DataLength: longint; {размер таблицы параметров и блока данных}
      HeaderLength: WORD; {размер всех трех частей}
      FlagCount: WORD; {количество элементов в таблице}
    end;

    Чтобы получить размер таблицы флагов, можно написать SizeOfTable:=FlagCount*SizeOf(DWord), a размер блока данных используйте DataLength-SizeOfTable.

  • Каждый элемент таблицы параметров должен быть декодирован, для того, чтобы получить его идентификатор и значение: идентификатор находят как element and $FF, а значение флага - element shr 8.
  • Блок данных содержит такую информацию, как дата получения, отправки, тема, адресат, отправитель, аккаунт и т.д. Для её чтения используется таблица флагов (параметров). Вот пример таблицы параметров и соответствующего блока данных:
Flags = 16     

     80: 00000074 
     81: 00000081
     02: 00000000
     84: 0002ECA0
     05: 00000008
     06: 00000025
     07: 0000002D
     08: 0000006E
     0D: 0000008B
     0E: 000000A5
     90: 00000003
     91: 0000376F
     12: 000000D4
     13: 000000DC
     14: 00000102
     1C: 0000012A

Data Block:
      
     00 72 F3 E4 58 22 C0 01 41 63 74 69 76 65 57 65 
         r  o  a  X  "  A  _  A  c  t  i  v  e  W  e 
      
     62 20 44 65 76 65 6C 6F 70 65 72 20 65 58 54 52 
      b     D  e  v  e  l  o  p  e  r     e  X  T  R 
      
     41 20 23 38 00 60 E2 F2 9C 90 35 C0 01 3C 4F 46 
      A     #  8     `  a  o  ?  ?  5  A  _  <  O  F 
      
     45 31 44 36 35 46 38 37 2E 32 39 39 31 37 34 31 
      E  1  D  6  5  F  8  7  .  2  9  9  1  7  4  1 
      
     42 2D 4F 4E 38 35 32 35 36 39 35 46 2E 30 30 35 
      B  -  O  N  8  5  2  5  6  9  5  F  .  0  0  5 
      
     43 33 46 41 36 40 70 69 6E 6E 61 63 6C 65 70 75 
      C  3  F  A  6  @  p  i  n  n  a  c  l  e  p  u 
      
     62 6C 69 73 68 69 6E 67 2E 63 6F 6D 3E 00 41 63 
      b  l  i  s  h  i  n  g  .  c  o  m  >     A  c 
      
     74 69 76 65 57 65 62 20 44 65 76 65 6C 6F 70 65 
      t  i  v  e  W  e  b     D  e  v  e  l  o  p  e 
      
     72 20 65 58 54 52 41 20 23 38 00 41 63 74 69 76 
      r     e  X  T  R  A     #  8     A  c  t  i  v 
      
     65 57 65 62 20 44 65 76 65 6C 6F 70 65 72 20 65 
      e  W  e  b     D  e  v  e  l  o  p  e  r     e 
      
     58 54 52 41 00 61 63 74 69 76 65 77 65 62 64 65 
      X  T  R  A     a  c  t  i  v  e  w  e  b  d  e 
      
     76 65 6C 6F 70 65 72 65 78 74 72 61 40 70 69 6E 
      v  e  l  o  p  e  r  e  x  t  r  a  @  p  i  n 
      
     6E 61 63 6C 65 70 75 62 6C 69 73 68 69 6E 67 2E 
      n  a  c  l  e  p  u  b  l  i  s  h  i  n  g  . 
      
     63 6F 6D 00 00 23 5E 0F 8B 22 C0 01 41 63 74 69 
      c  o  m        #  ^  _  ‹  "  A  _  A  c  t  i 
      
     76 65 57 65 62 20 44 65 76 65 6C 6F 70 65 72 20 
      v  e  W  e  b     D  e  v  e  l  o  p  e  r    
      
     65 58 54 52 41 20 53 75 62 73 63 72 69 62 65 72 
      e  X  T  R  A     S  u  b  s  c  r  i  b  e  r 
      
     20 00 3C 41 63 74 69 76 65 57 65 62 20 44 65 76 
            <  A  c  t  i  v  e  W  e  b     D  e  v 
      
     65 6C 6F 70 65 72 20 65 58 54 52 41 20 53 75 62 
      e  l  o  p  e  r     e  X  T  R  A     S  u  b 
      
     73 63 72 69 62 65 72 20 3E 00 88 00 00 00 01 00 
      s  c  r  i  b  e  r     >     ?           _    
      
     02 4E 00 00 00 00 F9 37 00 00 02 4E 00 00 01 00 
      _  N              u  7        _  N        _    
      
     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
                                                     
      
     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
                                                     
      
     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
                                                     
      
     00 00 00 00 00 00 40 05 00 00 F9 37 00 00 00 00 
                        @  _        u  7             
      
     00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 
                        _                            
      
     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
                                                     
      
     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
                                                     
      
     00 00 00 00 00 00 6D 70 
                        m  p

Теперь давайте разберёмся, что означает каждый флаг.

Для файла Folders.dbx:

  • $2: смещение от начала блока данных, по которому расположено название папки (Nullterminated string)
  • $3: смещение от начала блока данных, по которому расположено имя файла, представляющего эту папку (это тоже Nullterminated string)
  • $6: не важный флаг. Если папка его имеет, то значит она - специальная папка, у неё нет собственного файла, и она используется лишь для организации;
  • $80: идентификатор папки;
  • $81: идентификатор родительской папки (флаг $80 родительской папки).

Для других файлов:

  • $1: Смещение от начала блока данных, по которому расположен статус сообщения.
  • $2: Смещение от начала блока данных, по которому расположена дата отправки (дата представлена в TFileTime)
  • $4: Иногда типа DWord не хватает, чтобы представить и идентификатор флага, и смещения тела сообщения от начала файла, тогда смещение, по которому расположено тело, размещается в блоке данных. Значение этого флага - смещение (от начала блока данных) по которому находится это значение.
  • $7: Смещение от начала блока данных, по которому расположен идентификатор письма MessageID (nullterminated string)
  • $8: Смещение от начала блока данных, по которому расположена тема сообщения (nullterminated string)
  • $9: Смещение от начала блока данных, по которому расположен параметр “From Reply” сообщения (nullterminated string)
  • $A: Смещение от начала блока данных, по которому расположены ссылки (References)сообщения (nullterminated string)
  • $B: Смещение от начала блока данных, по которому расположена новостная группа сообщения (null terminated string)
  • $D: Смещение от начала блока данных, по которому расположен адресат (null terminated string)
  • $E: Смещение от начала блока данных, по которому расположены данные “Reply to:” (null terminated string)
  • $12: Смещение от начала блока данных, по которому расположена дата получения (дата представлена в формате TFileTime)
  • $13: Смещение от начала блока данных, по которому расположен получатель (null terminated string)
  • $1A: Смещение от начала блока данных, по которому расположен аккаунт (null terminated string)
  • $1B: Смещение от начала блока данных, по которому расположен идентификатор аккаунта (null terminated string)
  • $80: Номер сообщения
  • $81: Используется для хранения статуса письма
  • $84: Смещение тела сообщения в файле
  • $91: Размер сообщения.

Сообщения: Каждое письмо или сообщение групп новостей хранятся в блоках по 512 байт, у каждого блока есть заголовок. То есть каждое сообщение делится на части, и к каждой части добавляют заголовок (в котором содержится размер блока, размер занятой части блока, а так же положение следующего блока). Я использую такую структуру для чтения блоков (вместе с их заголовками):

Toe5_MsgItem = record
  FilePos: longint; {смещение структуры от BOF, используется для контроля верности операций}
  Unknown: longint; {я думаю, это размер блока данных}
  ItemSize: longint; {использованная часть блока}
  NextItem: longint; {смещение следующего блока от BOF, и 0, если это последний блок}
  MsgContent: array[0..511] of Char; {блок, содержащий непосредственно данные}
end;

Удаление сообщений

Когда какое либо сообщение удаляется, оно сначала помещается в папку "Удаленные", а физически - в соответствующий этой папке файл. Место же, которое занимало сообщение в прежнем файле, добавляется в список пустого пространства, и когда приходит новое сообщение, Outlook Express использует сначала это место. Смещение первого элемента в списке сободного места сохранено по смещению $48 от начала файла. Каждый элемент этого списка разделен на две части: заголовок и блок свободного пространства. Вот структура заголовка:

Toe5_FreeSpace = record
  FilePos: longint; {это смещение всей структуры от начала файла BOF, используется для контроля}
  ElementSize: longint; {размер структуры - заголовок и свободное место}
  FreeSpaceSize: longint; {Размер свободного пространства}
  PreviousElement: longint; {смещение (от начала файла) предыдущего элемента}
  NextElement: longint; {смещение следующего элемента}
end;

Даты

Все даты в заголовке сообщения сохранены в формате TFileTime, и основаном на UTC. Перед использованием это значение надо перевести в местное время. Вот небольшой пример того, как это можно сделать:

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

Статус сообщения

Для получения статуса сообщения можно использовать значение флага $81 следующим образом:

...
x := < значение флага $81 >
if (x and constant) <> 0 then
  ...

И на последок, некоторые константы:

  • DOWNLOADED = $1
  • MARKED = $20
  • (Отмечено флажком) READED = $80
  • DOWNLOAD_LATER = $100
  • NEWS_MSG = $800

Эти константы надо проверить:

  • ATTACHMENTS = $4000
  • REPLY =$80000
  • INSPECT_CONVERSATION = $400000
  • IGNORE_CONVERSATION =$800000

Модули к статье можно взять здесь

Все дополнения, модификации, предложения, благодарности и т.п. просьба присылать на Samum2000@mail15.com (особенно к русскому переводу) или на walther_e@yahoo.com (на английском языке, касательно оригинала). Walther Estergaard Walther_e@yahoo.com

Перевод:
Боднар Денис aka Samum, Samum2000@mail15.com, ICQ: 278395965.

Проект Delphi World © Выпуск 2002 - 2017
Автор проекта: Эксклюзивные курсы программирования