use anyhow::{bail, Result}; pub const CODE: &[u8] = b"61"; /// [Certificate authorization](https://geminiprotocol.net/docs/protocol-specification.gmi#status-61-certificate-not-authorized) pub struct NotAuthorized { pub message: Option, } impl NotAuthorized { /// Build `Self` from UTF-8 header bytes /// * expected buffer includes leading status code, message, CRLF pub fn from_bytes(buffer: &[u8]) -> Result { // calculate length once let len = buffer.len(); // validate headers for this response type if !(3..=1024).contains(&len) { bail!("Unexpected header length") } if buffer .get(..2) .is_none_or(|c| c[0] != CODE[0] || c[1] != CODE[1]) { bail!("Invalid status code") } // collect data bytes let mut m = Vec::with_capacity(len); for b in buffer[3..].iter() { if *b == b'\r' { continue; } if *b == b'\n' { break; } m.push(*b) } Ok(Self { message: String::from_utf8(m).map(|m| if m.is_empty() { None } else { Some(m) })?, }) } /// Convert `Self` into UTF-8 bytes presentation pub fn into_bytes(self) -> Vec { match self.message { Some(message) => { let mut bytes = Vec::with_capacity(message.len() + 5); bytes.extend(CODE); bytes.push(b' '); bytes.extend(message.into_bytes()); bytes.extend([b'\r', b'\n']); bytes } None => { let mut bytes = Vec::with_capacity(4); bytes.extend(CODE); bytes.extend([b'\r', b'\n']); bytes } } } } #[test] fn test() { let request = format!("61 message\r\n"); let source = NotAuthorized::from_bytes(request.as_bytes()).unwrap(); assert_eq!(source.message, Some("message".to_string())); assert_eq!(source.into_bytes(), request.as_bytes()); }