diff --git a/src/request.rs b/src/request.rs index 822decb..a59f68f 100644 --- a/src/request.rs +++ b/src/request.rs @@ -6,6 +6,7 @@ pub use titan::Titan; use anyhow::{bail, Result}; +/// https://geminiprotocol.net/docs/protocol-specification.gmi#requests pub enum Request<'a> { Gemini(Gemini), Titan(Titan<'a>), @@ -22,7 +23,6 @@ impl<'a> Request<'a> { None => bail!("Empty request"), } } - pub fn into_bytes(self) -> Vec { match self { Self::Gemini(this) => this.into_bytes(), diff --git a/src/request/gemini.rs b/src/request/gemini.rs index fe74c8f..4bc7c84 100644 --- a/src/request/gemini.rs +++ b/src/request/gemini.rs @@ -7,24 +7,34 @@ pub struct Gemini { impl Gemini { pub fn from_bytes(buffer: &[u8]) -> Result { - let mut l: usize = 0; - for b in buffer { - l += 1; - if l > 1024 { - bail!("Max header length reached!") - } - if *b == b'\r' { - continue; - } - if *b == b'\n' { - break; - } + if buffer.len() > 1024 { + bail!("Header bytes length reached") + } + match buffer.iter().position(|&b| b == b'\r') { + Some(n) => { + if buffer.get(n + 1).is_some_and(|&b| b == b'\n') { + Ok(Self { + url: String::from_utf8(buffer[..n].to_vec())?, + }) + } else { + bail!("LF byte not found") + } + } + None => bail!("CR byte not found"), } - Ok(Self { - url: String::from_utf8(buffer[..l].to_vec())?, - }) } pub fn into_bytes(self) -> Vec { - self.url.to_string().into_bytes() + let mut bytes = Vec::with_capacity(self.url.len() + 2); + bytes.extend(self.url.into_bytes()); + bytes.extend(b"\r\n"); + bytes } } + +#[test] +fn test() { + const REQUEST: &[u8] = "gemini://geminiprotocol.net\r\n".as_bytes(); + let request = Gemini::from_bytes(REQUEST).unwrap(); + assert_eq!(request.url, "gemini://geminiprotocol.net"); + assert_eq!(request.into_bytes(), REQUEST); +}