Skip to main content

jsonld_context_processing/
stack.rs

1use std::sync::Arc;
2
3/// Maximum depth of the remote context chain.
4///
5/// The [context processing algorithm][1] mandates a processor-defined limit
6/// on the number of entries in the `remote contexts` array; exceeding it is a
7/// `context overflow` error. This bounds the damage a hostile loader can do
8/// by serving an endless chain of distinct context IRIs.
9///
10/// [1]: <https://www.w3.org/TR/json-ld11-api/#context-processing-algorithm>
11pub const MAX_REMOTE_CONTEXTS: usize = 64;
12
13/// Single frame of the context processing stack.
14struct StackNode<I> {
15    /// Previous frame.
16    previous: Option<Arc<StackNode<I>>>,
17
18    /// URL of the last loaded context.
19    url: I,
20}
21
22impl<I> StackNode<I> {
23    /// Creates a stack frame recording the load of `url` on top of `previous`.
24    fn new(previous: Option<Arc<StackNode<I>>>, url: I) -> StackNode<I> {
25        StackNode { previous, url }
26    }
27
28    /// Checks whether this frame or any frame below it holds `url`.
29    fn contains(&self, url: &I) -> bool
30    where
31        I: PartialEq,
32    {
33        if self.url == *url {
34            true
35        } else {
36            match &self.previous {
37                Some(prev) => prev.contains(url),
38                None => false,
39            }
40        }
41    }
42}
43
44/// The chain of remote context URLs currently being processed.
45///
46/// This is the specification's `remote contexts` array. It serves two purposes:
47/// spotting a context that includes itself, and bounding the length of a remote
48/// context chain at [`MAX_REMOTE_CONTEXTS`]. Whether it is empty also tells the
49/// algorithm whether the context being processed came from a remote document,
50/// which is what decides if its `@base` entry applies.
51///
52/// Implemented as an immutable singly-linked list behind [`Arc`]s, so the copy
53/// each recursive call receives is a pointer bump rather than a clone of the
54/// chain.
55#[derive(Clone)]
56pub struct ProcessingStack<I> {
57    head: Option<Arc<StackNode<I>>>,
58}
59
60impl<I> ProcessingStack<I> {
61    /// Creates an empty stack, meaning no remote context is being processed.
62    #[must_use]
63    pub fn new() -> Self {
64        Self { head: None }
65    }
66
67    /// Checks whether no remote context has been entered, i.e. the context being
68    /// processed is not itself remote.
69    #[must_use]
70    pub fn is_empty(&self) -> bool {
71        self.head.is_none()
72    }
73
74    /// Returns the number of remote contexts on the stack.
75    ///
76    /// Walks the chain, so this is linear in the stack depth.
77    #[must_use]
78    pub fn len(&self) -> usize {
79        let mut len = 0;
80        let mut node = &self.head;
81        while let Some(frame) = node {
82            len += 1;
83            node = &frame.previous;
84        }
85        len
86    }
87
88    /// Checks whether `url` is already on the stack, meaning entering it would
89    /// close a loop.
90    pub fn cycle(&self, url: &I) -> bool
91    where
92        I: PartialEq,
93    {
94        match &self.head {
95            Some(head) => head.contains(url),
96            None => false,
97        }
98    }
99
100    /// Pushes `url` onto the stack, unless it is already there.
101    ///
102    /// Returns `true` when the URL was added, and `false` when it was already on
103    /// the stack and nothing changed. What `false` means is version-dependent:
104    /// JSON-LD 1.0 treats it as a recursive context inclusion error, while 1.1
105    /// simply skips reprocessing the context.
106    pub fn push(&mut self, url: I) -> bool
107    where
108        I: PartialEq,
109    {
110        if self.cycle(&url) {
111            false
112        } else {
113            let mut head = None;
114            std::mem::swap(&mut head, &mut self.head);
115            self.head = Some(Arc::new(StackNode::new(head, url)));
116            true
117        }
118    }
119}
120
121impl<I> Default for ProcessingStack<I> {
122    fn default() -> Self {
123        Self::new()
124    }
125}