1use std::error::Error as stdError;
2use std::fmt::{Debug, Formatter, Display};
3use crate::message::MatchRule;
4use crate::{MessageType, Path};
5use std::convert::TryFrom;
6use 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.
27pub 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
38impl Display for Error {
39 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
40 write!(f, "Error while parsing MatchRule: ")?;
41 match self {
42 Error::UnknownType => {
43 write!(f, "Unsupported message type")
44 }
45 Error::UnknownKey => {
46 write!(f, "Unknown key used")
47 }
48 Error::BadBoolean => {
49 write!(f, "Got bad boolean value")
50 }
51 Error::BadConversion(err: &String) => {
52 write!(f, "Error while converting: {}", err)
53 }
54 }
55 }
56}
57
58impl stdError for Error {}
59
60/// Key-Value-pair
61pub type TokenRule<'a> = (&'a str, &'a str);
62/// Fixed size buffer for match rule tokens
63pub type TokenBuffer<'a> = Vec<TokenRule<'a>>;
64
65#[derive(Clone, Debug)]
66/// Tokenizes a match rule into key-value-pairs
67struct Tokenizer<'a> {
68 text: &'a str,
69}
70
71impl<'a> Tokenizer<'a> {
72 /// Builds a new tokenizer for the input &str
73 pub fn new(text: &'a str) -> Self {
74 Self {
75 text,
76 }
77 }
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 fn key(&self) -> (&'a str, &'a str) {
82 let index = self.text.find('=').unwrap_or_else(|| self.text.len());
83
84 ((&self.text[..index]).trim(), &self.text[index + 1..])
85 }
86
87 /// Parses values as generic strings.
88 /// This does not do any validation (yet) with regards to supported characters.
89 fn value(&self) -> (&'a str, &'a str) {
90 let mut i = 0;
91 let mut quoted = false;
92 let mut escape = false;
93
94 for c in self.text.chars() {
95 match c {
96 '\'' if !escape => {
97 quoted = !quoted;
98 }
99 ',' if !quoted => {
100 break;
101 }
102 '\\' if !quoted => {
103 escape = true;
104 i += 1;
105 continue;
106 }
107 _ => {}
108 }
109 escape = false;
110
111 i += 1;
112 }
113
114 // Skip comma if there is still space in the buffer
115 let j = if self.text.len() == i { i } else { i + 1 };
116 ((&self.text[..i]).trim(), &self.text[j..])
117 }
118
119 /// Tokenizes a string into key-value-pairs
120 pub fn tokenize(&mut self) -> Result<TokenBuffer<'a>, Error> {
121 let mut rules = TokenBuffer::new();
122
123 while !self.text.is_empty() {
124 let (key, rest) = self.key();
125 self.text = rest;
126 let (value, rest) = self.value();
127 self.text = rest;
128 rules.push((key, value))
129 }
130 Ok(rules)
131 }
132}
133
134#[derive(Clone, Debug)]
135/// Helper struct for parsing MatchRule's
136pub struct Parser<'a> {
137 tokens: TokenBuffer<'a>,
138}
139
140impl<'a> Parser<'a> {
141 /// Builds a new parser after tokenizing the input string `text`.
142 pub fn new(text: &'a str) -> Result<Self, Error> {
143 Ok(Self {
144 tokens: Tokenizer::new(text).tokenize()?
145 })
146 }
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 fn clean_string(&self, buf: &str) -> String {
156 let mut quoted = false;
157 let mut escape = false;
158 let mut outbuf = String::with_capacity(buf.len());
159
160 for c in buf.chars() {
161 match c {
162 '\'' if !escape => {
163 quoted = !quoted;
164 }
165 '\\' if !quoted => {
166 escape = true;
167 continue;
168 }
169 c if c.is_whitespace() && !quoted => {
170 continue;
171 }
172 c => {
173 outbuf.push(c);
174 }
175 }
176 }
177
178 outbuf
179 }
180
181 /// Parses key-value-pair tokens into a MatchRule
182 pub fn parse(&self) -> Result<MatchRule<'a>, Error> {
183 let mut match_rule = MatchRule::new();
184
185 for &(key, raw_value) in &self.tokens {
186 let value = self.clean_string(raw_value);
187 match key {
188 "type" => {
189 match_rule = match_rule.with_type(MessageType::try_from(value.as_str()).map_err(|_| Error::UnknownType)?);
190 Ok(())
191 }
192 "interface" => {
193 match_rule.interface = Some(Interface::new(value).map_err(Error::BadConversion)?);
194 Ok(())
195 }
196 "sender" => {
197 match_rule.sender = Some(BusName::new(value).map_err(Error::BadConversion)?);
198 Ok(())
199 }
200 "member" => {
201 match_rule.member = Some(Member::new(value).map_err(Error::BadConversion)?);
202 Ok(())
203 }
204 "path" => {
205 match_rule.path = Some(Path::new(value).map_err(Error::BadConversion)?);
206 Ok(())
207 }
208 "path_namespace" => {
209 match_rule.path = Some(Path::new(value).map_err(Error::BadConversion)?);
210 match_rule.path_is_namespace = true;
211 Ok(())
212 }
213 "eavesdrop" => {
214 match raw_value {
215 "'true'" | "true" => {
216 match_rule = match_rule.with_eavesdrop();
217 Ok(())
218 }
219 "'false'" | "false" => {
220 Ok(())
221 }
222 _ => {
223 Err(Error::BadBoolean)
224 }
225 }
226 }
227 _ => {
228 // Args and Destination are not supported yet.
229 Err(Error::UnknownKey)
230 }
231 }?;
232 }
233
234 Ok(match_rule)
235 }
236}
237
238#[cfg(test)]
239mod 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}