复合二进制文档 - 基本概念(上篇)
系列文章分为上中下三篇,教你怎么使用纯js解析复合二进制文档(doc,ppt,msi)
什么是COM结构化存储详解
COM结构化存储(Structured Storage)是微软提供的一种复合文档存储技术,它允许将多个独立的数据流和存储对象组织在一个文件中,就像文件系统中的文件和目录一样。
核心概念
存储(storage)
- 类似于文件系统中的目录
- 可以包含其他存储对象和流对象
- 用于组织数据的层次结构
流 (stream)
- 类似于文件系统中的文件
- 包含实际的数据内容
- 是存储的基本单元
技术特点
- 事务支持:支持事务性操作,可以回滚更改
- 增量保存:只保存修改的部分,提高效率
复合二进制文档(CFB)
复合二进制文档是COM结构化存储的一种实现,常用的文件如:.doc,.xls,.msi都是标准的复合二级制文档格式;
- tips: doc和docx的区别:两者是两种不同的数据格式,doc是复合二进制格式,docx是基于xml的二进制格式;
扇区
扇区(sectors)是流的组成单元,一个流由多个扇区按照一定顺序组成。组成一个流的多个扇区叫做扇区链。 扇区的大小是固定的,而且任意扇区的大小都是一样的; 扇区按简单顺序列举,一个扇区的索引(从0开始)叫做扇区标识(SID:sector identifier)。SID是一个有符号的32位(4字节)的整型值。 扇区索引值存在一些特殊值,表示不同的含义:
- -1 : 空闲扇区,可存在于文件中,但不是任何流的组成部分。
- -2 : 扇区链的结束标记。
- -3 : 此扇区用于存放扇区配置表(SAT)。
- -4 : 此扇区用于存放主扇区配置表(MSAT)。
扇区链与扇区标识链
用于存储流数据的所有扇区链的列表叫做扇区链(Sector Chain)。 扇区可以是无序的。因此用于指定一个流的扇区的顺序的SID数组就称为扇区标识链(SID chain)。一个扇区标识链总是以-2为结束标记。
流的扇区标识是通过扇区配置表构建的,但短流和以下两种内部流除外: 1.主扇区配置表,其从自身构建SID链(每个扇区包含下一个扇区的SID)。 2.扇区配置表,其通过主扇区配置表构建SID链
扇区配置表
扇区配置表(SAT)是一个SID数组,记录了所有流的SID链及SID排列顺序。 SAT的大小(SID个数)就等于复合文档中所存在的sector的个数,也就是说扇区个数可以这样计算:(表示扇区配置表的扇区数量 * 扇区大小 )/ 4,表示扇区配置表的扇区数量和SID可以从主扇区配置表中得到。
当通过SAT为一个流创建SID链时,SAT数组的当前位置(数组的index)表示的就是当前的sector,而该位置存放的SID则指向下一个扇区。
SAT可能在任意位置的值为-1(未被引用),这些扇区将不被流使用。如果该位置的值是-2表示一个流的结束。如果扇区用于存放SAT则为SAT SID(-3),同样用于存放MSAT则为MSAT SID(-4)。
一个SID链的起点从用户流的目录入口 或头文档 或目录流本身获得。
主扇区配置表
主扇区配置表(MSAT)是一个扇区标识数组,存放了所有用于存放扇区配置表(SAT)的扇区的SID。 MSAT的前109个SID也存放于文档头(Header)中,在头文档的offset=76开始(前文已经说明,明个SID占4字节,所以109个SID的大小为436字节) 如果一个MSAT的SID数多余109个,那么多出来的SID将存放于sector中,文档头中已经指明了用于存放MSAT的第一个sector的SID。
存放MSAT的扇区的内容(假定扇区大小为 n 字节,则可以存放(n-4/4)个SID),那么:
| offset | size | content |
|---|---|---|
| 0 | n-4 | SID数组(数量为(n-4)/4) |
| n-4 | 4 | 下一个扇区的SID |
复合文档头
复合文档头在文件的开始,且其大小必定为512字节。这意味着第一个Sector的开始相对文件的偏移量为512字节。
复合文档头包含了一些特定数据:
| 偏移量(Offset) | 大小(Size) | 内容(Contents) |
|---|---|---|
| 0 | 8 | 复合文档文件标识:D0H CFH 11H E0H A1H B1H 1AH E1H |
| 8 | 16 | 此文件的唯一标识(不重要, 可全部为0) |
| 24 | 2 | 文件格式修订号 (一般为003EH) |
| 26 | 2 | 文件格式版本号(一般为0003H) |
| 28 | 2 | 字节顺序规则标识(大端与小端):FEH FFH = Little-Endian,FFH FEH = Big-Endian |
| 30 | 2 | 复合文档中sector的大小(ssz),以2的幂形式存储 |
| 32 | 2 | short-sector的大小,以2的幂形式存储 |
| 34 | 10 | Not used |
| 44 | 4 | 用于存放扇区配置表(SAT)的sector总数 |
| 48 | 4 | 用于存放目录流的第一个sector的SID |
| 52 | 4 | Not used |
| 56 | 4 | 标准流的最小大小(一般为4096 bytes),小于此值的流即为短流。 |
| 60 | 4 | 用于存放短扇区配置表(SSAT)的第一个sector的SID,或为–2 (End Of Chain SID)如不存在。 |
| 64 | 4 | 用于存放短扇区配置表(SSAT)的sector总数 |
| 68 | 4 | 用于存放主扇区配置表(MSAT)的第一个sector的SID,或为–2 (End Of Chain SID) 若无附加的sectors。 |
| 72 | 4 | 用于存放主扇区配置表(MSAT)的sector总数 |
| 76 | 436 | 存放主扇区配置表(MSAT)的第一部分,包含109个SID。 |
短流
当一个流的大小小于某个阈值(在复合文档头中指定),则把这个流成为短流;
短流并不是使用常规数据扇区来存储,而是集中存放在一个特殊的短流存放容器中,这个容器本身是一个常规的扇区。
短流的存储结构
短流数据存放在短流池; 所有短流的数据都连续或链式存储在一个专门的短流池中。 短流池本身是一个常规流(大小不受 4096 字节限制),但内部存储的是多个短流数据块。
短流使用短扇区(Short Sector)存储: 短流的存储单元是短扇区(Short Sector),通常大小为 64 字节,在复合文档头中指定。
短流的管理方式: 短扇区分配表(SSAT):记录短流数据的存储位置,类似于 FAT(文件分配表),但仅管理短流。 短流目录项(Directory Entry) 中会标记该流是否为短流(DIR_ENTRY::_mse = STGTY_STREAM + 大小 < 阈值)。
短流的读取流程
检查目录项(Directory Entry): 如果 streamSize < 4096(或其他阈值),则标记为短流。
定位短流池(Short Stream Container): 从根存储找到短流池的位置(通常是一个常规流)。
查询短扇区分配表(SSAT): 根据 SSAT 找到短流在短流池中的存储位置。
读取短扇区链: 短流可能占用多个短扇区,通过 SSAT 链式读取。
目录(storage)
目录(directory)是一种内部控制流,由一系列目录入口(directory entry)组成。
目录结构
目录入口
一个目录入口的大小严格地为128字节,计算其相对目录流的偏移量的公式为:dir_entry_pos(DID) = DID ∙ 128。目录入口的内容; 它的二进制结构如下:
| 偏移量 (Offset) | 大小 (Size) | 内容 (Contents) |
|---|---|---|
| 0 | 64 | 入口名称,一般为16位的Unicode字符,以0结束 |
| 64 | 2 | 用于存放名字的区域的大小,包括结尾的0 |
| 66 | 1 | 入口类型:unknown = 0,storage = 1,stream = 2,rootStorage = 5, |
| 67 | 1 | 此入口的节点颜色:00H = Red,01H = Black |
| 68 | 4 | 其左节点的DID(若此入口为一个 storage或stream),若没有左节点就为-1。 |
| 72 | 4 | 其右节点的DID(若此入口为一个 storage或stream),若没有右节点就为-1。 |
| 76 | 4 | 其成员红黑树的根节点的DID(若此入口为storage),其他为-1。 |
| 80 | 16 | 唯一标识符(若为storage)(不重要,可能全为0) |
| 96 | 4 | 用户标记 |
| 100 | 8 | 创建此入口的时间标记。大多数情况都不写。 |
| 108 | 8 | 最后修改此入口的时间标记。大多数情况都不写。 |
| 116 | 4 | 若此为流的入口,指定流的第一个扇区或短扇区的SID;若此为根storage入口,指定短流存放流的第一个sector的SID;其他情况,为0。 |
| 120 | 4 | 若此为流的入口,指定流的大小(字节);若此为根仓库入口,指定短流存放流的大小(字节);其他情况,为0。 |
| 124 | 4 | Not used |
总结
行文至此,做一个小总结:
- 复合二进制文档的二进制结构可以笼统的分为两个部分:复合文档头(固定512字节)和各种扇区(每个扇区的大小一致);
- 复合文档头指定了各种特定数据,如:扇区大小,短扇区大小,字节顺序规则标识,主扇区配置的钱109个扇区标识;
- 主扇区配置是一个扇区标识数组,扇区标识指定的扇区专门保存扇区配置,前109保存在文档头中;当超出后则保存在专门的扇区中;保存主扇区配置的扇区格式前文已经讲解了;
- 扇区配置表也是一个扇区标识数组array;数组下标i代表当前扇区的扇区标识,数组值array[i]标识扇区链的下一个扇区;当值为-2时表示尾结点;
- 目录则记录各个目录和流间的层级关系;
参考文档
CFBF 描述:sc.openoffice.org/compdocfile…