Coverage Report

Created: 2025-12-28 06:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}