一、引言
电子邮件如今已成为人们工作、生活中非常重要的通信工具,Outlook. Express作为微软公司在Windows操作系统中自带的电子邮件软件也因此得到广泛的使用。OE将全部邮件都存放在dbx文件中,这样做精巧简便,却不利于邮件的管理利用。例如用户有时需要将电子邮件存入数据库归档;有时需要根据时间、地址、内容对邮件进行分类检索,在OE中要实现这些操作就很繁琐。下面我们一起来研究一下dbx文件中邮件的存储结构,从而实现将邮件从中提取出来,以单个的.eml文件形式存放,为邮件数据的应用增加一种思路。
二、设计编程
1.读取dbx文件头
表1 dbx文件头存储结构
文件位置 |
数据类型 |
描述 |
0x0000 |
|
dbx文件标识 |
0x0004 |
|
dbx文件类型标识 |
...... |
|
|
0x00c4 |
Int4 |
邮件数量 |
0x00e4 |
Int4 |
邮件信息索引起始位置 |
(1) 如表1所示,文件的起始0x0000位置记录了dbx文件的标识,必须是cf ad 12 fe。
(2) 在文件的0x0004位置是dbx文件的格式标识。如果是c5 fd 74 6f,则表示该文件是邮件存储文件;如果是c6 fd 74 6f,则表示是OE的文件夹结构文件。本文只讨论邮件存储文件的格式。
(3) 在文件的0x00c4位置记录了本文件中存储的邮件数量。
(4) 在文件的0x00e4位置记录了邮件信息索引的起始位置。从该位置处可以读取全部邮件的信息索引。
读取dbx文件头的代码如下:
AssignFile(fr,Edit1.text); ReSet(fr,1);
//查看.dbx文件标志
BlockRead(fr,l,4);
if l<>$fe12adcf then begin
ShowMessage('非OE的邮件文件!');
exit;
end;
//查看.dbx文件中的邮件文件,不包括文件夹设置文件
BlockRead(fr,l,4);
if l<>$6f74fdc5 then begin
ShowMessage('可能是文件夹文件,不是邮件存放文件!');
exit;
end;
//邮件数
Seek(fr,$c4); BlockRead(fr,MsgCount,4);
if MsgCount=0 then begin
ShowMessage('没有邮件!');
exit;
end;
//邮件信息起始存放位置
Seek(fr,$e4); BlockRead(fr,MsgInfoPtr,4);
if MsgInfoPtr=0 then begin
ShowMessage('没有可提取的邮件!');
exit;
end;
2.读取邮件信息索引
根据文件头0x00e4处所记录的地址找到邮件信息索引,这些索引的存储结构如表2所示。
表2 邮件信息索引的存储结构
序号 |
数据类型 |
描述 |
1 |
Int4 |
|
2 |
Int4 |
|
3 |
Int4 |
下一段信息索引区的位置 |
4 |
Int4 |
上一段信息索引区的位置 |
5 |
Int1 |
|
6 |
Int1 |
本区域所含邮件信息索引数量 |
7 |
Int2 |
|
8 |
Int4 |
|
9 |
|
记录每封邮件信息的位置 |
|
...... |
|
信息索引可以分为若干段,字段3、字段4分别记录了下一段、上一段的位置,形成数据链。字段6记录了本段所包含的邮件信息索引数量,字段9处则根据该数量,以3个Int4为单位记录每封邮件信息所在的位置。这3个Int4包含表3所示的信息:
表3 邮件位置存储结构
序号 |
数据类型 |
描述 |
1 |
Int4 |
记录邮件信息存储的位置 |
2 |
Int4 |
下一段信息索引区的位置 |
3 |
Int4 |
下一段索引区所含邮件信息索引数量 |
如果字段2的值不为0,表示继续指向下一段邮件信息索引区,此时应该采用递归的方法继续读取;字段2的值为0,表示结束。
读取邮件信息索引的代码如下:
procedure NodeTree(FirstPtr:Longword );
var
NodeCount : Byte;
i : Longword;
t : NodeBody;
Ptr : Longword;
begin
Ptr:=FirstPtr;
while Ptr<>0 do begin
Seek(fr,Ptr+17); BlockRead(fr,NodeCount,1);
//将每封邮件的信息存放位置记入Msgs数组的InfoPtr
for i:=1 to NodeCount do begin
Seek(fr,Ptr+24+(i-1)*12);
BlockRead(fr,t,12);
//变长数组长度+1
SetLength(Msgs,Length(Msgs)+1);
Msgs[Length(Msgs)-1].InfoPtr:=t.Ptr;
//查看是否有子树,如果有,则进入递归读取
if t.NextPtr<>0 then NodeTree(t.NextPtr);
end;
//读取下一个Node,0表示结束
Seek(fr,Ptr+8); BlockRead(fr,Ptr,4);
end;
end;
3.读取邮件信息
根据邮件信息索引中字段9所记录的位置可以找到每封邮件的信息,这些信息存储的结构如表4所示。
表4 邮件信息的存储结构
序号 |
数据类型 |
描述 |
1 |
Int4 |
|
2 |
Int4 |
|
3 |
Int2 |
|
4 |
Int1 |
所包含的索引项数量 |
5 |
Int1 |
|
|
|
索引项数量*4 |
6 |
Int1 Bit 8 |
高位标志 |
7 |
Bit 1-7 |
索引号 |
8 |
Int3 |
数值 |
...... |
|
|
9 |
|
数据区 |
如果字段6的Bit 8位为0,表示该索引项的内容需要从下面的字段9数据区取得,起始位置就是Bit 1-7所记录的数据;如果字段6的Bit 8位为1,表示该索引项的数据就是Bit 1-7所记录的数据。
根据索引数据就可以列举出全部邮件的信息,如表5所示。
表5 全部邮件信息
索引号 |
数据类型 |
描述 |
0x00 |
Int4 |
|
0x01 |
Int4 |
|
0x02 |
|
|
0x03 |
Int4 |
|
0x04 |
Int4 |
存放邮件内容的起始块 |
...... |
|
|
0x08 |
String |
邮件标题 |
...... |
|
|
读取邮件信息的代码如下:
//读取每封邮件的标题和内容的存放位置
for i:=0 to Length(Msgs)-1 do begin
ProgressBar1.Position:=i*100 div Length(Msgs); Application.ProcessMessages;
Seek(fr,Msgs[i].InfoPtr+10); BlockRead(fr,NodeCount,1);
for j:=1 to NodeCount do begin
Seek(fr,Msgs[i].InfoPtr+12+(j-1)*4);
BlockRead(fr,b1,1); BlockRead(fr,b2,1); BlockRead(fr,b3,1); BlockRead(fr,b4,1);
m.Hi:=b1 shr 7;
m.Pos:=b1 and $7F;
m.Value:=(b4 shl 16)+(b3 shl 8)+b2;
if m.Hi=0 then m.Value:=Msgs[i].InfoPtr+12+NodeCount*4+m.Value;
case m.Pos of
4 : begin
//邮件内容存放的起始位置
if m.Hi=1 then Msgs[i].EMLPtr:=m.Value else begin
Seek(fr,m.Value); BlockRead(fr,l,4);
Msgs[i].EMLPtr:=l;
end;
end;
8 : begin
//邮件标题
s:='';
Seek(fr,m.Value);
repeat
BlockRead(fr,c,1);
if c in BadFilenameChar then c:='_';
if c<>#0 then s:=s+c;
until c=#0;
if s='' then s:='[无标题]';
if Length(s)>200 then s:=Copy(s,1,200);
Msgs[i].Subject:=s;
end;
end;
end;
end;
在dbx文件中邮件的内容数据是分块存储的,起始块的位置就记录在索引号为0x04的数据中。邮件内容数据块的数据结构如表6所示:
表6 邮件内容数据块的数据结构
序号 |
数据类型 |
描述 |
1 |
Int4 |
|
2 |
Int4 |
本块数据区长度 |
3 |
Int4 |
数据区内实际数据长度 |
4 |
Int4 |
下一个数据块的位置 |
5 |
|
数据区 |
如果字段3的数值小于字段2,即数据区内实际的数据长度小于本块数据区长度,则后面的是无用数据。字段4记录了下一块内容数据的位置,0x0000表示结束。
读取邮件内容,并以邮件标题为文件名生成eml文件的代码如下:
//读取每封邮件的内容并存盘
for i:=0 to Length(Msgs)-1 do begin
if Msgs[i].EMLPtr=0 then continue;
//先检查是否有同名文件,有的话在文件名后面添加计数
s1:=Edit2.Text+Msgs[i].Subject; s:=s1;
j:=1;
while FileExists(s+'.eml') do begin
s:=s1+' ['+InttoStr(j)+']';
Inc(j);
end;
s:=s+'.eml';
AssignFile(fw,s); ReWrite(fw,1);
Seek(fr,Msgs[i].EMLPtr);
repeat
BlockRead(fr,mh,16);
SetLength(Buf,mh.BlockLen);
//按照BlockLen读取
BlockRead(fr,Buf[0],mh.BlockLen);
//按照TextLen实际内容长度存盘
BlockWrite(fw,Buf[0],mh.TextLen);
//读取下一段所在位置
Seek(fr,mh.NextPtr);
until mh.NextPtr=0;
CloseFile(fw);
end;
三、程序运行界面
提取dbx文件中邮件程序的运行界面如图1所示。
图1 程序运行界面
四、结语
以上简单介绍了dbx文件的存储结构,虽然OE本身也提供了通过拖拽方式生成.eml文件的功能,但是在我们了解dbx文件的存储结构后,就可以实现邮件数据在不同操作系统、不同应用软件之间交换,从而方便用户灵活使用。
|