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
86#[inline]
87#[cfg(feature = "opensim")]
88fn is_xmlrpc_int_tag(tag: &str) -> bool {
89 matches!(tag, "int" | "i4" | "i")
90}
91
92#[inline]
93#[cfg(not(feature = "opensim"))]
94fn is_xmlrpc_int_tag(tag: &str) -> bool {
95 matches!(tag, "int")
96}
97
98pub fn from_parser<R: std::io::Read>(parser: EventReader<R>) -> Result<XmlRpc, anyhow::Error> {
99 use xml::reader::XmlEvent;
100 let mut stack: Vec<Llsd> = Vec::new();
101 let mut name_stack: Vec<String> = Vec::new();
102 let mut key_stack: Vec<String> = Vec::new();
103
104 let mut expect_value = Expected::XmlRpcHeader;
105 let mut method = None;
106
107 for event in parser {
108 match event {
109 Ok(XmlEvent::StartElement { name, .. }) => {
110 name_stack.push(name.local_name.clone());
111 match (expect_value, name.local_name.as_str()) {
112 (Expected::Data, "data") => expect_value = Expected::Value,
113 (Expected::Member, "member") => expect_value = Expected::Name,
114 (Expected::Name, "name") => expect_value = Expected::Value,
115 (Expected::Value, "value") => expect_value = Expected::None,
116 (Expected::XmlRpcHeader, "methodResponse") => expect_value = Expected::Parmas,
117 (Expected::XmlRpcHeader, "methodCall") => {
118 expect_value = Expected::MethodCallName
119 }
120 (Expected::MethodCallName, "methodName") => expect_value = Expected::Parmas,
121 (Expected::Parmas, "params") => expect_value = Expected::Param,
122 (Expected::Param, "param") => expect_value = Expected::Value,
123 (Expected::None, "nil") => stack.push(Llsd::Undefined),
124 (Expected::None, "boolean") => stack.push(Llsd::Boolean(false)),
125 (Expected::None, "string") => stack.push(Llsd::String(String::new())),
126 (Expected::None, tag) if is_xmlrpc_int_tag(tag) => stack.push(Llsd::Integer(0)),
127 (Expected::None, "double") => stack.push(Llsd::Real(0.0)),
128 (Expected::None, "dateTime.iso8601") => {
129 stack.push(Llsd::Date(Default::default()))
130 }
131 (Expected::None, "base64") => stack.push(Llsd::Binary(Vec::new())),
132 (Expected::None, "array") => {
133 stack.push(Llsd::Array(Vec::new()));
134 expect_value = Expected::Data;
135 }
136 (Expected::None, "struct") => {
137 stack.push(Llsd::Map(Default::default()));
138 expect_value = Expected::Member;
139 }
140 _ => {
141 return Err(anyhow::anyhow!(
142 "Error parsing XML-RPC: unexpected element {}",
143 name.local_name
144 ));
145 }
146 }
147 }
148 Ok(XmlEvent::Characters(data)) => {
149 let data = data.trim();
150 if expect_value == Expected::MethodCallName {
151 method = Some(data.to_string());
152 } else if name_stack.last().map(|s| s.as_str()) == Some("name") {
153 key_stack.push(data.to_string());
154 } else if let Some(llsd) = stack.last_mut() {
155 match llsd {
156 Llsd::Boolean(_) => match data {
157 "true" => *llsd = Llsd::Boolean(true),
158 "false" => *llsd = Llsd::Boolean(false),
159 "1" => *llsd = Llsd::Boolean(true),
160 "0" => *llsd = Llsd::Boolean(false),
161 _ => {
162 return Err(anyhow::anyhow!(
163 "Error parsing XML-RPC: expected boolean, got {}",
164 data
165 ));
166 }
167 },
168 &mut Llsd::String(ref mut s) => s.push_str(data),
169 &mut Llsd::Date(ref mut d) => {
170 *d = DateTime::parse_from_rfc3339(data)?.into()
171 }
172 &mut Llsd::Binary(ref mut b) => {
173 *b = BASE64_STANDARD.decode(data.as_bytes())?
174 }
175 &mut Llsd::Integer(ref mut i) => *i = data.parse()?,
176 &mut Llsd::Real(ref mut r) => match data {
177 "nan" => *r = f64::NAN,
178 "inf" => *r = f64::INFINITY,
179 "-inf" => *r = f64::NEG_INFINITY,
180 _ => *r = data.parse()?,
181 },
182 _ => {
183 return Err(anyhow::anyhow!(
184 "Error parsing XML-RPC: unexpected characters {}",
185 data
186 ));
187 }
188 }
189 }
190 }
191 Ok(XmlEvent::EndElement { name }) => {
192 if name_stack.pop().as_ref() != Some(&name.local_name) {
193 return Err(anyhow::anyhow!(
194 "Error parsing LLSD: unexpected end element {}",
195 name.local_name
196 ));
197 }
198 match name.local_name.as_str() {
199 "struct" | "array" if stack.len() > 1 => {
200 if let Some(parent) = stack.get(stack.len() - 2) {
201 if parent.is_array() {
202 expect_value = Expected::Value;
203 } else if parent.is_map() {
204 expect_value = Expected::Member;
205 } else {
206 return Err(anyhow::anyhow!(
207 "Error parsing XML-RPC: not a map or array"
208 ));
209 }
210 }
211 }
212 "member" => {
213 let Some(key) = key_stack.pop() else {
214 return Err(anyhow::anyhow!("Error parsing XML-RPC: missing key"));
215 };
216 let Some(value) = stack.pop() else {
217 return Err(anyhow::anyhow!(
218 "Error parsing XML-RPC: unexpected end element {}",
219 name.local_name
220 ));
221 };
222 let Some(Llsd::Map(parent)) = stack.last_mut() else {
223 return Err(anyhow::anyhow!("Error parsing XML-RPC: not a map"));
224 };
225 parent.insert(key.to_string(), value);
226 expect_value = Expected::Member;
227 }
228 "value" if stack.len() > 1 => {
229 let Some(value) = stack.pop() else {
230 return Err(anyhow::anyhow!(
231 "Error parsing XML-RPC: unexpected end element {}",
232 name.local_name
233 ));
234 };
235 if let Some(Llsd::Array(parent)) = stack.last_mut() {
236 parent.push(value);
237 expect_value = Expected::Value;
238 } else {
239 stack.push(value);
240 }
241 }
242 _ => {}
243 };
244 }
245 Err(e) => return Err(anyhow::anyhow!("Error parsing XML-RPC: {}", e)),
246 _ => {}
247 }
248 }
249 if let Some(llsd) = stack.pop() {
250 if !stack.is_empty() {
251 return Err(anyhow::anyhow!(
252 "Error parsing XML-RPC: expected 1 value, got {}",
253 stack.len() + 1
254 ));
255 }
256 if let Some(method) = method {
257 Ok(XmlRpc::MethodCall(method, llsd))
258 } else {
259 Ok(XmlRpc::MethodResponse(llsd))
260 }
261 } else {
262 Err(anyhow::anyhow!("Error parsing XML-RPC: missing key"))
263 }
264}
265
266pub fn from_str(data: &str) -> Result<XmlRpc, anyhow::Error> {
267 from_parser(EventReader::from_str(data))
268}
269
270pub fn from_reader<R: std::io::Read>(reader: R) -> Result<XmlRpc, anyhow::Error> {
271 from_parser(EventReader::new(reader))
272}
273
274pub fn from_slice(data: &[u8]) -> Result<XmlRpc, anyhow::Error> {
275 from_parser(EventReader::new(std::io::Cursor::new(data)))
276}
277
278fn write_inner<W: std::io::Write>(
279 llsd: &Llsd,
280 w: &mut EventWriter<W>,
281) -> Result<(), anyhow::Error> {
282 use xml::writer::XmlEvent;
283 let tag = |w: &mut EventWriter<W>, tag, text: &str| -> Result<(), anyhow::Error> {
284 w.write(XmlEvent::start_element(tag))?;
285 if !text.is_empty() {
286 w.write(XmlEvent::characters(text))?;
287 }
288 w.write(XmlEvent::end_element())?;
289 Ok(())
290 };
291 match llsd {
292 Llsd::Undefined => tag(w, "nil", ""),
293 Llsd::Boolean(b) => tag(w, "boolean", if *b { "1" } else { "0" }),
294 Llsd::Integer(i) => tag(w, "int", &i.to_string()),
295 Llsd::Real(r) => tag(w, "double", &r.to_string()),
296 Llsd::String(s) => tag(w, "string", s),
297 Llsd::Uri(u) => tag(w, "string", u.as_str()),
298 Llsd::Uuid(u) => tag(w, "string", &u.to_string()),
299 Llsd::Date(d) => tag(w, "dateTime.iso8601", &d.to_rfc3339()),
300 Llsd::Binary(b) => tag(w, "base64", &BASE64_STANDARD.encode(b)),
301 Llsd::Array(a) => {
302 w.write(XmlEvent::start_element("array"))?;
303 w.write(XmlEvent::start_element("data"))?;
304 for llsd in a {
305 w.write(XmlEvent::start_element("value"))?;
306 write_inner(llsd, w)?;
307 w.write(XmlEvent::end_element())?;
308 }
309 w.write(XmlEvent::end_element())?;
310 w.write(XmlEvent::end_element())?;
311 Ok(())
312 }
313 Llsd::Map(m) => {
314 w.write(XmlEvent::start_element("struct"))?;
315 for (k, v) in m {
316 w.write(XmlEvent::start_element("member"))?;
317 tag(w, "name", k)?;
318 w.write(XmlEvent::start_element("value"))?;
319 write_inner(v, w)?;
320 w.write(XmlEvent::end_element())?;
321 w.write(XmlEvent::end_element())?;
322 }
323 w.write(XmlEvent::end_element())?;
324 Ok(())
325 }
326 }
327}
328
329pub fn write<W: std::io::Write>(rpc: &XmlRpc, w: &mut EventWriter<W>) -> Result<(), anyhow::Error> {
330 use xml::writer::XmlEvent;
331 match rpc {
332 XmlRpc::MethodCall(method, _) => {
333 w.write(XmlEvent::start_element("methodCall"))?;
334 w.write(XmlEvent::start_element("methodName"))?;
335 w.write(XmlEvent::characters(method))?;
336 w.write(XmlEvent::end_element())?;
337 }
338 XmlRpc::MethodResponse(_) => {
339 w.write(XmlEvent::start_element("methodResponse"))?;
340 }
341 }
342 w.write(XmlEvent::start_element("params"))?;
343 w.write(XmlEvent::start_element("param"))?;
344 w.write(XmlEvent::start_element("value"))?;
345 write_inner(rpc.as_ref(), w)?;
346 w.write(XmlEvent::end_element())?;
347 w.write(XmlEvent::end_element())?;
348 w.write(XmlEvent::end_element())?;
349 w.write(XmlEvent::end_element())?;
350 Ok(())
351}
352
353pub fn to_string(rpc: &XmlRpc) -> Result<String, anyhow::Error> {
354 let mut buf = Vec::new();
355 write(rpc, &mut EventWriter::new(&mut buf))?;
356 Ok(String::from_utf8(buf)?)
357}
358
359pub fn to_pretty_string(rpc: &XmlRpc) -> Result<String, anyhow::Error> {
360 let mut buf = Vec::new();
361 write(
362 rpc,
363 &mut EventWriter::new_with_config(
364 &mut buf,
365 xml::writer::EmitterConfig::new().perform_indent(true),
366 ),
367 )?;
368 Ok(String::from_utf8(buf)?)
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374 use chrono::{TimeZone, Utc};
375 use std::collections::HashMap;
376 use url::Url;
377 use uuid::Uuid;
378
379 fn round_trip(llsd: Llsd) {
380 trip(llsd.clone(), llsd);
381 }
382
383 fn trip(input: Llsd, output: Llsd) {
384 let resp = XmlRpc::new_method_response(input);
385 let encoded = to_string(&resp).expect("Failed to encode");
386 let decoded = from_str(&encoded).expect("Failed to decode");
387 assert_eq!(&output, decoded.llsd());
388 }
389
390 #[test]
391 fn undefined() {
392 round_trip(Llsd::Undefined);
393 }
394
395 #[test]
396 fn boolean() {
397 round_trip(Llsd::Boolean(true));
398 round_trip(Llsd::Boolean(false));
399 }
400
401 #[test]
402 fn integer() {
403 round_trip(Llsd::Integer(42));
404 }
405
406 #[cfg(feature = "opensim")]
407 #[test]
408 fn parses_opensim_int_alias_tags() {
409 let xml_i4 = r#"
410<methodResponse><params><param><value><i4>7</i4></value></param></params></methodResponse>
411"#;
412 let xml_i = r#"
413<methodResponse><params><param><value><i>9</i></value></param></params></methodResponse>
414"#;
415 let parsed_i4 = from_str(xml_i4).expect("i4 should parse");
416 let parsed_i = from_str(xml_i).expect("i should parse");
417 assert_eq!(parsed_i4.llsd(), &Llsd::Integer(7));
418 assert_eq!(parsed_i.llsd(), &Llsd::Integer(9));
419 }
420
421 #[cfg(not(feature = "opensim"))]
422 #[test]
423 fn rejects_opensim_int_alias_tags_without_feature() {
424 let xml_i4 = r#"
425<methodResponse><params><param><value><i4>7</i4></value></param></params></methodResponse>
426"#;
427 let xml_i = r#"
428<methodResponse><params><param><value><i>9</i></value></param></params></methodResponse>
429"#;
430 assert!(
431 from_str(xml_i4).is_err(),
432 "i4 should not parse without opensim"
433 );
434 assert!(
435 from_str(xml_i).is_err(),
436 "i should not parse without opensim"
437 );
438 }
439
440 #[test]
441 fn real() {
442 round_trip(Llsd::Real(13.1415));
443 }
444
445 #[test]
446 fn string() {
447 round_trip(Llsd::String("Hello, LLSD!".to_owned()));
448 }
449
450 #[test]
451 fn uri() {
452 let url = Url::parse("https://example.com/").unwrap();
453 trip(Llsd::Uri(url.clone().into()), Llsd::String(url.to_string()));
454 }
455
456 #[test]
457 fn uuid() {
458 let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
459 trip(Llsd::Uuid(uuid), Llsd::String(uuid.to_string()));
460 }
461
462 #[test]
463 fn date() {
464 let dt = Utc.timestamp_opt(1_620_000_000, 0).unwrap();
465 round_trip(Llsd::Date(dt));
466 }
467
468 #[test]
469 fn binary() {
470 round_trip(Llsd::Binary(vec![0xde, 0xad, 0xbe, 0xef]));
471 }
472
473 #[test]
474 fn array() {
475 let arr = vec![
476 Llsd::Integer(1),
477 Llsd::String("two".into()),
478 Llsd::Boolean(false),
479 ];
480 round_trip(Llsd::Array(arr));
481 }
482
483 #[test]
484 fn map() {
485 let mut map = HashMap::new();
486 map.insert("answer".into(), Llsd::Integer(42));
487 map.insert("pi".into(), Llsd::Real(13.14));
488 map.insert("greeting".into(), Llsd::String("hello".into()));
489 round_trip(Llsd::Map(map));
490 }
491}