use async_std::io::Bytes; use async_std::io::Cursor; use async_std::io::Read; use async_std::io::ReadExt; use async_std::stream::StreamExt; use async_std::task::block_on; use core::fmt::Display; use core::fmt::Formatter; use core::str::FromStr; use std::collections::BTreeMap; use std::result::Result; use thiserror::Error; #[derive(Debug, Clone, Default)] pub struct Dpip { pub name: Option, pub properties: BTreeMap, } #[derive(Error, Debug)] pub enum DpipParseError { #[error("Invalid tag start character '{0}'")] InvalidStart(char), #[error("Invalid tag end character '{0}'")] InvalidEnd(char), #[error("Invalid value start character '{0}'")] InvalidValueStart(char), #[error("Tag value expected escaped quote or end but found '{0}'")] InvalidValueEnd(char), #[error("Unexpected space in key")] InvalidKeySpace, #[error("Unexpected quote in key")] InvalidKeyQuote, #[error("Stream ended before reading tag")] EOF, #[error(transparent)] IO(#[from] async_std::io::Error), #[error(transparent)] Utf8(#[from] std::string::FromUtf8Error), } async fn read_value(bytes: &mut Bytes) -> Result where T: Read + Unpin, { let mut value_bytes = Vec::new(); match bytes.next().await.ok_or(DpipParseError::EOF)?? { b'\'' => {} byte => return Err(DpipParseError::InvalidValueStart(byte.into())), } while let Some(b) = match bytes.next().await.ok_or(DpipParseError::EOF)?? { b'\'' => match bytes.next().await.ok_or(DpipParseError::EOF)?? { b'\'' => Some(b'\''), b' ' => None, b => return Err(DpipParseError::InvalidValueEnd(b.into())), }, b => Some(b), } { value_bytes.push(b); } let name = String::from_utf8(value_bytes)?; Ok(name) } async fn read_initial_key_value( bytes: &mut Bytes, ) -> Result<(String, Option), DpipParseError> where T: Read + Unpin, { let mut name_bytes = Vec::new(); while let Some(b) = match bytes.next().await.ok_or(DpipParseError::EOF)?? { b' ' => { let name = String::from_utf8(name_bytes)?; return Ok((name, None)); } b'=' => { let name = String::from_utf8(name_bytes)?; let value = read_value(bytes).await?; return Ok((name, Some(value))); } b => Some(b), } { name_bytes.push(b); } Err(DpipParseError::EOF) } async fn read_key_value(bytes: &mut Bytes) -> Result, DpipParseError> where T: Read + Unpin, { let mut key_bytes = Vec::new(); while let Some(b) = match bytes.next().await.ok_or(DpipParseError::EOF)?? { b'\'' => { if key_bytes.is_empty() { return Ok(None); } else { return Err(DpipParseError::InvalidKeyQuote); } } b' ' => { return Err(DpipParseError::InvalidKeySpace); } b'=' => None, b => Some(b), } { key_bytes.push(b); } let key = String::from_utf8(key_bytes)?; let value = read_value(bytes).await?; Ok(Some((key, value))) } impl Dpip { /// Create a dpip command with the given command name pub fn cmd(cmd: &str) -> Self { let mut dpip = Self::default(); dpip.properties.insert("cmd".to_string(), cmd.to_string()); dpip } /// Create a dpip command for serving a page pub fn start_send_page(url: &str) -> Self { let mut tag = Dpip::cmd("start_send_page"); tag.properties.insert("url".to_string(), url.to_string()); tag } /// Create a dpip command for sending a status message pub fn send_status_message(msg: &str) -> Self { let mut tag = Dpip::cmd("send_status_message"); tag.properties.insert("msg".to_string(), msg.to_string()); tag } /// Read a dpip tag. /// /// Format (from dillo/dpip/dpip.c): /// ``` /// "<"[*alpha] *("="QuoteQuote) " "Quote">" /// ``` pub async fn read(reader: &mut T) -> Result where T: Read + Unpin, { let mut map = BTreeMap::new(); let mut bytes = reader.bytes(); match bytes.next().await.ok_or(DpipParseError::EOF)?? { b'<' => {} byte => return Err(DpipParseError::InvalidStart(byte.into())), } let tag_name = match read_initial_key_value(&mut bytes).await? { (key, Some(value)) => { map.insert(key, value); None } (key, None) => Some(key), }; while let Some((key, value)) = read_key_value(&mut bytes).await? { map.insert(key, value); } match bytes.next().await.ok_or(DpipParseError::EOF)?? { b'>' => {} byte => return Err(DpipParseError::InvalidEnd(byte.into())), } Ok(Dpip { name: tag_name, properties: map, }) } } impl FromStr for Dpip { type Err = DpipParseError; fn from_str(tag: &str) -> std::result::Result { let mut reader = Cursor::new(tag.as_bytes().to_vec()); block_on(Dpip::read(&mut reader)) } } impl Display for Dpip { fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { write!(f, "<")?; if let Some(name) = &self.name { write!(f, "{} ", name)?; } for (key, value) in &self.properties { write!(f, "{}='{}' ", key, value.replace("'", "''"))?; } write!(f, "'>") } } #[cfg(test)] mod tests { use super::*; #[test] fn dpip() { block_on(dpip_async()); } async fn dpip_async() { let tags = b"' '>"; let mut reader = Cursor::new(tags.to_vec()); let tag = Dpip::read(&mut reader).await.unwrap(); assert_eq!(tag.to_string(), ""); let tag = Dpip::read(&mut reader).await.unwrap(); assert_eq!(tag.to_string(), ""); let tag = Dpip::read(&mut reader).await.unwrap(); assert_eq!(tag.to_string(), "' '>"); let tag = Dpip::read(&mut reader).await.unwrap(); assert_eq!(tag.to_string(), ""); let tag: Dpip = "".parse().unwrap(); assert_eq!(tag.properties["key"], "wouldn't it be nice"); let tags = b""; let mut reader = Cursor::new(tags.to_vec()); let tag = Dpip::read(&mut reader).await.unwrap(); assert_eq!(tag.properties["a"], "isn't that='cool' yes"); let tags = b"' '>"; let mut reader = Cursor::new(tags.to_vec()); Dpip::read(&mut reader).await.unwrap_err(); } }