httpio 0.2.4

A transport-agnostic, async HTTP/1.1 client library for any runtime.
Documentation
use crate::enums::char_set::CharSet;
use crate::enums::http_error::HttpError;
use crate::structures::http_body_part::HttpBodyPart;
use crate::utils::parse_util;
use async_regex::{CRLF, read_until_pattern_async};
use futures::AsyncBufRead;
use std::io::{self, Write};

pub const MULTIPART_EPILOGUE_MARKER: &[u8] = &[45, 45, 13, 10];

#[derive(Clone)]
pub struct HttpMultipartBody {
    pub parts: Vec<HttpBodyPart>,
    pub boundary: String,
    pub subtype: String,
    pub preamble: Vec<u8>,
    pub epilogue: Vec<u8>,
}

impl HttpMultipartBody {
    pub fn new(parts: Vec<HttpBodyPart>, boundary: String) -> Self {
        HttpMultipartBody {
            parts,
            boundary,
            subtype: "form-data".to_string(),
            preamble: vec![],
            epilogue: vec![],
        }
    }

    pub async fn read(
        mut reader: impl AsyncBufRead + Unpin,
        boundary: &str,
        subtype: &str,
    ) -> Result<HttpMultipartBody, HttpError> {
        let delimiter = format!("\r\n--{}", boundary);
        let mut buf = Vec::new();
        let (_, len) = read_until_pattern_async(&mut reader, &delimiter, &mut buf).await?;
        let preamble = buf[..len - delimiter.len()].to_vec();
        let mut epilogue: Vec<u8> = vec![];
        buf.truncate(0);
        let mut parts: Vec<HttpBodyPart> = Vec::new();
        let (_, mut len) = read_until_pattern_async(&mut reader, &delimiter, &mut buf).await?;
        loop {
            if len > 1 && &buf[..MULTIPART_EPILOGUE_MARKER.len()] == MULTIPART_EPILOGUE_MARKER {
                epilogue = buf[MULTIPART_EPILOGUE_MARKER.len()..].to_vec();
                break;
            }
            if len == 0 {
                break;
            }
            parts.push(HttpBodyPart::read(&mut &buf[2..len - delimiter.len()]).await?);
            buf.truncate(0);
            (_, len) = read_until_pattern_async(&mut reader, &delimiter, &mut buf).await?;
        }

        Ok(HttpMultipartBody {
            parts,
            boundary: boundary.to_string(),
            subtype: subtype.to_string(),
            preamble,
            epilogue,
        })
    }

    pub fn write_to<W: Write>(&self, mut writer: W) -> io::Result<()> {
        for part in self.parts.iter() {
            writer.write_all(b"--")?;
            writer.write_all(self.boundary.as_bytes())?;
            writer.write_all(CRLF)?;
            part.write_to(&mut writer)?;
            writer.write_all(CRLF)?;
        }

        writer.write_all(b"--")?;
        writer.write_all(self.boundary.as_bytes())?;
        writer.write_all(b"--")?;
        writer.write_all(CRLF)?;

        Ok(())
    }

    pub fn as_bytes(&self) -> Vec<u8> {
        let boundary_overhead = self.boundary.len() + 6;
        let estimated_capacity = self
            .parts
            .iter()
            .map(|p| p.content.len() + 128)
            .sum::<usize>()
            + boundary_overhead * (self.parts.len() + 1)
            + 4;
        let mut result = Vec::with_capacity(estimated_capacity);
        self.write_to(&mut result).expect("Vec write cannot fail");
        result
    }

    pub fn to_string(&self, charset: CharSet) -> Result<String, HttpError> {
        let bytes = self.as_bytes();
        let str = parse_util::bytes_to_string(&bytes, charset)?;
        Ok(str.to_string())
    }
}