use super::Error;
use crate::Format;
const FHCRC: u8 = 0x02;
const FEXTRA: u8 = 0x04;
const FNAME: u8 = 0x08;
const FCOMMENT: u8 = 0x10;
const FRESERVED: u8 = 0xE0;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Field {
Fixed,
ExtraLen,
Extra(usize),
Name,
Comment,
HeaderCrc,
Done,
}
#[derive(Clone, Debug)]
pub(super) struct HeaderParser {
field: Field,
buf: [u8; 10],
buf_len: usize,
gzip_flags: u8,
}
impl HeaderParser {
pub(super) const fn new() -> Self {
HeaderParser {
field: Field::Fixed,
buf: [0; 10],
buf_len: 0,
gzip_flags: 0,
}
}
fn collect(&mut self, input: &[u8], ip: &mut usize, need: usize) -> bool {
let n = (need - self.buf_len).min(input.len() - *ip);
self.buf[self.buf_len..self.buf_len + n].copy_from_slice(&input[*ip..*ip + n]);
self.buf_len += n;
*ip += n;
self.buf_len == need
}
pub(super) fn parse(
&mut self,
format: Format,
input: &[u8],
ip: &mut usize,
) -> Result<bool, Error> {
let bad = Err(Error::BadData);
loop {
match self.field {
Field::Done => return Ok(true),
Field::Fixed => match format {
Format::Deflate => self.field = Field::Done,
Format::Zlib => {
if !self.collect(input, ip, 2) {
return Ok(false);
}
zlib_header([self.buf[0], self.buf[1]])?;
self.field = Field::Done;
}
Format::Gzip => {
if !self.collect(input, ip, 10) {
return Ok(false);
}
let b = &self.buf;
if b[0] != 0x1F || b[1] != 0x8B || b[2] != 8 || b[3] & FRESERVED != 0 {
return bad;
}
self.gzip_flags = b[3];
self.buf_len = 0;
self.field = Field::ExtraLen;
}
},
Field::ExtraLen => {
if self.gzip_flags & FEXTRA == 0 {
self.field = Field::Name;
continue;
}
if !self.collect(input, ip, 2) {
return Ok(false);
}
let xlen = u16::from_le_bytes([self.buf[0], self.buf[1]]);
self.buf_len = 0;
self.field = Field::Extra(xlen as usize);
}
Field::Extra(n) => {
let skip = n.min(input.len() - *ip);
*ip += skip;
if skip < n {
self.field = Field::Extra(n - skip);
return Ok(false);
}
self.field = Field::Name;
}
Field::Name | Field::Comment => {
let (flag, next) = if self.field == Field::Name {
(FNAME, Field::Comment)
} else {
(FCOMMENT, Field::HeaderCrc)
};
if self.gzip_flags & flag != 0 {
let Some(i) = input[*ip..].iter().position(|&b| b == 0) else {
*ip = input.len();
return Ok(false);
};
*ip += i + 1;
}
self.field = next;
}
Field::HeaderCrc => {
if self.gzip_flags & FHCRC != 0 && !self.collect(input, ip, 2) {
return Ok(false);
}
self.field = Field::Done;
}
}
}
}
}
pub(super) fn zlib_header(hdr: [u8; 2]) -> Result<(), Error> {
let hdr = u16::from_be_bytes(hdr);
let cm = (hdr >> 8) & 0xF;
let cinfo = hdr >> 12;
let fdict = (hdr >> 5) & 1;
if !hdr.is_multiple_of(31) || cm != 8 || cinfo > 7 {
Err(Error::BadData)
} else if fdict != 0 {
Err(Error::PresetDictionary)
} else {
Ok(())
}
}
#[cfg_attr(not(feature = "alloc"), allow(dead_code))]
pub(super) fn body_bounds(format: Format, input: &[u8]) -> Result<(usize, usize), Error> {
let body_end = input
.len()
.checked_sub(trailer_len(format))
.ok_or(Error::BadData)?;
let header = &input[..body_end];
let start = match format {
Format::Deflate => 0,
Format::Zlib => match header.first_chunk() {
Some(&hdr) => {
zlib_header(hdr)?;
2
}
None => return Err(Error::BadData),
},
Format::Gzip => {
let mut pos = 0;
if !HeaderParser::new().parse(format, header, &mut pos)? {
return Err(Error::BadData);
}
pos
}
};
Ok((start, body_end))
}
pub(super) const fn trailer_len(format: Format) -> usize {
match format {
Format::Deflate => 0,
Format::Zlib => 4,
Format::Gzip => 8,
}
}
#[derive(Clone, Copy, Debug)]
pub(super) struct Check {
format: Format,
len_only: bool,
value: u32,
len: u64,
}
impl Check {
pub(super) const fn new(format: Format) -> Self {
let value = match format {
Format::Zlib => 1,
_ => 0,
};
Check {
format,
len_only: false,
value,
len: 0,
}
}
pub(super) const fn len_only(format: Format) -> Self {
Check {
len_only: true,
..Check::new(format)
}
}
#[inline]
pub(super) fn update(&mut self, data: &[u8]) {
self.len += data.len() as u64;
self.value = match self.format {
Format::Deflate => 0,
Format::Zlib => crate::checksum::adler32(self.value, data),
Format::Gzip => crate::checksum::crc32(self.value, data),
};
}
pub(super) fn count(&mut self, n: usize) {
self.len += n as u64;
}
#[cfg_attr(not(feature = "alloc"), allow(dead_code))]
pub(super) fn verify_at(&self, input: &[u8], pos: usize) -> Result<usize, Error> {
let end = pos + trailer_len(self.format);
self.verify(&input[pos..end])?;
Ok(end)
}
pub(super) fn verify(&self, t: &[u8]) -> Result<(), Error> {
let word = |i: usize| [t[i], t[i + 1], t[i + 2], t[i + 3]];
let ok = match self.format {
Format::Deflate => true,
Format::Zlib => self.len_only || u32::from_be_bytes(word(0)) == self.value,
Format::Gzip => {
(self.len_only || u32::from_le_bytes(word(0)) == self.value)
&& u32::from_le_bytes(word(4)) == self.len as u32
}
};
if ok {
Ok(())
} else {
Err(Error::ChecksumMismatch)
}
}
}