///|
fn read_u16_le(bytes : BytesView, offset : Int) -> UInt raise ZipError {
if offset < 0 || offset + 2 > bytes.length() {
raise OutOfBounds(offset~)
}
let b0 = bytes[offset].to_uint()
let b1 = bytes[offset + 1].to_uint()
b0 | (b1 << 8)
}
///|
fn read_u32_le(bytes : BytesView, offset : Int) -> UInt raise ZipError {
if offset < 0 || offset + 4 > bytes.length() {
raise OutOfBounds(offset~)
}
let b0 = bytes[offset].to_uint()
let b1 = bytes[offset + 1].to_uint()
let b2 = bytes[offset + 2].to_uint()
let b3 = bytes[offset + 3].to_uint()
b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
}
///|
fn gzip_push_u32_le(out : FixedByteOutput, value : UInt) -> Unit raise ZipError {
out.write_byte((value & 0xFF).to_byte())
out.write_byte(((value >> 8) & 0xFF).to_byte())
out.write_byte(((value >> 16) & 0xFF).to_byte())
out.write_byte(((value >> 24) & 0xFF).to_byte())
}
///|
fn skip_cstring(bytes : BytesView, start : Int) -> Int? {
for i in start.. Bytes raise ZipError {
// RFC 1952
if bytes.length() < 18 {
raise UnsupportedFeature(msg="invalid gzip data")
}
if bytes[0] != 0x1F || bytes[1] != 0x8B {
raise UnsupportedFeature(msg="invalid gzip header")
}
let compression_method = bytes[2].to_int()
if compression_method != 8 {
raise UnsupportedFeature(msg="unsupported gzip method")
}
let flags = bytes[3].to_int()
let mut idx = 10
if (flags & 0x04) != 0 {
let xlen = read_u16_le(bytes, idx).reinterpret_as_int()
idx = idx + 2 + xlen
}
if (flags & 0x08) != 0 {
idx = match skip_cstring(bytes, idx) {
Some(v) => v
None => raise UnsupportedFeature(msg="invalid gzip name")
}
}
if (flags & 0x10) != 0 {
idx = match skip_cstring(bytes, idx) {
Some(v) => v
None => raise UnsupportedFeature(msg="invalid gzip comment")
}
}
if (flags & 0x02) != 0 {
idx = idx + 2
}
if idx < 0 || idx > bytes.length() - 8 {
raise UnsupportedFeature(msg="invalid gzip data")
}
let trailer_crc = read_u32_le(bytes, bytes.length() - 8)
let trailer_isize = read_u32_le(bytes, bytes.length() - 4)
let compressed = bytes[idx:bytes.length() - 8]
// Allow a legitimately large declared size, but keep a hard ceiling so a
// lying tiny stream cannot expand without bound (decompression bomb).
let declared_isize = if trailer_isize <= (0x7FFFFFFF : UInt) {
trailer_isize.reinterpret_as_int()
} else {
0x7FFFFFFF
}
let max_output = if declared_isize > deflate_max_output_default {
declared_isize
} else {
deflate_max_output_default
}
let decoded = deflate_decode(compressed, max_output~)
let expected_isize = (decoded.length() & 0xFFFFFFFF).reinterpret_as_uint()
if trailer_isize != expected_isize {
raise UnsupportedFeature(msg="gzip size mismatch")
}
let actual_crc = crc32(decoded)
if trailer_crc != actual_crc {
raise UnsupportedFeature(msg="gzip crc mismatch")
}
decoded
}
///|
pub fn gzip(bytes : BytesView) -> Bytes raise ZipError {
let compressed_size = deflate_encoded_size(bytes)
if compressed_size > 0x7fffffff - 18 {
raise UnsupportedFeature(msg="gzip output size exceeds the Int range")
}
let out = FixedByteOutput::allocated(10 + compressed_size + 8)
// ID1 ID2 CM FLG MTIME(4) XFL OS
out.write_byte(0x1F)
out.write_byte(0x8B)
out.write_byte(0x08)
out.write_byte(0x00)
out.write_byte(0x00)
out.write_byte(0x00)
out.write_byte(0x00)
out.write_byte(0x00)
out.write_byte(0x00)
out.write_byte(0xFF)
let actual_size = deflate_encode_to_output(bytes, out)
if actual_size != compressed_size {
raise UnsupportedFeature(msg="DEFLATE sizing and gzip emission disagreed")
}
let crc = crc32(bytes)
gzip_push_u32_le(out, crc)
gzip_push_u32_le(out, (bytes.length() & 0xFFFFFFFF).reinterpret_as_uint())
out.finish()
}
///|
test "gzip wb: helper read bounds guards" {
try read_u16_le(b"\x00", 0) catch {
e => inspect(e is OutOfBounds(_), content="true")
} noraise {
_ => fail("expected read_u16_le to raise")
}
try read_u32_le(b"\x00\x01", 0) catch {
e => inspect(e is OutOfBounds(_), content="true")
} noraise {
_ => fail("expected read_u32_le to raise")
}
}