pub const CODE: &[u8] = b"20"; pub struct Meta { pub mime: String, } impl Meta { /// Build `Self` from UTF-8 meta bytes /// * expected buffer includes leading status code, message, CRLF pub fn from_bytes(buffer: &[u8]) -> Result { use crate::tool::Header; use regex::Regex; use std::str::from_utf8; let h = buffer.header_bytes()?; if h.get(..2) .is_none_or(|c| c[0] != CODE[0] || c[1] != CODE[1]) { bail!("Invalid status code") } Ok(Self { mime: match Regex::new(r"^([^\/]+\/[^\s;]+)")?.captures(from_utf8(&h[3..])?) { Some(c) => match c.get(1) { Some(m) => m.as_str().to_string(), None => bail!("Content type required"), }, None => bail!("Could not parse content type"), }, }) } /// Convert `Self` into UTF-8 bytes presentation pub fn into_bytes(self) -> Vec { self.to_bytes() } pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::with_capacity(3 + self.mime.len() + 2); bytes.extend(CODE); bytes.push(b' '); bytes.extend(self.mime.as_bytes()); bytes.extend([b'\r', b'\n']); bytes } } #[test] fn test() { const BYTES: &[u8] = "20 text/gemini\r\nDATA".as_bytes(); let meta = Meta::from_bytes(BYTES).unwrap(); assert_eq!(meta.mime, "text/gemini".to_string()); assert_eq!(meta.into_bytes(), BYTES[..BYTES.len() - 4]); // skip DATA } use anyhow::{bail, Result};