Files

349 lines
11 KiB
Rust

//! Strict wire boundary. Parsed nodes never escape into generated business signatures.
use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use base64::Engine as _;
use serde::Deserializer;
use serde::de::{self, MapAccess, SeqAccess, Visitor};
use serde_json::value::RawValue;
pub const MAX_BYTES: usize = 16 * 1024 * 1024;
pub const MAX_DEPTH: usize = 64;
pub const MAX_ELEMENTS: usize = 65_536;
pub struct Node<'a> {
raw: &'a str,
kind: Kind<'a>,
}
enum Kind<'a> {
Null,
Bool(bool),
Number,
Text(String),
List(Vec<Node<'a>>),
Object(BTreeMap<String, Node<'a>>),
}
pub fn parse(text: &str) -> Result<Node<'_>, String> {
if text.len() > MAX_BYTES {
return Err("wire JSON exceeds byte limit".into());
}
let raw: &RawValue = serde_json::from_str(text).map_err(|error| error.to_string())?;
let mut remaining = MAX_ELEMENTS;
parse_node(raw, 0, &mut remaining)
}
fn parse_node<'a>(
raw: &'a RawValue,
depth: usize,
remaining: &mut usize,
) -> Result<Node<'a>, String> {
if depth > MAX_DEPTH {
return Err("wire JSON exceeds depth limit".into());
}
*remaining = remaining
.checked_sub(1)
.ok_or("wire JSON exceeds element limit")?;
let text = raw.get();
let kind = match text.as_bytes()[0] {
b'n' => Kind::Null,
b't' => Kind::Bool(true),
b'f' => Kind::Bool(false),
b'"' => Kind::Text(serde_json::from_str(text).map_err(|error| error.to_string())?),
b'[' | b'{' => {
if depth == MAX_DEPTH {
return Err("wire JSON exceeds depth limit".into());
}
let mut decoder = serde_json::Deserializer::from_str(text);
let kind = decoder
.deserialize_any(Container { depth, remaining })
.map_err(|error| error.to_string())?;
decoder.end().map_err(|error| error.to_string())?;
kind
}
_ => Kind::Number,
};
Ok(Node { raw: text, kind })
}
struct Container<'b> {
depth: usize,
remaining: &'b mut usize,
}
impl<'de> Visitor<'de> for Container<'_> {
type Value = Kind<'de>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a bounded JSON container")
}
fn visit_seq<A: SeqAccess<'de>>(self, mut sequence: A) -> Result<Self::Value, A::Error> {
let mut values = Vec::new();
while let Some(raw) = sequence.next_element::<&'de RawValue>()? {
values
.push(parse_node(raw, self.depth + 1, self.remaining).map_err(de::Error::custom)?);
}
Ok(Kind::List(values))
}
fn visit_map<A: MapAccess<'de>>(self, mut object: A) -> Result<Self::Value, A::Error> {
let mut values = BTreeMap::new();
while let Some(key) = object.next_key::<String>()? {
if values.contains_key(&key) {
return Err(de::Error::custom("duplicate JSON field"));
}
let raw = object.next_value::<&'de RawValue>()?;
values.insert(
key,
parse_node(raw, self.depth + 1, self.remaining).map_err(de::Error::custom)?,
);
}
Ok(Kind::Object(values))
}
}
impl<'a> Node<'a> {
pub fn bytes(&self) -> Result<crate::bytes::Bytes, String> {
base64::engine::general_purpose::STANDARD
.decode(self.text()?)
.map(crate::bytes::Bytes::new)
.map_err(|_| "invalid wire Bytes base64".into())
}
pub fn is_null(&self) -> bool {
matches!(self.kind, Kind::Null)
}
pub fn raw(&self) -> &'a str {
self.raw
}
pub fn text(&self) -> Result<&str, String> {
match &self.kind {
Kind::Text(value) => Ok(value),
_ => Err("expected wire string".into()),
}
}
pub fn boolean(&self) -> Result<bool, String> {
match self.kind {
Kind::Bool(value) => Ok(value),
_ => Err("expected wire Bool".into()),
}
}
pub fn int(&self) -> Result<i64, String> {
if !matches!(self.kind, Kind::Number) {
return Err("expected wire integer".into());
}
self.raw
.parse()
.map_err(|_| "wire integer is not an exact i64".into())
}
pub fn float(&self) -> Result<f64, String> {
if !matches!(self.kind, Kind::Number) {
return Err("expected wire Float".into());
}
let value: f64 = self.raw.parse().map_err(|_| "invalid wire Float")?;
if !value.is_finite() {
return Err("non-finite wire Float".into());
}
Ok(value)
}
pub fn list(&self) -> Result<&[Node<'a>], String> {
match &self.kind {
Kind::List(values) => Ok(values),
_ => Err("expected wire List".into()),
}
}
pub fn fields(&self, names: &[&str]) -> Result<&BTreeMap<String, Node<'a>>, String> {
let fields = self.object()?;
if fields.keys().any(|name| !names.contains(&name.as_str())) {
return Err("unknown wire record field".into());
}
Ok(fields)
}
pub fn object(&self) -> Result<&BTreeMap<String, Node<'a>>, String> {
match &self.kind {
Kind::Object(fields) => Ok(fields),
_ => Err("expected wire record".into()),
}
}
}
/// Only validated JSON can be placed in an API envelope without re-encoding its numbers.
pub struct Encoded(String);
impl Encoded {
pub fn as_str(&self) -> &str {
&self.0
}
pub fn null() -> Self {
Self("null".into())
}
}
enum Frame {
Array(usize),
Object {
names: BTreeSet<String>,
awaiting_value: bool,
},
}
#[derive(Default)]
pub struct Encoder {
output: String,
stack: Vec<Frame>,
elements: usize,
failed: bool,
}
impl Encoder {
pub fn bytes(&mut self, value: &crate::bytes::Bytes) -> Result<(), String> {
let length = base64::encoded_len(value.values().len(), true)
.and_then(|length| length.checked_add(2))
.ok_or("wire JSON exceeds byte limit")?;
if length > MAX_BYTES.saturating_sub(self.output.len()) {
return Err("wire JSON exceeds byte limit".into());
}
self.text(&base64::engine::general_purpose::STANDARD.encode(value.values()))
}
fn append(&mut self, text: &str) -> Result<(), String> {
if text.len() > MAX_BYTES.saturating_sub(self.output.len()) {
// Frame bookkeeping may already have advanced before an append fails.
// Such a partial write must never become a validated Encoded value.
self.failed = true;
return Err("wire JSON exceeds byte limit".into());
}
self.output.push_str(text);
Ok(())
}
fn value(&mut self) -> Result<(), String> {
if self.elements == MAX_ELEMENTS {
return Err("wire JSON exceeds element limit".into());
}
self.elements += 1;
let comma = match self.stack.last_mut() {
Some(Frame::Array(count)) => {
*count += 1;
*count > 1
}
Some(Frame::Object { awaiting_value, .. }) if *awaiting_value => {
*awaiting_value = false;
false
}
Some(Frame::Object { .. }) => return Err("wire object requires a field name".into()),
None if self.elements != 1 => return Err("wire JSON has multiple roots".into()),
None => false,
};
if comma {
self.append(",")?;
}
Ok(())
}
pub fn null(&mut self) -> Result<(), String> {
self.scalar("null")
}
pub fn boolean(&mut self, value: bool) -> Result<(), String> {
self.scalar(if value { "true" } else { "false" })
}
pub fn int(&mut self, value: i64) -> Result<(), String> {
self.scalar(&value.to_string())
}
pub fn float(&mut self, value: f64) -> Result<(), String> {
if !value.is_finite() {
return Err("non-finite wire Float".into());
}
self.scalar(&serde_json::to_string(&value).map_err(|error| error.to_string())?)
}
pub fn text(&mut self, value: &str) -> Result<(), String> {
if value.len() > MAX_BYTES.saturating_sub(self.output.len()) {
return Err("wire JSON exceeds byte limit".into());
}
self.scalar(&serde_json::to_string(value).map_err(|error| error.to_string())?)
}
fn scalar(&mut self, text: &str) -> Result<(), String> {
self.value()?;
self.append(text)
}
pub fn json(&mut self, text: &str) -> Result<(), String> {
if text.len() > MAX_BYTES.saturating_sub(self.output.len()) {
return Err("wire JSON exceeds byte limit".into());
}
let raw: &RawValue = serde_json::from_str(text).map_err(|error| error.to_string())?;
let mut remaining = MAX_ELEMENTS - self.elements;
parse_node(raw, self.stack.len(), &mut remaining)?;
self.value()?;
self.elements = MAX_ELEMENTS - remaining;
self.append(raw.get())
}
pub fn begin_array(&mut self) -> Result<(), String> {
self.begin(false)
}
pub fn begin_object(&mut self) -> Result<(), String> {
self.begin(true)
}
fn begin(&mut self, object: bool) -> Result<(), String> {
if self.stack.len() >= MAX_DEPTH {
return Err("wire JSON exceeds depth limit".into());
}
self.value()?;
self.append(if object { "{" } else { "[" })?;
self.stack.push(if object {
Frame::Object {
names: BTreeSet::new(),
awaiting_value: false,
}
} else {
Frame::Array(0)
});
Ok(())
}
pub fn key(&mut self, name: &str) -> Result<(), String> {
if name.len() > MAX_BYTES.saturating_sub(self.output.len()) {
return Err("wire JSON exceeds byte limit".into());
}
let Some(Frame::Object {
names,
awaiting_value,
}) = self.stack.last_mut()
else {
return Err("wire field outside record".into());
};
if *awaiting_value {
return Err("wire field has no value".into());
}
if !names.insert(name.to_owned()) {
return Err("duplicate JSON field".into());
}
*awaiting_value = true;
let comma = names.len() > 1;
if comma {
self.append(",")?;
}
self.append(&serde_json::to_string(name).map_err(|error| error.to_string())?)?;
self.append(":")
}
pub fn end(&mut self) -> Result<(), String> {
if matches!(
self.stack.last(),
Some(Frame::Object {
awaiting_value: true,
..
})
) {
return Err("incomplete wire container".into());
}
match self.stack.pop() {
Some(Frame::Array(_)) => self.append("]"),
Some(Frame::Object {
awaiting_value: false,
..
}) => self.append("}"),
_ => Err("incomplete wire container".into()),
}
}
pub fn finish(self) -> Result<Encoded, String> {
if self.failed || !self.stack.is_empty() || self.elements == 0 {
return Err("incomplete wire JSON".into());
}
Ok(Encoded(self.output))
}
}