///|
let ole_identifier : Array[Byte] = [
0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1,
].map(v => v.to_byte())
///|
let header_cls_id : Bytes = Bytes::make(16, (0).to_byte())
///|
let end_of_chain = -2
///|
let free_sector = -1
///|
let fat_sector = -3
///|
let dif_sector = -4
///|
fn read_u16_le(bytes : BytesView, offset : Int) -> Int raise XlsxError {
if offset < 0 || offset + 2 > bytes.length() {
raise InvalidEncryptionInfo(msg="cfb read out of bounds")
}
let b0 = bytes[offset].to_int()
let b1 = bytes[offset + 1].to_int()
b0 | (b1 << 8)
}
///|
fn read_u32_le(bytes : BytesView, offset : Int) -> UInt raise XlsxError {
if offset < 0 || offset + 4 > bytes.length() {
raise InvalidEncryptionInfo(msg="cfb read out of bounds")
}
@crypto.u32_from_le(bytes, offset)
}
///|
fn read_i32_le(bytes : BytesView, offset : Int) -> Int raise XlsxError {
let value = read_u32_le(bytes, offset)
let signed = value.reinterpret_as_int()
signed
}
///|
fn read_u64_le(bytes : BytesView, offset : Int) -> UInt64 raise XlsxError {
if offset < 0 || offset + 8 > bytes.length() {
raise InvalidEncryptionInfo(msg="cfb read out of bounds")
}
@crypto.u64_from_le(bytes, offset)
}
///|
priv struct CfbSector {
mut cls_id : Bytes
content : Bytes
mut name : String
mut c : Int
mut l : Int
mut r : Int
mut color : Int
mut size : Int
mut start : Int
state : Int
mut type_id : Int
}
///|
priv struct CfbBuilder {
mut stream : Array[Byte]
mut position : Int
mut paths : Array[String]
mut sectors : Array[CfbSector]
}
///|
fn cfb_builder_new() -> CfbBuilder {
{
stream: [],
position: 0,
paths: ["Root Entry/"],
sectors: [
{
cls_id: header_cls_id,
content: Default::default(),
name: "Root Entry",
c: -1,
l: -1,
r: -1,
color: 1,
size: 0,
start: 0,
state: 0,
type_id: 5,
},
],
}
}
///|
fn CfbBuilder::write_bytes(self : CfbBuilder, value : BytesView) -> Unit {
let pos = self.position
let needed = pos + value.length()
if self.stream.length() < needed {
self.stream.resize(needed, (0).to_byte())
}
for i in 0.. Unit {
self.write_bytes([(value & 0xff).to_byte(), ((value >> 8) & 0xff).to_byte()])
}
///|
fn CfbBuilder::write_uint32(self : CfbBuilder, value : Int) -> Unit {
self.write_bytes([
(value & 0xff).to_byte(),
((value >> 8) & 0xff).to_byte(),
((value >> 16) & 0xff).to_byte(),
((value >> 24) & 0xff).to_byte(),
])
}
///|
fn CfbBuilder::write_uint64(self : CfbBuilder, value : Int) -> Unit {
// Builder inputs are non-negative MoonBit Int values, so the high 32 bits
// are always zero. Shifting an Int by 32 is not a widening conversion.
self.write_uint32(value)
self.write_uint32(0)
}
///|
fn CfbBuilder::write_utf16le(self : CfbBuilder, value : StringView) -> Unit {
let bytes = @encoding/utf16.encode(value, bom=false, endianness=Little)
self.write_bytes(bytes)
}
///|
fn CfbBuilder::put(self : CfbBuilder, name : String, content : Bytes) -> Unit {
let root = self.paths[0]
let mut path = if root.length() <= name.length() && name.has_prefix(root) {
name
} else if root.length() > 0 && !root.has_suffix("/") {
root + "/" + name
} else {
root + name
}
path = path.replace_all(old="//", new="/")
let sector : CfbSector = {
cls_id: header_cls_id,
content,
name: path,
c: -1,
l: -1,
r: -1,
color: 1,
size: content.length(),
start: 0,
state: 0,
type_id: 2,
}
self.sectors.push(sector)
self.paths.push(path)
}
///|
fn cfb_compare(left : String, right : String) -> Int {
let left_parts = Array::from_iter(left.split("/"))
let right_parts = Array::from_iter(right.split("/"))
let left_len = left_parts.length()
let right_len = right_parts.length()
let limit = if left_len < right_len { left_len } else { right_len }
for i in 0.. String {
match path.rev_find("/") {
Some(pos) => path[:pos].to_owned()
None => ""
}
}
///|
fn base_name(path : String) -> String {
match path.rev_find("/") {
Some(pos) => path[pos + 1:].to_owned()
None => path
}
}
///|
priv struct CfbBuildObj {
path : String
sector : CfbSector
}
///|
fn CfbBuilder::prepare(self : CfbBuilder) -> Unit {
let objects : Array[CfbBuildObj] = []
for i in 0.. cfb_compare(a.path, b.path))
self.paths = []
self.sectors = []
for obj in objects {
self.paths.push(obj.path)
self.sectors.push(obj.sector)
}
let len = self.sectors.length()
for i in 0.. 1 { 1 } else { -1 }
self.sectors[i].size = 0
self.sectors[i].type_id = 5
} else {
if i + 1 < len && parent_path(self.paths[i + 1]) == parent_path(path) {
self.sectors[i].r = i + 1
}
self.sectors[i].type_id = 2
}
}
}
///|
fn CfbBuilder::locate(self : CfbBuilder) -> Array[Int] {
let mut mini_stream_sector_size = 0
let mut fat_sector_size = 0
for sector in self.sectors {
if sector.content.length() == 0 {
continue
}
let size = sector.content.length()
if size > 0 {
if size < 0x1000 {
mini_stream_sector_size += (size + 0x3f) >> 6
} else {
fat_sector_size += (size + 0x1ff) >> 9
}
}
}
let directory_sectors = (self.paths.length() + 3) >> 2
let mini_stream_sectors = (mini_stream_sector_size + 7) >> 3
let mini_fat_sectors = (mini_stream_sector_size + 0x7f) >> 7
let sectors = mini_stream_sectors +
fat_sector_size +
directory_sectors +
mini_fat_sectors
let mut fat_sectors = (sectors + 0x7f) >> 7
let mut difat_sectors = 0
if fat_sectors > 109 {
difat_sectors = (fat_sectors - 109 + 0x7e) >> 7
}
while (sectors + fat_sectors + difat_sectors + 0x7f) >> 7 > fat_sectors {
fat_sectors = fat_sectors + 1
difat_sectors = if fat_sectors <= 109 {
0
} else {
(fat_sectors - 109 + 0x7e) >> 7
}
}
let location : Array[Int] = [
1, difat_sectors, fat_sectors, mini_fat_sectors, directory_sectors, fat_sector_size,
mini_stream_sector_size, 0,
]
self.sectors[0].size = mini_stream_sector_size << 6
self.sectors[0].start = location[0] +
location[1] +
location[2] +
location[3] +
location[4] +
location[5]
location[7] = self.sectors[0].start + ((location[6] + 7) >> 3)
location
}
///|
fn CfbBuilder::write_msat(self : CfbBuilder, location : Array[Int]) -> Unit {
for i in 0..<109 {
if i < location[2] {
self.write_uint32(location[1] + i)
} else {
self.write_uint32(-1)
}
}
let mut i = 109
if location[1] != 0 {
let mut offset = 0
while offset < location[1] {
while i < 236 + offset * 127 {
if i < location[2] {
self.write_uint32(location[1] + i)
} else {
self.write_uint32(-1)
}
i = i + 1
}
if offset == location[1] - 1 {
self.write_uint32(end_of_chain)
} else {
self.write_uint32(offset + 1)
}
offset = offset + 1
}
}
}
///|
fn CfbBuilder::write_directory_entry(
self : CfbBuilder,
location : Array[Int],
) -> Unit {
let mut i = 0
while i < location[4] << 2 {
let mut path = ""
if i < self.paths.length() {
path = self.paths[i]
}
if i >= self.paths.length() || path.length() == 0 {
for _ in 0..<=16 {
self.write_uint32(0)
}
for _ in 0..<=2 {
self.write_uint32(-1)
}
for _ in 0..<=11 {
self.write_uint32(0)
}
i = i + 1
continue
}
if i == 0 {
self.sectors[i].start = if self.sectors[i].size > 0 {
self.sectors[i].start - 1
} else {
end_of_chain
}
}
let name = self.sectors[i].name
let sector_size = 2 * (name.length() + 1)
self.write_utf16le(name)
self.position = self.position + (64 - 2 * name.length())
self.write_uint16(sector_size)
self.write_bytes([self.sectors[i].type_id.to_byte()])
self.write_bytes([self.sectors[i].color.to_byte()])
self.write_uint32(self.sectors[i].l)
self.write_uint32(self.sectors[i].r)
self.write_uint32(self.sectors[i].c)
if self.sectors[i].cls_id.length() == 0 {
for _ in 0..<=3 {
self.write_uint32(0)
}
} else {
self.write_bytes(self.sectors[i].cls_id)
}
self.write_uint32(self.sectors[i].state)
self.write_uint32(0)
self.write_uint32(0)
self.write_uint32(0)
self.write_uint32(0)
self.write_uint32(self.sectors[i].start)
self.write_uint64(self.sectors[i].size)
i = i + 1
}
}
///|
fn cfb_write_chain(
builder : CfbBuilder,
head : Int,
offset : Int,
i : Int,
) -> (Int, Int) {
let mut local_i = i
let local_offset = offset + head
while local_i < local_offset - 1 {
builder.write_uint32(local_i + 1)
local_i = local_i + 1
}
if head != 0 {
local_i = local_i + 1
builder.write_uint32(end_of_chain)
}
(local_offset, local_i)
}
///|
fn CfbBuilder::write_sector_chains(
self : CfbBuilder,
location : Array[Int],
) -> CfbSector {
let mut i = 0
let mut offset = 0
while i < location[1] {
self.write_uint32(dif_sector)
i = i + 1
}
while i < location[1] + location[2] {
self.write_uint32(fat_sector)
i = i + 1
}
let (new_offset0, new_i0) = cfb_write_chain(
self,
location[3],
offset + location[1] + location[2],
i,
)
offset = new_offset0
i = new_i0
let (new_offset1, new_i1) = cfb_write_chain(self, location[4], offset, i)
offset = new_offset1
i = new_i1
let mut sector = self.sectors[0]
for idx in 0..> 9,
offset,
i,
)
offset = new_offset2
i = new_i2
}
let (new_offset3, new_i3) = cfb_write_chain(
self,
(location[6] + 7) >> 3,
offset,
i,
)
offset = new_offset3
i = new_i3
while (self.position & 0x1ff) != 0 {
self.write_uint32(end_of_chain)
}
i = 0
offset = 0
for idx in 0..= 0x1000 {
continue
}
self.sectors[idx].start = offset
let (new_offset4, new_i4) = cfb_write_chain(
self,
(size + 0x3f) >> 6,
offset,
i,
)
offset = new_offset4
i = new_i4
}
while (self.position & 0x1ff) != 0 {
self.write_uint32(end_of_chain)
}
sector
}
///|
fn CfbBuilder::write(self : CfbBuilder) -> Bytes {
self.prepare()
let location = self.locate()
self.stream = Array::make(location[7] << 9, (0).to_byte())
for i in 0..<=7 {
self.write_bytes([ole_identifier[i]])
}
self.write_bytes(Bytes::make(16, (0).to_byte()))
self.write_uint16(0x003e)
self.write_uint16(0x0003)
self.write_uint16(0xfffe)
self.write_uint16(0x0009)
self.write_uint16(0x0006)
self.write_bytes(Bytes::make(10, (0).to_byte()))
self.write_uint32(location[2])
self.write_uint32(location[0] + location[1] + location[2] + location[3] - 1)
self.write_uint32(0)
self.write_uint32(1 << 12)
if location[3] != 0 {
self.write_uint32(location[0] + location[1] + location[2] - 1)
} else {
self.write_uint32(end_of_chain)
}
self.write_uint32(location[3])
if location[1] != 0 {
self.write_uint32(location[0] - 1)
} else {
self.write_uint32(end_of_chain)
}
self.write_uint32(location[1])
self.write_msat(location)
let mut sector = self.write_sector_chains(location)
self.write_directory_entry(location)
for i in 1..= 0x1000 {
self.position = (sector.start + 1) << 9
for b in sector.content {
self.write_bytes([b])
}
while (sector.size & 0x1ff) != 0 && (self.position & 0x1ff) != 0 {
self.write_bytes([(0).to_byte()])
}
}
}
for i in 1.. 0 && sector.size < 0x1000 {
for b in sector.content {
self.write_bytes([b])
}
while (self.position & 0x3f) != 0 {
self.write_bytes([(0).to_byte()])
}
}
}
while self.position < self.stream.length() {
self.write_bytes([(0).to_byte()])
}
Bytes::from_array(self.stream)
}
///|
priv struct CfbEntry {
name : String
type_id : Int
start : Int
size : Int
}
///|
priv struct CfbReader {
bytes : BytesView
sector_size : Int
mini_sector_size : Int
fat : Array[Int]
mini_fat : Array[Int]
entries : Array[CfbEntry]
mini_stream : Bytes
}
///|
fn sector_offset(sector_size : Int, index : Int) -> Int {
(index + 1) * sector_size
}
///|
fn read_sector_chain(
fat : Array[Int],
start : Int,
sector_count? : Int = fat.length(),
maximum_length? : Int = fat.length(),
cancelled? : () -> Bool = () => false,
) -> Array[Int] raise XlsxError {
check_read_cancelled(cancelled)
let chain : Array[Int] = []
let visited = Array::make(sector_count, false)
let mut current = start
while current != end_of_chain && current != free_sector {
check_read_cancelled(cancelled)
if current < 0 || current >= sector_count || current >= fat.length() {
raise InvalidEncryptionInfo(msg="cfb chain out of bounds")
}
if visited[current] {
raise InvalidEncryptionInfo(msg="cfb chain cycle")
}
if chain.length() >= maximum_length {
raise InvalidEncryptionInfo(msg="cfb chain too long")
}
visited[current] = true
chain.push(current)
current = fat[current]
}
chain
}
///|
fn cfb_read(
bytes : BytesView,
cancelled? : () -> Bool = () => false,
) -> CfbReader raise XlsxError {
check_read_cancelled(cancelled)
if bytes.length() < 512 {
raise InvalidEncryptionInfo(msg="cfb header too short")
}
for i in 0..<=7 {
if bytes[i] != ole_identifier[i] {
raise InvalidEncryptionInfo(msg="invalid cfb signature")
}
}
let sector_shift = read_u16_le(bytes, 0x1e)
let mini_sector_shift = read_u16_le(bytes, 0x20)
if (sector_shift != 9 && sector_shift != 12) || mini_sector_shift != 6 {
raise InvalidEncryptionInfo(msg="unsupported cfb sector size")
}
let sector_size = 1 << sector_shift
let mini_sector_size = 1 << mini_sector_shift
if bytes.length() < sector_size || bytes.length() % sector_size != 0 {
raise InvalidEncryptionInfo(msg="invalid cfb package size")
}
let sector_count = bytes.length() / sector_size - 1
if sector_count <= 0 {
raise InvalidEncryptionInfo(msg="cfb package has no sectors")
}
let entries_per_fat_sector = sector_size / 4
let minimum_fat_sectors = (sector_count + entries_per_fat_sector - 1) /
entries_per_fat_sector
// Some producers reserve spare FAT sectors. Permit bounded proportional
// slack while keeping the materialized FAT table linear in package size.
let maximum_fat_sectors = if minimum_fat_sectors * 2 + 1 < sector_count {
minimum_fat_sectors * 2 + 1
} else {
sector_count
}
let num_fat_u64 = read_u32_le(bytes, 0x2c).to_uint64()
if num_fat_u64 > maximum_fat_sectors.to_uint64() {
raise InvalidEncryptionInfo(msg="cfb FAT sector count exceeds package")
}
let num_fat = num_fat_u64.to_int()
let dir_start = read_i32_le(bytes, 0x30)
let mini_fat_start = read_i32_le(bytes, 0x3c)
let num_mini_fat_u64 = read_u32_le(bytes, 0x40).to_uint64()
if num_mini_fat_u64 > sector_count.to_uint64() {
raise InvalidEncryptionInfo(msg="cfb mini FAT sector count exceeds package")
}
let num_mini_fat = num_mini_fat_u64.to_int()
let difat_start = read_i32_le(bytes, 0x44)
let num_difat_u64 = read_u32_le(bytes, 0x48).to_uint64()
if num_difat_u64 > sector_count.to_uint64() {
raise InvalidEncryptionInfo(msg="cfb DIFAT sector count exceeds package")
}
let num_difat = num_difat_u64.to_int()
let fat_sectors : Array[Int] = []
let fat_sector_seen = Array::make(sector_count, false)
let mut pos = 0x4c
for _ in 0..<=108 {
let entry = read_i32_le(bytes, pos)
if entry != free_sector {
if entry < 0 || entry >= sector_count {
raise InvalidEncryptionInfo(msg="cfb FAT sector out of bounds")
}
if fat_sector_seen[entry] {
raise InvalidEncryptionInfo(msg="duplicate cfb FAT sector")
}
if fat_sectors.length() >= num_fat {
raise InvalidEncryptionInfo(msg="cfb FAT sector count mismatch")
}
fat_sector_seen[entry] = true
fat_sectors.push(entry)
}
pos = pos + 4
}
let mut difat_chain_start = difat_start
let mut difat_count = 0
let difat_seen = Array::make(sector_count, false)
while difat_chain_start != end_of_chain && difat_chain_start != free_sector {
check_read_cancelled(cancelled)
if difat_count >= num_difat {
raise InvalidEncryptionInfo(msg="cfb DIFAT chain longer than declared")
}
if difat_chain_start < 0 || difat_chain_start >= sector_count {
raise InvalidEncryptionInfo(msg="cfb DIFAT sector out of bounds")
}
if difat_seen[difat_chain_start] {
raise InvalidEncryptionInfo(msg="cfb DIFAT cycle")
}
difat_seen[difat_chain_start] = true
let offset = sector_offset(sector_size, difat_chain_start)
let difat_bytes = bytes[offset:offset + sector_size]
let mut idx = 0
for _ in 0..<(entries_per_fat_sector - 1) {
let entry = read_i32_le(difat_bytes, idx)
if entry != free_sector {
if entry < 0 || entry >= sector_count {
raise InvalidEncryptionInfo(msg="cfb FAT sector out of bounds")
}
if fat_sector_seen[entry] {
raise InvalidEncryptionInfo(msg="duplicate cfb FAT sector")
}
if fat_sectors.length() >= num_fat {
raise InvalidEncryptionInfo(msg="cfb FAT sector count mismatch")
}
fat_sector_seen[entry] = true
fat_sectors.push(entry)
}
idx = idx + 4
}
difat_chain_start = read_i32_le(
difat_bytes,
(entries_per_fat_sector - 1) * 4,
)
difat_count = difat_count + 1
}
if difat_count != num_difat {
raise InvalidEncryptionInfo(msg="cfb DIFAT sector count mismatch")
}
if fat_sectors.length() != num_fat {
raise InvalidEncryptionInfo(msg="cfb FAT sector count mismatch")
}
let fat : Array[Int] = []
for sector in fat_sectors {
check_read_cancelled(cancelled)
let offset = sector_offset(sector_size, sector)
let fat_bytes = bytes[offset:offset + sector_size]
let mut idx = 0
while idx < sector_size {
fat.push(read_i32_le(fat_bytes, idx))
idx = idx + 4
}
}
let dir_chain = read_sector_chain(
fat,
dir_start,
sector_count~,
maximum_length=sector_count,
cancelled~,
)
let dir_data : Array[Byte] = []
for sector in dir_chain {
check_read_cancelled(cancelled)
let offset = sector_offset(sector_size, sector)
let chunk = bytes[offset:offset + sector_size]
dir_data.append(chunk.to_array())
}
let entries : Array[CfbEntry] = []
let mut root_entry : CfbEntry = { name: "", type_id: 0, start: 0, size: 0 }
let mut offset = 0
while offset + 128 <= dir_data.length() {
if offset % 4096 == 0 {
check_read_cancelled(cancelled)
}
let entry_bytes = Bytes::from_array(dir_data[offset:offset + 128])
let name_len = read_u16_le(entry_bytes, 0x40)
if name_len > 64 || name_len % 2 != 0 {
raise InvalidEncryptionInfo(msg="invalid cfb name length")
}
let type_id = entry_bytes[0x42].to_int()
let start = read_i32_le(entry_bytes, 0x74)
let size_u64 = read_u64_le(entry_bytes, 0x78)
if size_u64 > bytes.length().to_uint64() {
raise InvalidEncryptionInfo(msg="cfb stream size exceeds package")
}
let size = size_u64.to_int()
let name = if name_len >= 2 {
let name_bytes = entry_bytes[0:name_len - 2]
@encoding/utf16.decode(name_bytes, ignore_bom=true, endianness=Little) catch {
_ => raise InvalidEncryptionInfo(msg="invalid cfb name")
}
} else {
""
}
let entry = { name, type_id, start, size }
if entries.length() == 0 {
root_entry = entry
}
entries.push(entry)
offset = offset + 128
}
let mini_fat : Array[Int] = []
if num_mini_fat > 0 && mini_fat_start != end_of_chain {
let mini_chain = read_sector_chain(
fat,
mini_fat_start,
sector_count~,
maximum_length=num_mini_fat,
cancelled~,
)
if mini_chain.length() != num_mini_fat {
raise InvalidEncryptionInfo(msg="cfb mini FAT sector count mismatch")
}
for sector in mini_chain {
check_read_cancelled(cancelled)
let offset = sector_offset(sector_size, sector)
let chunk = bytes[offset:offset + sector_size]
let mut idx = 0
while idx < sector_size {
mini_fat.push(read_i32_le(chunk, idx))
idx = idx + 4
}
}
}
let mut mini_stream : Bytes = Default::default()
if root_entry.size > 0 && root_entry.start != end_of_chain {
let root_sector_count = (root_entry.size + sector_size - 1) / sector_size
let root_chain = read_sector_chain(
fat,
root_entry.start,
sector_count~,
maximum_length=root_sector_count,
cancelled~,
)
let data : Array[Byte] = []
for sector in root_chain {
check_read_cancelled(cancelled)
let offset = sector_offset(sector_size, sector)
let chunk = bytes[offset:offset + sector_size]
data.append(chunk.to_array())
}
if data.length() < root_entry.size {
raise InvalidEncryptionInfo(msg="cfb mini stream is truncated")
}
if data.length() > root_entry.size {
data.truncate(root_entry.size)
}
mini_stream = Bytes::from_array(data)
}
{
// A BytesView retains its immutable backing allocation. Keep the package
// borrowed instead of duplicating the complete encrypted input after it
// has already passed all structural checks.
bytes,
sector_size,
mini_sector_size,
fat,
mini_fat,
entries,
mini_stream,
}
}
///|
fn CfbReader::read_stream(
self : CfbReader,
name : StringView,
cancelled? : () -> Bool = () => false,
) -> Bytes raise XlsxError {
check_read_cancelled(cancelled)
let mut entry : CfbEntry? = None
let target = name.to_owned()
for item in self.entries {
if item.name == target {
entry = Some(item)
break
}
}
match entry {
None => raise InvalidEncryptionInfo(msg="cfb entry not found")
Some(item) => {
if item.type_id != 2 {
raise InvalidEncryptionInfo(msg="cfb entry is not a stream")
}
if item.size == 0 {
return Default::default()
}
if item.size < 0x1000 {
if self.mini_stream.length() == 0 {
raise InvalidEncryptionInfo(msg="cfb mini stream missing")
}
let data : Array[Byte] = []
let mut current = item.start
let mut remaining = item.size
let mut guard_count = 0
while current != end_of_chain && current != free_sector && remaining > 0 {
check_read_cancelled(cancelled)
if current < 0 || current >= self.mini_fat.length() {
raise InvalidEncryptionInfo(msg="cfb mini chain out of bounds")
}
if self.mini_stream.length() < self.mini_sector_size ||
current >
(self.mini_stream.length() - self.mini_sector_size) /
self.mini_sector_size {
raise InvalidEncryptionInfo(msg="cfb mini stream out of bounds")
}
let offset = current * self.mini_sector_size
let chunk = self.mini_stream[offset:offset + self.mini_sector_size]
let take = if remaining < self.mini_sector_size {
remaining
} else {
self.mini_sector_size
}
data.append(chunk[:take].to_array())
remaining = remaining - take
current = self.mini_fat[current]
guard_count = guard_count + 1
if guard_count > self.mini_fat.length() {
raise InvalidEncryptionInfo(msg="cfb mini chain too long")
}
}
if remaining != 0 {
raise InvalidEncryptionInfo(msg="cfb mini stream is truncated")
}
Bytes::from_array(data)
} else {
let sector_count = self.bytes.length() / self.sector_size - 1
let stream_sector_count = (item.size + self.sector_size - 1) /
self.sector_size
let chain = read_sector_chain(
self.fat,
item.start,
sector_count~,
maximum_length=stream_sector_count,
cancelled~,
)
let data : Array[Byte] = []
for sector in chain {
check_read_cancelled(cancelled)
let offset = sector_offset(self.sector_size, sector)
let chunk = self.bytes[offset:offset + self.sector_size]
data.append(chunk.to_array())
}
if data.length() < item.size {
raise InvalidEncryptionInfo(msg="cfb stream is truncated")
}
if data.length() > item.size {
data.truncate(item.size)
}
Bytes::from_array(data)
}
}
}
}
///|
fn wb_write_i32_le(buf : Array[Byte], offset : Int, value : Int) -> Unit {
let v = value.reinterpret_as_uint()
buf[offset] = (v & 0xFF).to_byte()
buf[offset + 1] = ((v >> 8) & 0xFF).to_byte()
buf[offset + 2] = ((v >> 16) & 0xFF).to_byte()
buf[offset + 3] = ((v >> 24) & 0xFF).to_byte()
}
///|
fn wb_write_u16_le(buf : Array[Byte], offset : Int, value : Int) -> Unit {
let v = value.reinterpret_as_uint()
buf[offset] = (v & 0xFF).to_byte()
buf[offset + 1] = ((v >> 8) & 0xFF).to_byte()
}
///|
test "cfb wb: primitive guards and helper branches" {
let bad16 : Result[Int, Error] = Ok(read_u16_le(b"\x00", 0)) catch {
e => Err(e)
}
inspect(bad16 is Err(XlsxError::InvalidEncryptionInfo(_)), content="true")
let bad32 : Result[UInt, Error] = Ok(read_u32_le(b"\x00\x01", 0)) catch {
e => Err(e)
}
inspect(bad32 is Err(XlsxError::InvalidEncryptionInfo(_)), content="true")
let bad64 : Result[UInt64, Error] = Ok(read_u64_le(b"\x00\x01\x02", 0)) catch {
e => Err(e)
}
inspect(bad64 is Err(XlsxError::InvalidEncryptionInfo(_)), content="true")
let builder = cfb_builder_new()
builder.write_bytes(b"abc")
inspect(builder.position, content="3")
builder.put("Root Entry/a", b"1")
builder.paths[0] = "Root Entry"
builder.put("b", b"2")
inspect(builder.paths.length(), content="3")
inspect(cfb_compare("x/y", "x/z"), content="-1")
inspect(cfb_compare("x", "x/y"), content="-1")
inspect(parent_path("plain"), content="")
inspect(base_name("plain"), content="plain")
}
///|
test "cfb wb: prepare and read-chain guard branches" {
let builder = cfb_builder_new()
builder.put("Root Entry/a", b"a")
builder.put("Root Entry/b", b"b")
builder.sectors[1].type_id = 0
builder.sectors[2].cls_id = Default::default()
builder.prepare()
let mut found_b = false
for sector in builder.sectors {
if sector.name == "b" {
found_b = true
inspect(sector.cls_id.length(), content="16")
}
}
inspect(found_b, content="true")
let chain_oob : Result[Array[Int], Error] = Ok(read_sector_chain([5], 2)) catch {
e => Err(e)
}
inspect(chain_oob is Err(XlsxError::InvalidEncryptionInfo(_)), content="true")
let chain_cycle : Result[Array[Int], Error] = Ok(read_sector_chain([0], 0)) catch {
e => Err(e)
}
inspect(
chain_cycle is Err(XlsxError::InvalidEncryptionInfo(msg="cfb chain cycle")),
content="true",
)
let chain_long : Result[Array[Int], Error] = Ok(
read_sector_chain([1, 2, 1], 0, maximum_length=2),
) catch {
e => Err(e)
}
inspect(
chain_long
is Err(XlsxError::InvalidEncryptionInfo(msg="cfb chain too long")),
content="true",
)
}
///|
test "cfb wb: DIFAT declarations and cycles are bounded by package sectors" {
let builder = cfb_builder_new()
builder.put("EncryptionInfo", b"info")
builder.put("EncryptedPackage", Bytes::make(4096, b'x'))
let bytes = builder.write()
let excessive = bytes.to_array()
wb_write_i32_le(excessive, 0x48, -1)
let excessive_result : Result[CfbReader, Error] = Ok(
cfb_read(Bytes::from_array(excessive)),
) catch {
error => Err(error)
}
inspect(
excessive_result
is Err(
XlsxError::InvalidEncryptionInfo(
msg="cfb DIFAT sector count exceeds package"
)
),
content="true",
)
let cycle = bytes.to_array()
let difat_sector = cycle.length() / 512 - 1
let difat_offset = cycle.length()
cycle.append(Array::make(512, (0xff).to_byte()))
wb_write_i32_le(cycle, 0x44, difat_sector)
wb_write_i32_le(cycle, 0x48, 2)
wb_write_i32_le(cycle, difat_offset + 508, difat_sector)
let cycle_result : Result[CfbReader, Error] = Ok(
cfb_read(Bytes::from_array(cycle)),
) catch {
error => Err(error)
}
inspect(
cycle_result is Err(XlsxError::InvalidEncryptionInfo(msg="cfb DIFAT cycle")),
content="true",
)
}
///|
test "cfb wb: large builder/write and cfb_read difat branches" {
let builder = cfb_builder_new()
let big = Bytes::make(8_388_608, (0x41).to_byte())
builder.put("BigStream", big)
builder.sectors[1].cls_id = Default::default()
let bytes = builder.write()
inspect(bytes.length() > 0, content="true")
let reader = cfb_read(bytes) catch {
err => fail("expected big cfb to parse, got \{repr(err)}")
}
let stream = reader.read_stream("BigStream") catch {
err => fail("expected BigStream, got \{repr(err)}")
}
inspect(stream.length(), content="8388608")
let tweaked = bytes.to_array()
wb_write_i32_le(tweaked, 0x44, 0)
wb_write_i32_le(tweaked, 0x48, 0)
let parsed_tweaked : Result[CfbReader, Error] = Ok(
cfb_read(Bytes::from_array(tweaked)),
) catch {
e => Err(e)
}
inspect(parsed_tweaked is Ok(_) || parsed_tweaked is Err(_), content="true")
}
///|
test "cfb wb: cfb_read short/signature/invalid-name branches" {
let short_header : Result[CfbReader, Error] = Ok(cfb_read(b"short")) catch {
e => Err(e)
}
inspect(
short_header
is Err(XlsxError::InvalidEncryptionInfo(msg="cfb header too short")),
content="true",
)
let bad_sig : Array[Byte] = Array::make(512, (0x00).to_byte())
let invalid_sig : Result[CfbReader, Error] = Ok(
cfb_read(Bytes::from_array(bad_sig)),
) catch {
e => Err(e)
}
inspect(
invalid_sig
is Err(XlsxError::InvalidEncryptionInfo(msg="invalid cfb signature")),
content="true",
)
let builder = cfb_builder_new()
builder.put("Doc", b"x")
let bytes = builder.write()
let sector_shift = read_u16_le(bytes, 0x1e)
let sector_size = 1 << sector_shift
let dir_start = read_i32_le(bytes, 0x30)
let dir_offset = (dir_start + 1) * sector_size
let second_entry = dir_offset + 128
let invalid_name = bytes.to_array()
invalid_name[second_entry] = (0x00).to_byte()
invalid_name[second_entry + 1] = (0xD8).to_byte()
wb_write_u16_le(invalid_name, second_entry + 0x40, 4)
let bad_name_result : Result[CfbReader, Error] = Ok(
cfb_read(Bytes::from_array(invalid_name)),
) catch {
e => Err(e)
}
inspect(
bad_name_result
is Err(XlsxError::InvalidEncryptionInfo(msg="invalid cfb name")),
content="true",
)
}
///|
test "cfb wb: read_stream guard branches" {
let not_found : CfbReader = {
bytes: b"",
sector_size: 512,
mini_sector_size: 64,
fat: [],
mini_fat: [],
entries: [],
mini_stream: Default::default(),
}
let missing : Result[Bytes, Error] = Ok(not_found.read_stream("missing")) catch {
e => Err(e)
}
inspect(
missing is Err(XlsxError::InvalidEncryptionInfo(msg="cfb entry not found")),
content="true",
)
let not_stream : CfbReader = {
bytes: b"",
sector_size: 512,
mini_sector_size: 64,
fat: [],
mini_fat: [],
entries: [{ name: "x", type_id: 5, start: 0, size: 1 }],
mini_stream: Default::default(),
}
let not_stream_result : Result[Bytes, Error] = Ok(not_stream.read_stream("x")) catch {
e => Err(e)
}
inspect(
not_stream_result
is Err(XlsxError::InvalidEncryptionInfo(msg="cfb entry is not a stream")),
content="true",
)
let zero_size : CfbReader = {
bytes: b"",
sector_size: 512,
mini_sector_size: 64,
fat: [],
mini_fat: [],
entries: [{ name: "x", type_id: 2, start: 0, size: 0 }],
mini_stream: Default::default(),
}
inspect(zero_size.read_stream("x"), content="b\"\"")
let mini_missing : CfbReader = {
bytes: b"",
sector_size: 512,
mini_sector_size: 64,
fat: [],
mini_fat: [],
entries: [{ name: "x", type_id: 2, start: 0, size: 1 }],
mini_stream: Default::default(),
}
let mini_missing_result : Result[Bytes, Error] = Ok(
mini_missing.read_stream("x"),
) catch {
e => Err(e)
}
inspect(
mini_missing_result
is Err(XlsxError::InvalidEncryptionInfo(msg="cfb mini stream missing")),
content="true",
)
let mini_oob : CfbReader = {
bytes: b"",
sector_size: 512,
mini_sector_size: 64,
fat: [],
mini_fat: [0],
entries: [{ name: "x", type_id: 2, start: 1, size: 1 }],
mini_stream: Bytes::make(64, (0x00).to_byte()),
}
let mini_oob_result : Result[Bytes, Error] = Ok(mini_oob.read_stream("x")) catch {
e => Err(e)
}
inspect(
mini_oob_result
is Err(XlsxError::InvalidEncryptionInfo(msg="cfb mini chain out of bounds")),
content="true",
)
let mini_chain_oob : CfbReader = {
bytes: b"",
sector_size: 512,
mini_sector_size: 64,
fat: [],
mini_fat: [],
entries: [{ name: "x", type_id: 2, start: 0, size: 1 }],
mini_stream: Bytes::make(64, (0x00).to_byte()),
}
let mini_chain_oob_result : Result[Bytes, Error] = Ok(
mini_chain_oob.read_stream("x"),
) catch {
e => Err(e)
}
inspect(
mini_chain_oob_result
is Err(XlsxError::InvalidEncryptionInfo(msg="cfb mini chain out of bounds")),
content="true",
)
let mini_chain_long : CfbReader = {
bytes: b"",
sector_size: 512,
mini_sector_size: 64,
fat: [],
mini_fat: [0],
entries: [{ name: "x", type_id: 2, start: 0, size: 128 }],
mini_stream: Bytes::make(64, (0x00).to_byte()),
}
let mini_chain_long_result : Result[Bytes, Error] = Ok(
mini_chain_long.read_stream("x"),
) catch {
e => Err(e)
}
inspect(
mini_chain_long_result
is Err(XlsxError::InvalidEncryptionInfo(msg="cfb mini chain too long")),
content="true",
)
}
///|
test "cfb wb: locate/write-msat/write-directory residual branches" {
let medium = cfb_builder_new()
medium.put("Medium", Bytes::make(65_024, (0x61).to_byte()))
medium.prepare()
let location = medium.locate()
inspect(location[1], content="0")
inspect(location[2], content="2")
let msat = cfb_builder_new()
msat.write_msat([1, 2, 400, 0, 0, 0, 0, 0])
inspect(msat.position > 0, content="true")
let directory = cfb_builder_new()
directory.sectors[0].cls_id = Default::default()
directory.write_directory_entry([1, 0, 0, 0, 1, 0, 0, 0])
inspect(directory.position > 0, content="true")
}