use crate::util::PartialBuffer;
use std::io;
#[derive(Debug, Default)]
struct Flags {
ascii: bool,
crc: bool,
extra: bool,
filename: bool,
comment: bool,
}
#[derive(Debug, Default)]
pub(super) struct Header {
flags: Flags,
}
#[derive(Debug)]
enum State {
Fixed(PartialBuffer<[u8; 10]>),
ExtraLen(PartialBuffer<[u8; 2]>),
Extra(PartialBuffer<Vec<u8>>),
Filename(Vec<u8>),
Comment(Vec<u8>),
Crc(PartialBuffer<[u8; 2]>),
Done,
}
impl Default for State {
fn default() -> Self {
State::Fixed(<_>::default())
}
}
#[derive(Debug, Default)]
pub(super) struct Parser {
state: State,
header: Header,
}
impl Header {
fn parse(input: &[u8; 10]) -> io::Result<Self> {
if input[0..3] != [0x1f, 0x8b, 0x08] {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Invalid gzip header",
));
}
let flag = input[3];
let flags = Flags {
ascii: (flag & 0b0000_0001) != 0,
crc: (flag & 0b0000_0010) != 0,
extra: (flag & 0b0000_0100) != 0,
filename: (flag & 0b0000_1000) != 0,
comment: (flag & 0b0001_0000) != 0,
};
Ok(Header { flags })
}
}
impl Parser {
pub(super) fn input(
&mut self,
input: &mut PartialBuffer<impl AsRef<[u8]>>,
) -> io::Result<Option<Header>> {
loop {
match &mut self.state {
State::Fixed(data) => {
data.copy_unwritten_from(input);
if data.unwritten().is_empty() {
self.header = Header::parse(&data.take().into_inner())?;
self.state = State::ExtraLen(<_>::default());
} else {
return Ok(None);
}
}
State::ExtraLen(data) => {
if !self.header.flags.extra {
self.state = State::Filename(<_>::default());
continue;
}
data.copy_unwritten_from(input);
if data.unwritten().is_empty() {
let len = u16::from_le_bytes(data.take().into_inner());
self.state = State::Extra(vec![0; usize::from(len)].into());
} else {
return Ok(None);
}
}
State::Extra(data) => {
data.copy_unwritten_from(input);
if data.unwritten().is_empty() {
self.state = State::Filename(<_>::default());
} else {
return Ok(None);
}
}
State::Filename(data) => {
if !self.header.flags.filename {
self.state = State::Comment(<_>::default());
continue;
}
if let Some(len) = memchr::memchr(0, input.unwritten()) {
data.extend_from_slice(&input.unwritten()[..len]);
input.advance(len + 1);
self.state = State::Comment(<_>::default());
} else {
data.extend_from_slice(input.unwritten());
input.advance(input.unwritten().len());
return Ok(None);
}
}
State::Comment(data) => {
if !self.header.flags.comment {
self.state = State::Crc(<_>::default());
continue;
}
if let Some(len) = memchr::memchr(0, input.unwritten()) {
data.extend_from_slice(&input.unwritten()[..len]);
input.advance(len + 1);
self.state = State::Crc(<_>::default());
} else {
data.extend_from_slice(input.unwritten());
input.advance(input.unwritten().len());
return Ok(None);
}
}
State::Crc(data) => {
if !self.header.flags.crc {
self.state = State::Done;
return Ok(Some(std::mem::take(&mut self.header)));
}
data.copy_unwritten_from(input);
if data.unwritten().is_empty() {
self.state = State::Done;
return Ok(Some(std::mem::take(&mut self.header)));
} else {
return Ok(None);
}
}
State::Done => {
return Err(io::Error::new(
io::ErrorKind::Other,
"parser used after done",
));
}
};
}
}
}