//! Bounded JSON parsing with explicit `LibreMetaverse` OSD coercions. #![allow(clippy::missing_errors_doc)] #![allow(clippy::needless_pass_by_value)] use crate::{Error, OSD}; use libremetaverse_types::compat::{ReadWrite, Uri}; use serde::de::{self, DeserializeSeed, Error as _, MapAccess, SeqAccess, Visitor}; use std::cell::Cell; use std::collections::HashMap; use std::fmt; use std::io::Read as _; use std::rc::Rc; const MAX_BYTES: usize = OSD::DEFAULT_MAX_BINARY_BYTES; pub(crate) fn deserialize_string(json: String) -> Result { if json.len() > MAX_BYTES { return Err(parse_error(0, "JSON OSD input exceeds allocation limit")); } let json = json.strip_prefix('\u{feff}').unwrap_or(&json); let limits = Limits::default(); let mut deserializer = serde_json::Deserializer::from_str(json); let value = Seed { depth: 0, limits } .deserialize(&mut deserializer) .map_err(|error| json_error(json, &error))?; deserializer .end() .map_err(|error| json_error(json, &error))?; value.validate_limits( OSD::DEFAULT_MAX_DEPTH, OSD::DEFAULT_MAX_NODES, OSD::DEFAULT_MAX_BINARY_BYTES, )?; Ok(value) } pub(crate) fn deserialize_stream(mut json: Box) -> Result { let mut bytes = Vec::new(); (&mut *json) .take((MAX_BYTES + 1) as u64) .read_to_end(&mut bytes) .map_err(|_| Error::InvalidOperation)?; if bytes.len() > MAX_BYTES { return Err(parse_error(0, "JSON OSD input exceeds allocation limit")); } let text = String::from_utf8(bytes) .map_err(|_| parse_error(0, "JSON OSD input is not valid UTF-8"))?; deserialize_string(text) } pub(crate) fn serialize(osd: OSD, preserve_defaults: Option) -> Result { osd.validate_limits( OSD::DEFAULT_MAX_DEPTH, OSD::DEFAULT_MAX_NODES, OSD::DEFAULT_MAX_BINARY_BYTES, )?; let mut encoder = Encoder::new(); encoder.write_value(&osd, preserve_defaults.unwrap_or(false), 0)?; Ok(encoder.output) } #[derive(Clone, Default)] struct Limits { nodes: Rc>, allocated: Rc>, } impl Limits { fn add_node(&self, depth: usize) -> Result<(), E> { if depth > OSD::DEFAULT_MAX_DEPTH { return Err(E::custom("JSON OSD nesting depth exceeded")); } let nodes = self .nodes .get() .checked_add(1) .ok_or_else(|| E::custom("JSON OSD node count overflow"))?; if nodes > OSD::DEFAULT_MAX_NODES { return Err(E::custom("JSON OSD node limit exceeded")); } self.nodes.set(nodes); Ok(()) } fn add_allocation(&self, amount: usize) -> Result<(), E> { let allocated = self .allocated .get() .checked_add(amount) .ok_or_else(|| E::custom("JSON OSD allocation overflow"))?; if allocated > MAX_BYTES { return Err(E::custom("JSON OSD allocation limit exceeded")); } self.allocated.set(allocated); Ok(()) } } struct Seed { depth: usize, limits: Limits, } impl<'de> DeserializeSeed<'de> for Seed { type Value = OSD; fn deserialize>(self, deserializer: D) -> Result { self.limits.add_node(self.depth)?; deserializer.deserialize_any(OsdVisitor { depth: self.depth, limits: self.limits, }) } } struct OsdVisitor { depth: usize, limits: Limits, } impl<'de> Visitor<'de> for OsdVisitor { type Value = OSD; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a JSON value convertible to OSD") } fn visit_bool(self, value: bool) -> Result { Ok(OSD::Boolean(value)) } fn visit_i64(self, value: i64) -> Result { if let Ok(value) = i32::try_from(value) { Ok(OSD::Integer(value)) } else { self.limits.add_allocation(8)?; Ok(OSD::Binary(value.to_be_bytes().to_vec())) } } #[allow(clippy::cast_precision_loss)] fn visit_u64(self, value: u64) -> Result { if let Ok(value) = i32::try_from(value) { Ok(OSD::Integer(value)) } else if let Ok(value) = i64::try_from(value) { self.limits.add_allocation(8)?; Ok(OSD::Binary(value.to_be_bytes().to_vec())) } else { Ok(OSD::Real(value as f64)) } } fn visit_f64(self, value: f64) -> Result { if value.is_finite() { Ok(OSD::Real(value)) } else { Err(E::custom( "JSON OSD number is outside the finite double range", )) } } fn visit_str(self, value: &str) -> Result { self.limits.add_allocation(value.len())?; Ok(if value.is_empty() { OSD::Undefined } else { OSD::String(value.to_owned()) }) } fn visit_string(self, value: String) -> Result { self.limits.add_allocation(value.len())?; Ok(if value.is_empty() { OSD::Undefined } else { OSD::String(value) }) } fn visit_none(self) -> Result { Ok(OSD::Undefined) } fn visit_unit(self) -> Result { Ok(OSD::Undefined) } fn visit_seq>(self, mut sequence: A) -> Result { let mut values = Vec::with_capacity(sequence.size_hint().unwrap_or(0).min(1024)); while let Some(value) = sequence.next_element_seed(Seed { depth: self.depth + 1, limits: self.limits.clone(), })? { self.limits .add_allocation::(std::mem::size_of::())?; values.push(value); } Ok(OSD::Array(values)) } fn visit_map>(self, mut object: A) -> Result { let mut values = HashMap::with_capacity(object.size_hint().unwrap_or(0).min(1024)); while let Some(key) = object.next_key::()? { self.limits .add_allocation::(key.len() + std::mem::size_of::<(String, OSD)>())?; if values.contains_key(&key) { return Err(A::Error::custom("duplicate JSON OSD object property")); } let value = object.next_value_seed(Seed { depth: self.depth + 1, limits: self.limits.clone(), })?; values.insert(key, value); } Ok(OSD::Map(values)) } } struct Encoder { output: String, } impl Encoder { fn new() -> Self { Self { output: String::with_capacity(128), } } fn write_value(&mut self, value: &OSD, preserve: bool, depth: usize) -> Result<(), Error> { if depth > OSD::DEFAULT_MAX_DEPTH { return Err(Error::Argument); } if !preserve && is_default(value) { return self.push("null"); } match value { OSD::Undefined | OSD::LlsdXml(_) => self.push("null"), OSD::Boolean(value) => self.push(if *value { "true" } else { "false" }), OSD::Integer(value) => self.push(&value.to_string()), OSD::Real(value) => { let number = serde_json::Number::from_f64(*value).ok_or(Error::Argument)?; self.push(&number.to_string()) } OSD::String(value) => self.write_string(value), OSD::UUID(value) => self.write_string(&value.to_string()), OSD::Date(value) => { self.write_string(&crate::model::format_system_time_for_codec(*value)) } OSD::Uri(Uri(value)) => self.write_string(&crate::model::format_uri_for_codec(value)), OSD::Binary(values) => { self.push("[")?; for (index, value) in values.iter().enumerate() { if index != 0 { self.push(",")?; } self.push(&value.to_string())?; } self.push("]") } OSD::Array(values) => { self.push("[")?; for (index, value) in values.iter().enumerate() { if index != 0 { self.push(",")?; } self.write_value(value, preserve, depth + 1)?; } self.push("]") } OSD::Map(values) => { self.push("{")?; let mut entries: Vec<_> = values.iter().collect(); entries.sort_unstable_by_key(|(key, _)| *key); let mut written = 0; for (key, value) in entries { if !preserve && is_default(value) { continue; } if written != 0 { self.push(",")?; } self.write_string(key)?; self.push(":")?; self.write_value(value, preserve, depth + 1)?; written += 1; } self.push("}") } } } fn write_string(&mut self, value: &str) -> Result<(), Error> { self.push("\"")?; let mut start = 0; for (index, character) in value.char_indices() { let escape = match character { '\u{08}' => Some("\\b"), '\t' => Some("\\t"), '\n' => Some("\\n"), '\u{0c}' => Some("\\f"), '\r' => Some("\\r"), '"' => Some("\\u0022"), '&' => Some("\\u0026"), '\'' => Some("\\u0027"), '+' => Some("\\u002B"), '<' => Some("\\u003C"), '>' => Some("\\u003E"), '\\' => Some("\\\\"), character if character < ' ' || !character.is_ascii() => None, _ => continue, }; self.push(&value[start..index])?; if let Some(escape) = escape { self.push(escape)?; } else { let mut units = [0_u16; 2]; for unit in character.encode_utf16(&mut units) { self.push(&format!("\\u{unit:04X}"))?; } } start = index + character.len_utf8(); } self.push(&value[start..])?; self.push("\"") } fn push(&mut self, value: &str) -> Result<(), Error> { let length = self .output .len() .checked_add(value.len()) .ok_or(Error::Argument)?; if length > MAX_BYTES { return Err(Error::Argument); } self.output.push_str(value); Ok(()) } } fn is_default(value: &OSD) -> bool { match value { OSD::Undefined | OSD::LlsdXml(_) => true, OSD::Boolean(value) => !value, OSD::Integer(value) => *value == 0, OSD::Real(value) => *value == 0.0, OSD::String(value) | OSD::Uri(Uri(value)) => value.is_empty(), OSD::UUID(value) => value.equals_with_uuid(libremetaverse_types::UUID::zero()), OSD::Binary(value) => value.is_empty(), OSD::Date(_) | OSD::Array(_) | OSD::Map(_) => false, } } fn json_error(input: &str, error: &serde_json::Error) -> Error { let line = error.line().max(1); let column = error.column().max(1); let line_start = input .match_indices('\n') .take(line.saturating_sub(1)) .last() .map_or(0, |(index, _)| index + 1); parse_error( line_start.saturating_add(column - 1).min(input.len()), "malformed or resource-limited JSON OSD", ) } const fn parse_error(position: usize, context: &'static str) -> Error { Error::Parse { position, context } } #[cfg(test)] mod tests { use super::*; use libremetaverse_types::UUID; #[test] fn explicit_json_coercions_and_default_policy_match_reference() { assert_eq!(deserialize_string("\"\"".into()).unwrap(), OSD::Undefined); assert_eq!(deserialize_string("null".into()).unwrap(), OSD::Undefined); let stream: Box = Box::new(std::io::Cursor::new(b"{\"stream\":true}".to_vec())); assert_eq!( deserialize_stream(stream).unwrap(), OSD::Map(HashMap::from([("stream".into(), OSD::Boolean(true))])) ); assert_eq!( deserialize_string("2147483648".into()).unwrap(), OSD::Binary(2_147_483_648_i64.to_be_bytes().to_vec()) ); let value = OSD::Map(HashMap::from([ ("false".into(), OSD::Boolean(false)), ("true".into(), OSD::Boolean(true)), ("zero".into(), OSD::Integer(0)), ( "array".into(), OSD::Array(vec![OSD::Integer(0), OSD::String("x".into())]), ), ])); assert_eq!( serialize(value.clone(), None).unwrap(), "{\"array\":[null,\"x\"],\"true\":true}" ); assert_eq!( serialize(value, Some(true)).unwrap(), include_str!("../../../tests/fixtures/structured_data/json_reference.json").trim() ); } #[test] fn json_strings_use_system_text_json_compatible_escaping() { let value = OSD::String("<&+\"'\\ ж 𠮟".into()); assert_eq!( serialize(value.clone(), Some(true)).unwrap(), "\"\\u003C\\u0026\\u002B\\u0022\\u0027\\\\ \\u0436 \\uD842\\uDF9F\"" ); assert_eq!( deserialize_string(serialize(value.clone(), Some(true)).unwrap()).unwrap(), value ); } #[test] fn malformed_duplicate_and_deep_json_are_positioned_errors() { for input in ["{\"x\":1,\"x\":2}", "[1,", "1 trailing"] { assert!(matches!( deserialize_string(input.into()), Err(Error::Parse { .. }) )); } let too_deep = format!( "{}null{}", "[".repeat(OSD::DEFAULT_MAX_DEPTH + 2), "]".repeat(OSD::DEFAULT_MAX_DEPTH + 2) ); assert!(matches!( deserialize_string(too_deep), Err(Error::Parse { .. }) )); } #[test] fn json_round_trip_preserves_representable_osd_values() { let value = OSD::Map(HashMap::from([ ("boolean".into(), OSD::Boolean(false)), ("integer".into(), OSD::Integer(-4)), ("real".into(), OSD::Real(1.5)), ("string".into(), OSD::String("text".into())), ("uuid".into(), OSD::UUID(UUID::zero())), ])); let decoded = deserialize_string(serialize(value, Some(true)).unwrap()).unwrap(); let OSD::Map(decoded) = decoded else { panic!("expected JSON object"); }; assert_eq!(decoded["boolean"], OSD::Boolean(false)); assert_eq!(decoded["integer"], OSD::Integer(-4)); assert_eq!(decoded["real"], OSD::Real(1.5)); assert_eq!(decoded["string"], OSD::String("text".into())); assert_eq!( decoded["uuid"], OSD::String("00000000-0000-0000-0000-000000000000".into()) ); } }