cloudflare-soos 2.3.1

Helper tool for Cloudflare's enhanced HTTP/2 and HTTP/3 prioritization, which makes progressive images load faster. Supports JPEG, GIF, and PNG.
Documentation
use self::reader::read_info;
use std::cell::Cell;

pub use crate::error::*;
use crate::Scans;
use std::io;
use std::io::BufRead;
use std::io::Read;

mod common;
mod reader;
mod traits;

struct CountingReader<'counter, 'data> {
    buf: u8,
    pub inner: &'data [u8],
    pub consumed: &'counter Cell<usize>,
}

const MAX_GIF_SIZE: usize = 200_000;

impl BufRead for CountingReader<'_, '_> {
    #[inline(always)]
    fn fill_buf(&mut self) -> io::Result<&[u8]> {
        if let Some((s, rest)) = self.inner.split_first() {
            self.inner = rest;
            self.buf = *s;
        } else {
            return Err(io::ErrorKind::UnexpectedEof.into());
        }
        Ok(std::slice::from_ref(&self.buf))
    }

    #[inline(always)]
    fn consume(&mut self, amt: usize) {
        self.consumed.set(self.consumed.get() + amt)
    }
}

impl Read for CountingReader<'_, '_> {
    fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
        unimplemented!()
    }
}

pub fn scans(input_file: &[u8]) -> Result<Scans> {
    let file_size = input_file.len();

    let bytes_read = Cell::new(0);
    let r = CountingReader {
        buf: 0,
        inner: &input_file[..MAX_GIF_SIZE.min(file_size)], // chop the file to avoid expensive decoding of large images
        consumed: &bytes_read,
    };
    match read_info(r) {
        Ok(mut d) => {
            let metadata_end = bytes_read.get();
            let frame = d.next_frame_info().unwrap_or_default();
            let was_interlaced = frame.map_or(false, |f| f.was_interlaced);
            let frame_render_start = bytes_read.get();
            let _ = d.read_into_buffer();
            let first_scan_end = bytes_read.get();
            Ok(Scans {
                metadata_end: Some(metadata_end),
                frame_render_start: Some(frame_render_start),
                first_scan_end: Some(if was_interlaced { frame_render_start + (first_scan_end - frame_render_start) / 2 } else { first_scan_end }),
                good_scan_end: Some(first_scan_end),
                file_size,
            })
        },
        Err(self::reader::DecodingError::Io(e)) => Err(Error::Io(e)),
        Err(self::reader::DecodingError::Format) => Err(Error::Format("can't parse GIF")),
    }
}