git ssb

0+

cel-desktop / dillo-did



Tree: 0a2159981e51aa2801cd12c60af8d5dac9a253ee

Files: 0a2159981e51aa2801cd12c60af8d5dac9a253ee / src / dpip.rs

6990 bytesRaw
1use async_std::io::Bytes;
2use async_std::io::Cursor;
3use async_std::io::Read;
4use async_std::io::ReadExt;
5use async_std::stream::StreamExt;
6use async_std::task::block_on;
7use core::fmt::Display;
8use core::fmt::Formatter;
9use core::str::FromStr;
10use std::collections::BTreeMap;
11use std::result::Result;
12use thiserror::Error;
13
14#[derive(Debug, Clone, Default)]
15pub struct Dpip {
16 pub name: Option<String>,
17 pub properties: BTreeMap<String, String>,
18}
19
20#[derive(Error, Debug)]
21pub enum DpipParseError {
22 #[error("Invalid tag start character '{0}'")]
23 InvalidStart(char),
24 #[error("Invalid tag end character '{0}'")]
25 InvalidEnd(char),
26 #[error("Invalid value start character '{0}'")]
27 InvalidValueStart(char),
28 #[error("Tag value expected escaped quote or end but found '{0}'")]
29 InvalidValueEnd(char),
30 #[error("Unexpected space in key")]
31 InvalidKeySpace,
32 #[error("Unexpected quote in key")]
33 InvalidKeyQuote,
34 #[error("Stream ended before reading tag")]
35 EOF,
36 #[error(transparent)]
37 IO(#[from] async_std::io::Error),
38 #[error(transparent)]
39 Utf8(#[from] std::string::FromUtf8Error),
40}
41
42async fn read_value<T>(bytes: &mut Bytes<T>) -> Result<String, DpipParseError>
43where
44 T: Read + Unpin,
45{
46 let mut value_bytes = Vec::new();
47 match bytes.next().await.ok_or(DpipParseError::EOF)?? {
48 b'\'' => {}
49 byte => return Err(DpipParseError::InvalidValueStart(byte.into())),
50 }
51 while let Some(b) = match bytes.next().await.ok_or(DpipParseError::EOF)?? {
52 b'\'' => match bytes.next().await.ok_or(DpipParseError::EOF)?? {
53 b'\'' => Some(b'\''),
54 b' ' => None,
55 b => return Err(DpipParseError::InvalidValueEnd(b.into())),
56 },
57 b => Some(b),
58 } {
59 value_bytes.push(b);
60 }
61 let name = String::from_utf8(value_bytes)?;
62 Ok(name)
63}
64
65async fn read_initial_key_value<T>(
66 bytes: &mut Bytes<T>,
67) -> Result<(String, Option<String>), DpipParseError>
68where
69 T: Read + Unpin,
70{
71 let mut name_bytes = Vec::new();
72 while let Some(b) = match bytes.next().await.ok_or(DpipParseError::EOF)?? {
73 b' ' => {
74 let name = String::from_utf8(name_bytes)?;
75 return Ok((name, None));
76 }
77 b'=' => {
78 let name = String::from_utf8(name_bytes)?;
79 let value = read_value(bytes).await?;
80 return Ok((name, Some(value)));
81 }
82 b => Some(b),
83 } {
84 name_bytes.push(b);
85 }
86 Err(DpipParseError::EOF)
87}
88
89async fn read_key_value<T>(bytes: &mut Bytes<T>) -> Result<Option<(String, String)>, DpipParseError>
90where
91 T: Read + Unpin,
92{
93 let mut key_bytes = Vec::new();
94 while let Some(b) = match bytes.next().await.ok_or(DpipParseError::EOF)?? {
95 b'\'' => {
96 if key_bytes.is_empty() {
97 return Ok(None);
98 } else {
99 return Err(DpipParseError::InvalidKeyQuote);
100 }
101 }
102 b' ' => {
103 return Err(DpipParseError::InvalidKeySpace);
104 }
105 b'=' => None,
106 b => Some(b),
107 } {
108 key_bytes.push(b);
109 }
110 let key = String::from_utf8(key_bytes)?;
111 let value = read_value(bytes).await?;
112 Ok(Some((key, value)))
113}
114
115impl Dpip {
116 /// Create a dpip command with the given command name
117 pub fn cmd(cmd: &str) -> Self {
118 let mut dpip = Self::default();
119 dpip.properties.insert("cmd".to_string(), cmd.to_string());
120 dpip
121 }
122
123 /// Create a dpip command for serving a page
124 pub fn start_send_page(url: &str) -> Self {
125 let mut tag = Dpip::cmd("start_send_page");
126 tag.properties.insert("url".to_string(), url.to_string());
127 tag
128 }
129
130 /// Create a dpip command for sending a status message
131 pub fn send_status_message(msg: &str) -> Self {
132 let mut tag = Dpip::cmd("send_status_message");
133 tag.properties.insert("msg".to_string(), msg.to_string());
134 tag
135 }
136
137 /// Read a dpip tag.
138 ///
139 /// Format (from dillo/dpip/dpip.c):
140 /// ```
141 /// "<"[*alpha] *(<name>"="Quote<escaped_value>Quote) " "Quote">"
142 /// ```
143 pub async fn read<T>(reader: &mut T) -> Result<Dpip, DpipParseError>
144 where
145 T: Read + Unpin,
146 {
147 let mut map = BTreeMap::new();
148 let mut bytes = reader.bytes();
149 match bytes.next().await.ok_or(DpipParseError::EOF)?? {
150 b'<' => {}
151 byte => return Err(DpipParseError::InvalidStart(byte.into())),
152 }
153 let tag_name = match read_initial_key_value(&mut bytes).await? {
154 (key, Some(value)) => {
155 map.insert(key, value);
156 None
157 }
158 (key, None) => Some(key),
159 };
160 while let Some((key, value)) = read_key_value(&mut bytes).await? {
161 map.insert(key, value);
162 }
163 match bytes.next().await.ok_or(DpipParseError::EOF)?? {
164 b'>' => {}
165 byte => return Err(DpipParseError::InvalidEnd(byte.into())),
166 }
167
168 Ok(Dpip {
169 name: tag_name,
170 properties: map,
171 })
172 }
173}
174
175impl FromStr for Dpip {
176 type Err = DpipParseError;
177 fn from_str(tag: &str) -> std::result::Result<Self, Self::Err> {
178 let mut reader = Cursor::new(tag.as_bytes().to_vec());
179 block_on(Dpip::read(&mut reader))
180 }
181}
182
183impl Display for Dpip {
184 fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
185 write!(f, "<")?;
186 if let Some(name) = &self.name {
187 write!(f, "{} ", name)?;
188 }
189 for (key, value) in &self.properties {
190 write!(f, "{}='{}' ", key, value.replace("'", "''"))?;
191 }
192 write!(f, "'>")
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn dpip() {
202 block_on(dpip_async());
203 }
204 async fn dpip_async() {
205 let tags = b"<a='b' '><c='d' e='f g' '><h='>' '><i='j''k' '>";
206 let mut reader = Cursor::new(tags.to_vec());
207 let tag = Dpip::read(&mut reader).await.unwrap();
208 assert_eq!(tag.to_string(), "<a='b' '>");
209 let tag = Dpip::read(&mut reader).await.unwrap();
210 assert_eq!(tag.to_string(), "<c='d' e='f g' '>");
211 let tag = Dpip::read(&mut reader).await.unwrap();
212 assert_eq!(tag.to_string(), "<h='>' '>");
213 let tag = Dpip::read(&mut reader).await.unwrap();
214 assert_eq!(tag.to_string(), "<i='j''k' '>");
215
216 let tag: Dpip = "<key='wouldn''t it be nice' '>".parse().unwrap();
217 assert_eq!(tag.properties["key"], "wouldn't it be nice");
218
219 let tags = b"<a='isn''t that=''cool'' yes' '>";
220 let mut reader = Cursor::new(tags.to_vec());
221 let tag = Dpip::read(&mut reader).await.unwrap();
222 assert_eq!(tag.properties["a"], "isn't that='cool' yes");
223
224 let tags = b"<bad = '>' '>";
225 let mut reader = Cursor::new(tags.to_vec());
226 Dpip::read(&mut reader).await.unwrap_err();
227 }
228}
229

Built with git-ssb-web