blob: 98a1f83ebb8683d2a2993be69ec9aea7bd0c9373 [file] [log] [blame]
// Copyright 2018 The Wuffs Authors.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// SPDX-License-Identifier: Apache-2.0 OR MIT
use "std/crc32"
use "std/deflate"
pub status "#bad checksum"
pub status "#bad compression method"
pub status "#bad encoding flags"
pub status "#bad header"
pub status "#truncated input"
pub const DECODER_DST_HISTORY_RETAIN_LENGTH_MAX_INCL_WORST_CASE : base.u64 = 0
// TODO: reference deflate.DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE.
pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 1
pub struct decoder? implements base.io_transformer(
ignore_checksum : base.bool,
checksum : crc32.ieee_hasher,
flate : deflate.decoder,
util : base.utility,
)
pub func decoder.get_quirk(key: base.u32) base.u64 {
if (args.key == base.QUIRK_IGNORE_CHECKSUM) and this.ignore_checksum {
return 1
}
return 0
}
pub func decoder.set_quirk!(key: base.u32, value: base.u64) base.status {
if args.key == base.QUIRK_IGNORE_CHECKSUM {
this.ignore_checksum = args.value > 0
return ok
}
return base."#unsupported option"
}
pub func decoder.dst_history_retain_length() base.optional_u63 {
return this.util.make_optional_u63(has_value: true, value: 0)
}
pub func decoder.workbuf_len() base.range_ii_u64 {
return this.util.make_range_ii_u64(
min_incl: DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE,
max_incl: DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE)
}
pub func decoder.transform_io?(dst: base.io_writer, src: base.io_reader, workbuf: slice base.u8) {
var status : base.status
while true {
status =? this.do_transform_io?(dst: args.dst, src: args.src, workbuf: args.workbuf)
if (status == base."$short read") and args.src.is_closed() {
return "#truncated input"
}
yield? status
} endwhile
}
pri func decoder.do_transform_io?(dst: base.io_writer, src: base.io_reader, workbuf: slice base.u8) {
var c8 : base.u8
var flags : base.u8
var xlen : base.u16
var mark : base.u64
var checksum_have : base.u32
var decoded_length_have : base.u32
var status : base.status
var checksum_want : base.u32
var decoded_length_want : base.u32
// Read the header.
c8 = args.src.read_u8?()
if c8 <> 0x1F {
return "#bad header"
}
c8 = args.src.read_u8?()
if c8 <> 0x8B {
return "#bad header"
}
c8 = args.src.read_u8?()
if c8 <> 0x08 {
return "#bad compression method"
}
flags = args.src.read_u8?()
// TODO: API for returning the header's MTIME field.
args.src.skip_u32?(n: 6)
// Handle FEXTRA.
if (flags & 0x04) <> 0 {
xlen = args.src.read_u16le?()
args.src.skip_u32?(n: xlen as base.u32)
}
// Handle FNAME.
//
// TODO: API for returning the header's FNAME field. This might require
// converting ISO 8859-1 to UTF-8. We may also want to cap the UTF-8
// filename length to NAME_MAX, which is 255.
if (flags & 0x08) <> 0 {
while true {
c8 = args.src.read_u8?()
if c8 == 0 {
break
}
} endwhile
}
// Handle FCOMMENT.
if (flags & 0x10) <> 0 {
while true {
c8 = args.src.read_u8?()
if c8 == 0 {
break
}
} endwhile
}
// Handle FHCRC.
if (flags & 0x02) <> 0 {
args.src.skip_u32?(n: 2)
}
// Reserved flags bits must be zero.
if (flags & 0xE0) <> 0 {
return "#bad encoding flags"
}
// Decode and checksum the DEFLATE-encoded payload.
while true {
mark = args.dst.mark()
status =? this.flate.transform_io?(dst: args.dst, src: args.src, workbuf: args.workbuf)
if not this.ignore_checksum {
checksum_have = this.checksum.update_u32!(x: args.dst.since(mark: mark))
decoded_length_have ~mod+= (args.dst.count_since(mark: mark) & 0xFFFF_FFFF) as base.u32
}
if status.is_ok() {
break
}
yield? status
} endwhile
checksum_want = args.src.read_u32le?()
decoded_length_want = args.src.read_u32le?()
if (not this.ignore_checksum) and
((checksum_have <> checksum_want) or (decoded_length_have <> decoded_length_want)) {
return "#bad checksum"
}
}