/rust/registry/src/index.crates.io-1949cf8c6b5b557f/dbus-0.9.7/src/message/parser.rs
Line | Count | Source |
1 | | use std::error::Error as stdError; |
2 | | use std::fmt::{Debug, Formatter, Display}; |
3 | | use crate::message::MatchRule; |
4 | | use crate::{MessageType, Path}; |
5 | | use std::convert::TryFrom; |
6 | | use crate::strings::{Interface, BusName, Member}; |
7 | | |
8 | | // Our grammar: |
9 | | // rules: rule (, rule)* |
10 | | // rule: sender | type | interface | member | path | path_namespace | destination | arg | arg_path |
11 | | // bool : 'true' | 'false' |
12 | | // type: "type" "=" message_type |
13 | | // message_type: "'signal'" | "'method_call'" | "'method_return'" | "'error'" |
14 | | // sender: "sender" "=" string |
15 | | // interface: "interface" "=" string |
16 | | // member: "member" "=" string |
17 | | // path: "path" "=" string |
18 | | // path_namespace: "path_namespace" "=" string |
19 | | // destination: "destination" "=" string |
20 | | // arg: "arg" 0-63 "=" string |
21 | | // arg_path: "arg" 0-63 "path" "=" string |
22 | | // eavesdrop: "eavesdrop" "=" bool |
23 | | |
24 | | |
25 | | #[derive(Clone, Debug)] |
26 | | /// Error type that covers errors that might happen during parsing. |
27 | | pub enum Error { |
28 | | /// The type specified in the match rule is unknown |
29 | | UnknownType, |
30 | | /// The key is wrong / unsupported |
31 | | UnknownKey, |
32 | | /// Boolean could not be parsed |
33 | | BadBoolean, |
34 | | /// Error that occured while converting a string to a DBus format |
35 | | BadConversion(String), |
36 | | } |
37 | | |
38 | | impl Display for Error { |
39 | 0 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
40 | 0 | write!(f, "Error while parsing MatchRule: ")?; |
41 | 0 | match self { |
42 | | Error::UnknownType => { |
43 | 0 | write!(f, "Unsupported message type") |
44 | | } |
45 | | Error::UnknownKey => { |
46 | 0 | write!(f, "Unknown key used") |
47 | | } |
48 | | Error::BadBoolean => { |
49 | 0 | write!(f, "Got bad boolean value") |
50 | | } |
51 | 0 | Error::BadConversion(err) => { |
52 | 0 | write!(f, "Error while converting: {}", err) |
53 | | } |
54 | | } |
55 | 0 | } |
56 | | } |
57 | | |
58 | | impl stdError for Error {} |
59 | | |
60 | | /// Key-Value-pair |
61 | | pub type TokenRule<'a> = (&'a str, &'a str); |
62 | | /// Fixed size buffer for match rule tokens |
63 | | pub type TokenBuffer<'a> = Vec<TokenRule<'a>>; |
64 | | |
65 | | #[derive(Clone, Debug)] |
66 | | /// Tokenizes a match rule into key-value-pairs |
67 | | struct Tokenizer<'a> { |
68 | | text: &'a str, |
69 | | } |
70 | | |
71 | | impl<'a> Tokenizer<'a> { |
72 | | /// Builds a new tokenizer for the input &str |
73 | 0 | pub fn new(text: &'a str) -> Self { |
74 | 0 | Self { |
75 | 0 | text, |
76 | 0 | } |
77 | 0 | } |
78 | | |
79 | | /// Parse the key part of the key-value-pair. This is rather easy as all keys are rather |
80 | | /// easily defined and may only contain `[a-z0-9]` so we can simply split at `'='`. |
81 | 0 | fn key(&self) -> (&'a str, &'a str) { |
82 | 0 | let index = self.text.find('=').unwrap_or_else(|| self.text.len()); |
83 | | |
84 | 0 | ((&self.text[..index]).trim(), &self.text[index + 1..]) |
85 | 0 | } |
86 | | |
87 | | /// Parses values as generic strings. |
88 | | /// This does not do any validation (yet) with regards to supported characters. |
89 | 0 | fn value(&self) -> (&'a str, &'a str) { |
90 | 0 | let mut i = 0; |
91 | 0 | let mut quoted = false; |
92 | 0 | let mut escape = false; |
93 | | |
94 | 0 | for c in self.text.chars() { |
95 | 0 | match c { |
96 | 0 | '\'' if !escape => { |
97 | 0 | quoted = !quoted; |
98 | 0 | } |
99 | 0 | ',' if !quoted => { |
100 | 0 | break; |
101 | | } |
102 | 0 | '\\' if !quoted => { |
103 | 0 | escape = true; |
104 | 0 | i += 1; |
105 | 0 | continue; |
106 | | } |
107 | 0 | _ => {} |
108 | | } |
109 | 0 | escape = false; |
110 | | |
111 | 0 | i += 1; |
112 | | } |
113 | | |
114 | | // Skip comma if there is still space in the buffer |
115 | 0 | let j = if self.text.len() == i { i } else { i + 1 }; |
116 | 0 | ((&self.text[..i]).trim(), &self.text[j..]) |
117 | 0 | } |
118 | | |
119 | | /// Tokenizes a string into key-value-pairs |
120 | 0 | pub fn tokenize(&mut self) -> Result<TokenBuffer<'a>, Error> { |
121 | 0 | let mut rules = TokenBuffer::new(); |
122 | | |
123 | 0 | while !self.text.is_empty() { |
124 | 0 | let (key, rest) = self.key(); |
125 | 0 | self.text = rest; |
126 | 0 | let (value, rest) = self.value(); |
127 | 0 | self.text = rest; |
128 | 0 | rules.push((key, value)) |
129 | | } |
130 | 0 | Ok(rules) |
131 | 0 | } |
132 | | } |
133 | | |
134 | | #[derive(Clone, Debug)] |
135 | | /// Helper struct for parsing MatchRule's |
136 | | pub struct Parser<'a> { |
137 | | tokens: TokenBuffer<'a>, |
138 | | } |
139 | | |
140 | | impl<'a> Parser<'a> { |
141 | | /// Builds a new parser after tokenizing the input string `text`. |
142 | 0 | pub fn new(text: &'a str) -> Result<Self, Error> { |
143 | | Ok(Self { |
144 | 0 | tokens: Tokenizer::new(text).tokenize()? |
145 | | }) |
146 | 0 | } |
147 | | |
148 | | /// Cleans a string from the string syntax allowed by DBus. This includes concatenating |
149 | | /// things like `''\'''` to `'`. DBus strings sort of work like in a POSIX shell and |
150 | | /// concatenation is implied. There is only one escape sequence and that is in an unquoted |
151 | | /// substring a backslash may escape an ASCII apostrophe (U+0027). |
152 | | /// This method is the only one within the parser that allocates and in theory could be |
153 | | /// rewritten to taking a `&mut str` instead of returning a `String` since all |
154 | | /// strings are |output| <= |buf.len()|. |
155 | 0 | fn clean_string(&self, buf: &str) -> String { |
156 | 0 | let mut quoted = false; |
157 | 0 | let mut escape = false; |
158 | 0 | let mut outbuf = String::with_capacity(buf.len()); |
159 | | |
160 | 0 | for c in buf.chars() { |
161 | 0 | match c { |
162 | 0 | '\'' if !escape => { |
163 | 0 | quoted = !quoted; |
164 | 0 | } |
165 | 0 | '\\' if !quoted => { |
166 | 0 | escape = true; |
167 | 0 | continue; |
168 | | } |
169 | 0 | c if c.is_whitespace() && !quoted => { |
170 | 0 | continue; |
171 | | } |
172 | 0 | c => { |
173 | 0 | outbuf.push(c); |
174 | 0 | } |
175 | | } |
176 | | } |
177 | | |
178 | 0 | outbuf |
179 | 0 | } |
180 | | |
181 | | /// Parses key-value-pair tokens into a MatchRule |
182 | 0 | pub fn parse(&self) -> Result<MatchRule<'a>, Error> { |
183 | 0 | let mut match_rule = MatchRule::new(); |
184 | | |
185 | 0 | for &(key, raw_value) in &self.tokens { |
186 | 0 | let value = self.clean_string(raw_value); |
187 | 0 | match key { |
188 | 0 | "type" => { |
189 | 0 | match_rule = match_rule.with_type(MessageType::try_from(value.as_str()).map_err(|_| Error::UnknownType)?); |
190 | 0 | Ok(()) |
191 | | } |
192 | 0 | "interface" => { |
193 | 0 | match_rule.interface = Some(Interface::new(value).map_err(Error::BadConversion)?); |
194 | 0 | Ok(()) |
195 | | } |
196 | 0 | "sender" => { |
197 | 0 | match_rule.sender = Some(BusName::new(value).map_err(Error::BadConversion)?); |
198 | 0 | Ok(()) |
199 | | } |
200 | 0 | "member" => { |
201 | 0 | match_rule.member = Some(Member::new(value).map_err(Error::BadConversion)?); |
202 | 0 | Ok(()) |
203 | | } |
204 | 0 | "path" => { |
205 | 0 | match_rule.path = Some(Path::new(value).map_err(Error::BadConversion)?); |
206 | 0 | Ok(()) |
207 | | } |
208 | 0 | "path_namespace" => { |
209 | 0 | match_rule.path = Some(Path::new(value).map_err(Error::BadConversion)?); |
210 | 0 | match_rule.path_is_namespace = true; |
211 | 0 | Ok(()) |
212 | | } |
213 | 0 | "eavesdrop" => { |
214 | 0 | match raw_value { |
215 | 0 | "'true'" | "true" => { |
216 | 0 | match_rule = match_rule.with_eavesdrop(); |
217 | 0 | Ok(()) |
218 | | } |
219 | 0 | "'false'" | "false" => { |
220 | 0 | Ok(()) |
221 | | } |
222 | | _ => { |
223 | 0 | Err(Error::BadBoolean) |
224 | | } |
225 | | } |
226 | | } |
227 | | _ => { |
228 | | // Args and Destination are not supported yet. |
229 | 0 | Err(Error::UnknownKey) |
230 | | } |
231 | 0 | }?; |
232 | | } |
233 | | |
234 | 0 | Ok(match_rule) |
235 | 0 | } |
236 | | } |
237 | | |
238 | | #[cfg(test)] |
239 | | mod tests { |
240 | | use crate::message::parser::Error; |
241 | | use crate::message::MatchRule; |
242 | | |
243 | | #[test] |
244 | | fn test_tokenizer() -> Result<(), Error> { |
245 | | let mr = MatchRule::parse(r"interface='org.freedesktop.Notifications',member='Notify'")?; |
246 | | assert_eq!(mr.match_str(), "interface='org.freedesktop.Notifications',member='Notify'"); |
247 | | let mr = MatchRule::parse(r"interface='org.mpris.MediaPlayer2.Player' , path= /org/mpris/MediaPlayer2,member='Notify', eavesdrop ='true'")?; |
248 | | assert_eq!(mr.match_str(), "path='/org/mpris/MediaPlayer2',interface='org.mpris.MediaPlayer2.Player',member='Notify',eavesdrop='true'"); |
249 | | Ok(()) |
250 | | } |
251 | | |
252 | | #[test] |
253 | | fn test_malformed() { |
254 | | assert!(MatchRule::parse(r"interface='org.freedesktop.Notifications',member=").is_err()); |
255 | | } |
256 | | |
257 | | #[test] |
258 | | fn test_spurious_comma() { |
259 | | assert!(MatchRule::parse(r"interface='org.freedesktop.Notifications',").is_ok()); |
260 | | } |
261 | | } |