1use base64::prelude::*;
2use chrono::DateTime;
3use xml::{EventReader, EventWriter};
4
5use super::Llsd;
6
7#[derive(Debug, Clone, PartialEq)]
8pub enum XmlRpc {
9 MethodCall(String, Llsd),
10 MethodResponse(Llsd),
11}
12
13impl XmlRpc {
14 pub fn new_method_call(method: String, llsd: Llsd) -> Self {
15 XmlRpc::MethodCall(method, llsd)
16 }
17
18 pub fn new_method_response(llsd: Llsd) -> Self {
19 XmlRpc::MethodResponse(llsd)
20 }
21
22 pub fn method(&self) -> Option<&str> {
23 match self {
24 XmlRpc::MethodCall(method, _) => Some(method),
25 XmlRpc::MethodResponse(_) => None,
26 }
27 }
28
29 pub fn llsd(&self) -> &Llsd {
30 match self {
31 XmlRpc::MethodCall(_, llsd) => llsd,
32 XmlRpc::MethodResponse(llsd) => llsd,
33 }
34 }
35}
36
37impl AsRef<Llsd> for XmlRpc {
38 fn as_ref(&self) -> &Llsd {
39 self.llsd()
40 }
41}
42
43impl AsMut<Llsd> for XmlRpc {
44 fn as_mut(&mut self) -> &mut Llsd {
45 match self {
46 XmlRpc::MethodCall(_, llsd) => llsd,
47 XmlRpc::MethodResponse(llsd) => llsd,
48 }
49 }
50}
51
52impl From<XmlRpc> for Llsd {
53 fn from(rpc: XmlRpc) -> Self {
54 match rpc {
55 XmlRpc::MethodCall(_, llsd) => llsd,
56 XmlRpc::MethodResponse(llsd) => llsd,
57 }
58 }
59}
60
61impl From<Llsd> for XmlRpc {
62 fn from(llsd: Llsd) -> Self {
63 XmlRpc::MethodResponse(llsd)
64 }
65}
66
67impl From<(String, Llsd)> for XmlRpc {
68 fn from((method, llsd): (String, Llsd)) -> Self {
69 XmlRpc::MethodCall(method, llsd)
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74enum Expected {
75 None,
76 Data,
77 Member,
78 Name,
79 Value,
80 XmlRpcHeader,
81 MethodCallName,
82 Parmas,
83 Param,
84}
85
86pub fn from_parser<R: std::io::Read>(parser: EventReader<R>) -> Result<XmlRpc, anyhow::Error> {
87 use xml::reader::XmlEvent;
88 let mut stack: Vec<Llsd> = Vec::new();
89 let mut name_stack: Vec<String> = Vec::new();
90 let mut key_stack: Vec<String> = Vec::new();
91
92 let mut expect_value = Expected::XmlRpcHeader;
93 let mut method = None;
94
95 for event in parser {
96 match event {
97 Ok(XmlEvent::StartElement { name, .. }) => {
98 name_stack.push(name.local_name.clone());
99 match (expect_value, name.local_name.as_str()) {
100 (Expected::Data, "data") => expect_value = Expected::Value,
101 (Expected::Member, "member") => expect_value = Expected::Name,
102 (Expected::Name, "name") => expect_value = Expected::Value,
103 (Expected::Value, "value") => expect_value = Expected::None,
104 (Expected::XmlRpcHeader, "methodResponse") => expect_value = Expected::Parmas,
105 (Expected::XmlRpcHeader, "methodCall") => {
106 expect_value = Expected::MethodCallName
107 }
108 (Expected::MethodCallName, "methodName") => expect_value = Expected::Parmas,
109 (Expected::Parmas, "params") => expect_value = Expected::Param,
110 (Expected::Param, "param") => expect_value = Expected::Value,
111 (Expected::None, "nil") => stack.push(Llsd::Undefined),
112 (Expected::None, "boolean") => stack.push(Llsd::Boolean(false)),
113 (Expected::None, "string") => stack.push(Llsd::String(String::new())),
114 (Expected::None, "int") => stack.push(Llsd::Integer(0)),
115 (Expected::None, "double") => stack.push(Llsd::Real(0.0)),
116 (Expected::None, "dateTime.iso8601") => {
117 stack.push(Llsd::Date(Default::default()))
118 }
119 (Expected::None, "base64") => stack.push(Llsd::Binary(Vec::new())),
120 (Expected::None, "array") => {
121 stack.push(Llsd::Array(Vec::new()));
122 expect_value = Expected::Data;
123 }
124 (Expected::None, "struct") => {
125 stack.push(Llsd::Map(Default::default()));
126 expect_value = Expected::Member;
127 }
128 _ => {
129 return Err(anyhow::anyhow!(
130 "Error parsing XML-RPC: unexpected element {}",
131 name.local_name
132 ));
133 }
134 }
135 }
136 Ok(XmlEvent::Characters(data)) => {
137 let data = data.trim();
138 if expect_value == Expected::MethodCallName {
139 method = Some(data.to_string());
140 } else if name_stack.last().map(|s| s.as_str()) == Some("name") {
141 key_stack.push(data.to_string());
142 } else if let Some(llsd) = stack.last_mut() {
143 match llsd {
144 Llsd::Boolean(_) => match data {
145 "true" => *llsd = Llsd::Boolean(true),
146 "false" => *llsd = Llsd::Boolean(false),
147 "1" => *llsd = Llsd::Boolean(true),
148 "0" => *llsd = Llsd::Boolean(false),
149 _ => {
150 return Err(anyhow::anyhow!(
151 "Error parsing XML-RPC: expected boolean, got {}",
152 data
153 ));
154 }
155 },
156 &mut Llsd::String(ref mut s) => s.push_str(data),
157 &mut Llsd::Date(ref mut d) => {
158 *d = DateTime::parse_from_rfc3339(data)?.into()
159 }
160 &mut Llsd::Binary(ref mut b) => {
161 *b = BASE64_STANDARD.decode(data.as_bytes())?
162 }
163 &mut Llsd::Integer(ref mut i) => *i = data.parse()?,
164 &mut Llsd::Real(ref mut r) => match data {
165 "nan" => *r = f64::NAN,
166 "inf" => *r = f64::INFINITY,
167 "-inf" => *r = f64::NEG_INFINITY,
168 _ => *r = data.parse()?,
169 },
170 _ => {
171 return Err(anyhow::anyhow!(
172 "Error parsing XML-RPC: unexpected characters {}",
173 data
174 ));
175 }
176 }
177 }
178 }
179 Ok(XmlEvent::EndElement { name }) => {
180 if name_stack.pop().as_ref() != Some(&name.local_name) {
181 return Err(anyhow::anyhow!(
182 "Error parsing LLSD: unexpected end element {}",
183 name.local_name
184 ));
185 }
186 match name.local_name.as_str() {
187 "struct" | "array" if stack.len() > 1 => {
188 if let Some(parent) = stack.get(stack.len() - 2) {
189 if parent.is_array() {
190 expect_value = Expected::Value;
191 } else if parent.is_map() {
192 expect_value = Expected::Member;
193 } else {
194 return Err(anyhow::anyhow!(
195 "Error parsing XML-RPC: not a map or array"
196 ));
197 }
198 }
199 }
200 "member" => {
201 let Some(key) = key_stack.pop() else {
202 return Err(anyhow::anyhow!("Error parsing XML-RPC: missing key"));
203 };
204 let Some(value) = stack.pop() else {
205 return Err(anyhow::anyhow!(
206 "Error parsing XML-RPC: unexpected end element {}",
207 name.local_name
208 ));
209 };
210 let Some(Llsd::Map(parent)) = stack.last_mut() else {
211 return Err(anyhow::anyhow!("Error parsing XML-RPC: not a map"));
212 };
213 parent.insert(key.to_string(), value);
214 expect_value = Expected::Member;
215 }
216 "value" if stack.len() > 1 => {
217 let Some(value) = stack.pop() else {
218 return Err(anyhow::anyhow!(
219 "Error parsing XML-RPC: unexpected end element {}",
220 name.local_name
221 ));
222 };
223 if let Some(Llsd::Array(parent)) = stack.last_mut() {
224 parent.push(value);
225 expect_value = Expected::Value;
226 } else {
227 stack.push(value);
228 }
229 }
230 _ => {}
231 };
232 }
233 Err(e) => return Err(anyhow::anyhow!("Error parsing XML-RPC: {}", e)),
234 _ => {}
235 }
236 }
237 if let Some(llsd) = stack.pop() {
238 if !stack.is_empty() {
239 return Err(anyhow::anyhow!(
240 "Error parsing XML-RPC: expected 1 value, got {}",
241 stack.len() + 1
242 ));
243 }
244 if let Some(method) = method {
245 Ok(XmlRpc::MethodCall(method, llsd))
246 } else {
247 Ok(XmlRpc::MethodResponse(llsd))
248 }
249 } else {
250 Err(anyhow::anyhow!("Error parsing XML-RPC: missing key"))
251 }
252}
253
254pub fn from_str(data: &str) -> Result<XmlRpc, anyhow::Error> {
255 from_parser(EventReader::from_str(data))
256}
257
258pub fn from_reader<R: std::io::Read>(reader: R) -> Result<XmlRpc, anyhow::Error> {
259 from_parser(EventReader::new(reader))
260}
261
262pub fn from_slice(data: &[u8]) -> Result<XmlRpc, anyhow::Error> {
263 from_parser(EventReader::new(std::io::Cursor::new(data)))
264}
265
266fn write_inner<W: std::io::Write>(
267 llsd: &Llsd,
268 w: &mut EventWriter<W>,
269) -> Result<(), anyhow::Error> {
270 use xml::writer::XmlEvent;
271 let tag = |w: &mut EventWriter<W>, tag, text: &str| -> Result<(), anyhow::Error> {
272 w.write(XmlEvent::start_element(tag))?;
273 if !text.is_empty() {
274 w.write(XmlEvent::characters(text))?;
275 }
276 w.write(XmlEvent::end_element())?;
277 Ok(())
278 };
279 match llsd {
280 Llsd::Undefined => tag(w, "nil", ""),
281 Llsd::Boolean(b) => tag(w, "boolean", if *b { "1" } else { "0" }),
282 Llsd::Integer(i) => tag(w, "int", &i.to_string()),
283 Llsd::Real(r) => tag(w, "double", &r.to_string()),
284 Llsd::String(s) => tag(w, "string", s),
285 Llsd::Uri(u) => tag(w, "string", u.as_str()),
286 Llsd::Uuid(u) => tag(w, "string", &u.to_string()),
287 Llsd::Date(d) => tag(w, "dateTime.iso8601", &d.to_rfc3339()),
288 Llsd::Binary(b) => tag(w, "base64", &BASE64_STANDARD.encode(b)),
289 Llsd::Array(a) => {
290 w.write(XmlEvent::start_element("array"))?;
291 w.write(XmlEvent::start_element("data"))?;
292 for llsd in a {
293 w.write(XmlEvent::start_element("value"))?;
294 write_inner(llsd, w)?;
295 w.write(XmlEvent::end_element())?;
296 }
297 w.write(XmlEvent::end_element())?;
298 w.write(XmlEvent::end_element())?;
299 Ok(())
300 }
301 Llsd::Map(m) => {
302 w.write(XmlEvent::start_element("struct"))?;
303 for (k, v) in m {
304 w.write(XmlEvent::start_element("member"))?;
305 tag(w, "name", k)?;
306 w.write(XmlEvent::start_element("value"))?;
307 write_inner(v, w)?;
308 w.write(XmlEvent::end_element())?;
309 w.write(XmlEvent::end_element())?;
310 }
311 w.write(XmlEvent::end_element())?;
312 Ok(())
313 }
314 }
315}
316
317pub fn write<W: std::io::Write>(rpc: &XmlRpc, w: &mut EventWriter<W>) -> Result<(), anyhow::Error> {
318 use xml::writer::XmlEvent;
319 match rpc {
320 XmlRpc::MethodCall(method, _) => {
321 w.write(XmlEvent::start_element("methodCall"))?;
322 w.write(XmlEvent::start_element("methodName"))?;
323 w.write(XmlEvent::characters(method))?;
324 w.write(XmlEvent::end_element())?;
325 }
326 XmlRpc::MethodResponse(_) => {
327 w.write(XmlEvent::start_element("methodResponse"))?;
328 }
329 }
330 w.write(XmlEvent::start_element("params"))?;
331 w.write(XmlEvent::start_element("param"))?;
332 w.write(XmlEvent::start_element("value"))?;
333 write_inner(rpc.as_ref(), w)?;
334 w.write(XmlEvent::end_element())?;
335 w.write(XmlEvent::end_element())?;
336 w.write(XmlEvent::end_element())?;
337 w.write(XmlEvent::end_element())?;
338 Ok(())
339}
340
341pub fn to_string(rpc: &XmlRpc) -> Result<String, anyhow::Error> {
342 let mut buf = Vec::new();
343 write(rpc, &mut EventWriter::new(&mut buf))?;
344 Ok(String::from_utf8(buf)?)
345}
346
347pub fn to_pretty_string(rpc: &XmlRpc) -> Result<String, anyhow::Error> {
348 let mut buf = Vec::new();
349 write(
350 rpc,
351 &mut EventWriter::new_with_config(
352 &mut buf,
353 xml::writer::EmitterConfig::new().perform_indent(true),
354 ),
355 )?;
356 Ok(String::from_utf8(buf)?)
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362 use chrono::{TimeZone, Utc};
363 use std::collections::HashMap;
364 use url::Url;
365 use uuid::Uuid;
366
367 fn round_trip(llsd: Llsd) {
368 trip(llsd.clone(), llsd);
369 }
370
371 fn trip(input: Llsd, output: Llsd) {
372 let resp = XmlRpc::new_method_response(input);
373 let encoded = to_string(&resp).expect("Failed to encode");
374 let decoded = from_str(&encoded).expect("Failed to decode");
375 assert_eq!(&output, decoded.llsd());
376 }
377
378 #[test]
379 fn undefined() {
380 round_trip(Llsd::Undefined);
381 }
382
383 #[test]
384 fn boolean() {
385 round_trip(Llsd::Boolean(true));
386 round_trip(Llsd::Boolean(false));
387 }
388
389 #[test]
390 fn integer() {
391 round_trip(Llsd::Integer(42));
392 }
393
394 #[test]
395 fn real() {
396 round_trip(Llsd::Real(13.1415));
397 }
398
399 #[test]
400 fn string() {
401 round_trip(Llsd::String("Hello, LLSD!".to_owned()));
402 }
403
404 #[test]
405 fn uri() {
406 let url = Url::parse("https://example.com/").unwrap();
407 trip(Llsd::Uri(url.clone().into()), Llsd::String(url.to_string()));
408 }
409
410 #[test]
411 fn uuid() {
412 let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
413 trip(Llsd::Uuid(uuid), Llsd::String(uuid.to_string()));
414 }
415
416 #[test]
417 fn date() {
418 let dt = Utc.timestamp_opt(1_620_000_000, 0).unwrap();
419 round_trip(Llsd::Date(dt));
420 }
421
422 #[test]
423 fn binary() {
424 round_trip(Llsd::Binary(vec![0xde, 0xad, 0xbe, 0xef]));
425 }
426
427 #[test]
428 fn array() {
429 let arr = vec![
430 Llsd::Integer(1),
431 Llsd::String("two".into()),
432 Llsd::Boolean(false),
433 ];
434 round_trip(Llsd::Array(arr));
435 }
436
437 #[test]
438 fn map() {
439 let mut map = HashMap::new();
440 map.insert("answer".into(), Llsd::Integer(42));
441 map.insert("pi".into(), Llsd::Real(13.14));
442 map.insert("greeting".into(), Llsd::String("hello".into()));
443 round_trip(Llsd::Map(map));
444 }
445}