mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-31 21:37:08 -06:00
Reader::children re-collected a group's child objects by scanning every raw Chld array slot, discarding nulls before the node budget counted anything. A shared group holding a large null-filled child array, linked many times, multiplied into excessive CPU work the MAX_NODES limit never observed. Charge every raw child slot (nulls included) against a document-wide MAX_CHILD_SLOTS budget before filtering, via a new Stream::array_len helper, and return a limit error when it is exhausted. Finding: 3973fcd0-acda-4312-b538-d986dcffeb1c (CWE-400)
115 lines
3.6 KiB
Rust
115 lines
3.6 KiB
Rust
//! Typed reads of [`Stream`] objects. Every accessor returns `None` when a field is missing or has
|
|
//! another type, so the document mapping treats odd data as absent rather than failing.
|
|
|
|
use crate::stream::{ObjId, Object, Stream, Tag, Value};
|
|
|
|
impl Stream {
|
|
pub fn object(&self, id: ObjId) -> Option<&Object> {
|
|
self.objects.get(id)
|
|
}
|
|
|
|
pub fn field(&self, id: ObjId, tag: &[u8; 4]) -> Option<&Value> {
|
|
self.object(id)?.get(Tag::of(tag))
|
|
}
|
|
|
|
pub fn class(&self, id: ObjId) -> Option<Tag> {
|
|
self.object(id).map(|o| o.class)
|
|
}
|
|
|
|
pub fn is(&self, id: ObjId, class: &[u8; 4]) -> bool {
|
|
self.class(id) == Some(Tag::of(class))
|
|
}
|
|
|
|
/// The raw slot count of an object's array field (including null slots), for charging
|
|
/// traversal work before nulls are filtered out. `None` when the field is not an array.
|
|
pub fn array_len(&self, id: ObjId, tag: &[u8; 4]) -> Option<usize> {
|
|
match self.field(id, tag)? {
|
|
Value::Array(a) => Some(a.len()),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// A child object (shared, linked or inline); `None` for null or missing.
|
|
pub fn obj(&self, id: ObjId, tag: &[u8; 4]) -> Option<ObjId> {
|
|
match self.field(id, tag)? {
|
|
Value::Obj(o) => *o,
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// An array of objects (nulls skipped).
|
|
pub fn objs(&self, id: ObjId, tag: &[u8; 4]) -> Vec<ObjId> {
|
|
match self.field(id, tag) {
|
|
Some(Value::Array(a)) => a.iter().filter_map(|v| if let Value::Obj(Some(o)) = v { Some(*o) } else { None }).collect(),
|
|
_ => Vec::new(),
|
|
}
|
|
}
|
|
|
|
pub fn f64(&self, id: ObjId, tag: &[u8; 4]) -> Option<f64> {
|
|
match self.field(id, tag)? {
|
|
Value::Float(f) if f.is_finite() => Some(*f),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn int(&self, id: ObjId, tag: &[u8; 4]) -> Option<i64> {
|
|
match self.field(id, tag)? {
|
|
Value::Int(i) => Some(*i),
|
|
Value::UInt(u) => i64::try_from(*u).ok(),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn bool(&self, id: ObjId, tag: &[u8; 4]) -> Option<bool> {
|
|
match self.field(id, tag)? {
|
|
Value::Bool(b) => Some(*b),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn str(&self, id: ObjId, tag: &[u8; 4]) -> Option<&str> {
|
|
match self.field(id, tag)? {
|
|
Value::Str(s) => Some(s),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn enumeration(&self, id: ObjId, tag: &[u8; 4]) -> Option<(u16, u16)> {
|
|
match self.field(id, tag)? {
|
|
Value::Enum { id, version } => Some((*id, *version)),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// A fixed vector of `N` finite floats (points, rectangles, transforms).
|
|
pub fn floats<const N: usize>(&self, id: ObjId, tag: &[u8; 4]) -> Option<[f64; N]> {
|
|
match self.field(id, tag)? {
|
|
Value::Floats(v) if v.len() == N && v.iter().all(|f| f.is_finite()) => {
|
|
let mut a = [0.0; N];
|
|
a.copy_from_slice(v);
|
|
Some(a)
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn bytes(&self, id: ObjId, tag: &[u8; 4]) -> Option<&[u8]> {
|
|
match self.field(id, tag)? {
|
|
Value::Bytes(b) | Value::Blob(b) => Some(b),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn entry(&self, id: ObjId, tag: &[u8; 4]) -> Option<&str> {
|
|
match self.field(id, tag)? {
|
|
Value::Entry(s) => Some(s),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Untagged fields of a positional object, in order.
|
|
pub fn positional(&self, id: ObjId) -> impl Iterator<Item = &Value> {
|
|
self.object(id).into_iter().flat_map(|o| o.fields.iter().map(|(_, v)| v))
|
|
}
|
|
}
|