leyline-http 0.1.5

HTTP client that impersonates Chrome, Firefox, and Safari on the wire: TLS (JA3/JA4), HTTP/2, and HTTP/3 fingerprints
Documentation
use super::*;

pub(in crate::quic::pool) fn write_pending_request_bodies(
    h3: &mut quiche::h3::Connection,
    conn: &mut quiche::Connection,
    streams: &mut HashMap<u64, H3Stream>,
) {
    let mut failed = Vec::new();
    for (&stream_id, stream) in streams.iter_mut() {
        if stream.body_write_pending() && write_request_body(h3, conn, stream_id, stream) {
            failed.push(stream_id);
        }
    }
    for stream_id in failed {
        streams.remove(&stream_id);
    }
}

pub(in crate::quic::pool) fn write_request_body(
    h3: &mut quiche::h3::Connection,
    conn: &mut quiche::Connection,
    stream_id: u64,
    stream: &mut H3Stream,
) -> bool {
    let Err(message) = send_queued_body(h3, conn, stream_id, stream) else {
        return false;
    };
    abort_stream(
        h3,
        conn,
        stream_id,
        stream,
        quiche::h3::WireErrorCode::RequestCancelled,
    );
    stream.deliver_error(message);
    true
}

fn send_queued_body(
    h3: &mut quiche::h3::Connection,
    conn: &mut quiche::Connection,
    stream_id: u64,
    stream: &mut H3Stream,
) -> Result<(), String> {
    while let Some(front) = stream.out_chunks.front() {
        let remaining = &front[stream.out_offset..];
        let last_chunk = stream.body_eof && stream.out_chunks.len() == 1;
        match h3.send_body(conn, stream_id, remaining, last_chunk) {
            Ok(0) | Err(quiche::h3::Error::Done) | Err(quiche::h3::Error::StreamBlocked) => {
                return Ok(());
            }
            Ok(written) => {
                stream.out_offset += written;
                if let Some(credit) = &stream.upload_credit {
                    credit.add_permits(written);
                }
                if stream.out_offset >= front.len() {
                    stream.out_chunks.pop_front();
                    stream.out_offset = 0;
                    if last_chunk {
                        stream.fin_sent = true;
                    }
                }
            }
            Err(e) => return Err(format!("h3 send_body: {e}")),
        }
    }

    if stream.body_eof && !stream.fin_sent {
        match h3.send_body(conn, stream_id, &[], true) {
            Ok(_) => stream.fin_sent = true,
            Err(quiche::h3::Error::Done) | Err(quiche::h3::Error::StreamBlocked) => {}
            Err(e) => return Err(format!("h3 send_body fin: {e}")),
        }
    }
    Ok(())
}

pub(in crate::quic::pool) fn on_request_body_chunk(
    h3: &mut quiche::h3::Connection,
    conn: &mut quiche::Connection,
    streams: &mut HashMap<u64, H3Stream>,
    chunk: H3BodyChunk,
) {
    match chunk {
        H3BodyChunk::Chunk { stream_id, data } => {
            if let Some(stream) = streams.get_mut(&stream_id) {
                stream.out_chunks.push_back(data);
            }
        }
        H3BodyChunk::Eof { stream_id, error } => {
            let Some(stream) = streams.get_mut(&stream_id) else {
                return;
            };
            match error {
                None => stream.body_eof = true,
                Some(e) => {
                    abort_stream(
                        h3,
                        conn,
                        stream_id,
                        stream,
                        quiche::h3::WireErrorCode::GeneralProtocolError,
                    );
                    stream.deliver_request_body_error(e);
                    streams.remove(&stream_id);
                }
            }
        }
    }
}