Skip to main content

structured_pades/
fields.rs

1//! Historical `AcroForm` field ownership, names and inherited signature types.
2
3use crate::{
4    document::{Document, Graph, GraphObject},
5    error::{Error, ErrorKind, ReadFailure, Syntax},
6    model::admit,
7    range::RangeSource,
8    source::Location,
9};
10use structured_asn1::memory::{HashWork, Memory, MemoryError, OwnedText, OwnedVec, Work};
11
12#[derive(Clone, Copy, PartialEq, Eq, Hash)]
13struct Position {
14    owner: usize,
15    node: usize,
16}
17impl HashWork for Position {
18    fn hash_work(&self) -> Result<u64, MemoryError> {
19        Ok(2)
20    }
21}
22
23/// One actual field dictionary with its full, decoded original field name.
24pub struct Field<'g, 's, D, G> {
25    pub(crate) object: GraphObject<'g, 's, D, G>,
26    pub(crate) name: OwnedText,
27    pub(crate) signature: bool,
28    pub(crate) parent: Option<usize>,
29}
30
31impl<'g, 's, D: Copy + Eq, G: Copy + Eq> Field<'g, 's, D, G> {
32    /// Fully qualified original text name, not an object-number alias.
33    #[must_use]
34    pub fn name(&self) -> &str {
35        self.name.as_str()
36    }
37    /// Original field dictionary, resolved in the selected historical graph.
38    #[must_use]
39    pub fn object(&self) -> GraphObject<'g, 's, D, G> {
40        self.object
41    }
42    /// Actual FT, inherited through the field tree as required by ISO32000.
43    #[must_use]
44    pub fn is_signature(&self) -> bool {
45        self.signature
46    }
47    /// Owning parent field ordinal, independently of widget Parent backlinks.
48    #[must_use]
49    pub fn parent(&self) -> Option<usize> {
50        self.parent
51    }
52}
53
54/// The complete historical field tree. Cycles, shared children and duplicate
55/// full field names refuse rather than selecting one original definition.
56pub struct Fields<'g, 's, D, G> {
57    pub(crate) graph: &'g Graph<'s, D, G>,
58    pub(crate) values: OwnedVec<Field<'g, 's, D, G>, Memory>,
59    index: OwnedVec<usize, Memory>,
60}
61
62impl<'g, 's, D: Copy + Eq, G: Copy + Eq> Fields<'g, 's, D, G> {
63    /// Every original field dictionary, including nonterminal field groups.
64    #[must_use]
65    pub fn values(&self) -> &[Field<'g, 's, D, G>] {
66        &self.values
67    }
68
69    /// Finds one actual full name through the paid sorted field index.
70    pub fn get(&self, name: &str, operation: &impl Work) -> crate::Result<Option<usize>> {
71        let work = self.graph.session.query(operation);
72        let mut low = 0;
73        let mut high = self.index.len();
74        while low < high {
75            let middle = low + (high - low) / 2;
76            let field = &self.values[self.index[middle]];
77            admit(
78                &work,
79                field.object.direct().location(),
80                name.len().min(field.name.len()) as u64 + 1,
81            )?;
82            match field.name().cmp(name) {
83                core::cmp::Ordering::Less => low = middle + 1,
84                core::cmp::Ordering::Greater => high = middle,
85                core::cmp::Ordering::Equal => return Ok(Some(self.index[middle])),
86            }
87        }
88        Ok(None)
89    }
90
91    /// Effective V follows the original parent fields, preserving the actual
92    /// value's source owner. An absent child V is not an empty replacement.
93    pub fn value(
94        &self,
95        ordinal: usize,
96        operation: &impl Work,
97    ) -> crate::Result<Option<GraphObject<'g, 's, D, G>>> {
98        self.inherited(ordinal, b"V", operation)
99    }
100
101    pub(crate) fn inherited(
102        &self,
103        ordinal: usize,
104        key: &[u8],
105        operation: &impl Work,
106    ) -> crate::Result<Option<GraphObject<'g, 's, D, G>>> {
107        let work = self.graph.session.query(operation);
108        let mut next = Some(ordinal);
109        while let Some(index) = next {
110            let field = self.values.get(index).ok_or(Error {
111                kind: ErrorKind::Foreign,
112                location: self.graph.root().direct().location(),
113            })?;
114            admit(&work, field.object.direct().location(), 1)?;
115            if let Some(value) = field.object.get(key, &work)?
116                && value.direct().kind() != crate::object::Type::Null
117            {
118                return Ok(Some(value));
119            }
120            next = field.parent;
121        }
122        Ok(None)
123    }
124
125    /// Finds the original signature field whose V selects this exact dictionary.
126    /// Equal CMS values cannot substitute another field or historical value.
127    pub fn signature(
128        &self,
129        location: Location,
130        operation: &impl Work,
131    ) -> crate::Result<Option<usize>> {
132        let mut result = None;
133        for (ordinal, field) in self.values.iter().enumerate() {
134            admit(operation, location, 1)?;
135            if !field.signature {
136                continue;
137            }
138            if field
139                .object
140                .get(b"V", operation)?
141                .is_some_and(|value| value.direct().location() == location)
142            {
143                if result.is_some() {
144                    return Err(Error {
145                        kind: ErrorKind::Ambiguous(Syntax::Dictionary),
146                        location,
147                    });
148                }
149                result = Some(ordinal);
150            }
151        }
152        Ok(result)
153    }
154}
155
156struct Pending<'g, 's, D, G> {
157    object: GraphObject<'g, 's, D, G>,
158    parent: Option<usize>,
159    signature: bool,
160}
161
162fn ordered<D: Copy + Eq, G: Copy + Eq, W: Work>(
163    values: &[Field<'_, '_, D, G>],
164    location: Location,
165    work: &W,
166) -> crate::Result<OwnedVec<usize, W>> {
167    let mut ordered = work.vector();
168    for (ordinal, field) in values.iter().enumerate() {
169        admit(work, field.object.direct().location(), 1)?;
170        if !field.name.is_empty() {
171            ordered.try_push(ordinal).map_err(|cause| Error {
172                kind: ErrorKind::Resource(cause),
173                location,
174            })?;
175        }
176    }
177    ordered
178        .try_sort_unstable_by(|&a, &b| {
179            let first = values[a].name();
180            let second = values[b].name();
181            admit(
182                work,
183                values[b].object.direct().location(),
184                first.len().min(second.len()) as u64 + 1,
185            )?;
186            Ok::<_, Error>(first.cmp(second))
187        })
188        .map_err(|cause| match cause {
189            structured_asn1::memory::SortError::Resource(cause) => Error {
190                kind: ErrorKind::Resource(cause),
191                location,
192            },
193            structured_asn1::memory::SortError::Comparison(cause) => cause,
194        })?;
195    for pair in ordered.windows(2) {
196        admit(
197            work,
198            location,
199            values[pair[0]].name.len().min(values[pair[1]].name.len()) as u64 + 1,
200        )?;
201        if values[pair[0]].name == values[pair[1]].name {
202            return Err(Error {
203                kind: ErrorKind::Ambiguous(Syntax::Dictionary),
204                location: values[pair[1]].object.direct().location(),
205            });
206        }
207    }
208    Ok(ordered)
209}
210
211fn seed<'g, 's, D: Copy + Eq, G: Copy + Eq, W: Work>(
212    graph: &'g Graph<'s, D, G>,
213    pending: &mut OwnedVec<Pending<'g, 's, D, G>, W>,
214    location: Location,
215    work: &impl Work,
216) -> crate::Result<()> {
217    if let Some(form) = graph.root().get(b"AcroForm", work)?
218        && let Some(roots) = form.get(b"Fields", work)?
219    {
220        let members = roots.members(work).ok_or(Error {
221            kind: ErrorKind::Syntax(Syntax::Dictionary),
222            location: roots.direct().location(),
223        })?;
224        for root in members {
225            let object = root?.resolved(work)?.ok_or(Error {
226                kind: ErrorKind::Syntax(Syntax::Dictionary),
227                location,
228            })?;
229            pending
230                .try_push(Pending {
231                    object,
232                    parent: None,
233                    signature: false,
234                })
235                .map_err(|cause| Error {
236                    kind: ErrorKind::Resource(cause),
237                    location,
238                })?;
239        }
240    }
241    Ok(())
242}
243
244impl<'s, S: RangeSource> Document<'s, S> {
245    async fn field_name<'g, W: Work>(
246        &self,
247        graph: &'g Graph<'s, S::Document, S::Generation>,
248        object: GraphObject<'g, 's, S::Document, S::Generation>,
249        parent_name: Option<&str>,
250        work: &W,
251    ) -> Result<OwnedText<W>, ReadFailure<S::Error>> {
252        let location = object.direct().location();
253        let mut name = if let Some(parent) = parent_name {
254            OwnedText::try_from_str(parent, work).map_err(|cause| Error {
255                kind: ErrorKind::Resource(cause),
256                location,
257            })?
258        } else {
259            OwnedText::try_from_str("", work).map_err(|cause| Error {
260                kind: ErrorKind::Resource(cause),
261                location,
262            })?
263        };
264        if let Some(part) = object.get(b"T", work)? {
265            let reading = graph.nodes[part.owner]
266                .reading
267                .as_ref()
268                .expect("resolved field text owner");
269            let bytes = self
270                .string_in(
271                    reading,
272                    graph.decoded(part.owner),
273                    part.node,
274                    usize::MAX,
275                    work,
276                )
277                .await?;
278            let text = crate::text::read(&bytes, part.direct().location(), work)?;
279            admit(work, part.direct().location(), text.len() as u64)?;
280            if text.as_bytes().contains(&b'.') || text.is_empty() {
281                return Err(Error {
282                    kind: ErrorKind::Syntax(Syntax::String),
283                    location: part.direct().location(),
284                }
285                .into());
286            }
287            if !name.is_empty() {
288                name.try_push_str(".").map_err(|cause| Error {
289                    kind: ErrorKind::Resource(cause),
290                    location,
291                })?;
292            }
293            name.try_push_str(text.as_str()).map_err(|cause| Error {
294                kind: ErrorKind::Resource(cause),
295                location,
296            })?;
297        }
298        Ok(name)
299    }
300
301    async fn read_field<'g, W: Work>(
302        &self,
303        graph: &'g Graph<'s, S::Document, S::Generation>,
304        item: &Pending<'g, 's, S::Document, S::Generation>,
305        values: &[Field<'g, 's, S::Document, S::Generation>],
306        work: &W,
307    ) -> Result<Option<Field<'g, 's, S::Document, S::Generation>>, ReadFailure<S::Error>> {
308        let object = item.object;
309        let parent = item.parent;
310        let inherited_signature = item.signature;
311        let location = object.direct().location();
312        if object.direct().kind() != crate::object::Type::Dictionary {
313            return Err(Error {
314                kind: ErrorKind::Syntax(Syntax::Dictionary),
315                location,
316            }
317            .into());
318        }
319        if let Some(parent) = parent {
320            let backlink = object.get(b"Parent", work)?.ok_or(Error {
321                kind: ErrorKind::Syntax(Syntax::Dictionary),
322                location,
323            })?;
324            let expected = values[parent].object;
325            if backlink.direct().location() != expected.direct().location() {
326                return Err(Error {
327                    kind: ErrorKind::Ambiguous(Syntax::Dictionary),
328                    location,
329                }
330                .into());
331            }
332        }
333        let signature = match object.get(b"FT", work)? {
334            Some(kind) => kind.direct().name_equals(b"Sig", work)?,
335            None => inherited_signature,
336        };
337        let name = self
338            .field_name(
339                graph,
340                object,
341                parent.map(|ordinal| values[ordinal].name()),
342                work,
343            )
344            .await?;
345        // A pure widget child is not another field with the same name.
346        let widget = object
347            .get(b"Subtype", work)?
348            .map(|value| value.direct().name_equals(b"Widget", work))
349            .transpose()?
350            .unwrap_or(false);
351        if widget && object.get(b"T", work)?.is_none() && object.get(b"FT", work)?.is_none() {
352            return Ok(None);
353        }
354        Ok(Some(Field {
355            object,
356            name: name.into_owned(),
357            signature,
358            parent,
359        }))
360    }
361
362    /// Reads the actual `AcroForm` tree iteratively under current and original
363    /// funding. PDF text-name decoding is shared with `FieldMDP` selections.
364    ///
365    /// # Panics
366    /// Panics if a completed graph loses a resolved field-name owner.
367    pub async fn fields<'g, W: Work>(
368        &self,
369        graph: &'g Graph<'s, S::Document, S::Generation>,
370        operation: &W,
371    ) -> Result<Fields<'g, 's, S::Document, S::Generation>, ReadFailure<S::Error>> {
372        let work = self.session.query(operation);
373        let location = graph.root().direct().location();
374        if !core::ptr::eq(graph.session, self.session) || graph.stamp != self.stamp {
375            return Err(Error {
376                kind: ErrorKind::Foreign,
377                location,
378            }
379            .into());
380        }
381        self.check(&work, location)?;
382        let mut values: OwnedVec<Field<'g, 's, S::Document, S::Generation>, _> = work.vector();
383        let mut pending = work.vector();
384        let mut seen = work.map();
385        seed(graph, &mut pending, location, &work)?;
386        let mut at = 0;
387        while at < pending.len() {
388            admit(&work, location, 1)?;
389            let object = pending[at].object;
390            let location = object.direct().location();
391            let position = Position {
392                owner: object.owner,
393                node: object.node.0,
394            };
395            if seen
396                .try_insert(position, ())
397                .map_err(|cause| Error {
398                    kind: ErrorKind::Resource(cause),
399                    location,
400                })?
401                .is_some()
402            {
403                return Err(Error {
404                    kind: ErrorKind::Ambiguous(Syntax::Cycle),
405                    location,
406                }
407                .into());
408            }
409            let Some(field) = self.read_field(graph, &pending[at], &values, &work).await? else {
410                at += 1;
411                continue;
412            };
413            let signature = field.signature;
414            let ordinal = values.len();
415            if let Some(kids) = object.get(b"Kids", &work)? {
416                let members = kids.members(&work).ok_or(Error {
417                    kind: ErrorKind::Syntax(Syntax::Dictionary),
418                    location: kids.direct().location(),
419                })?;
420                for child in members {
421                    let child = child?.resolved(&work)?.ok_or(Error {
422                        kind: ErrorKind::Syntax(Syntax::Dictionary),
423                        location,
424                    })?;
425                    pending
426                        .try_push(Pending {
427                            object: child,
428                            parent: Some(ordinal),
429                            signature,
430                        })
431                        .map_err(|cause| Error {
432                            kind: ErrorKind::Resource(cause),
433                            location,
434                        })?;
435                }
436            }
437            values.try_push(field).map_err(|cause| Error {
438                kind: ErrorKind::Resource(cause),
439                location,
440            })?;
441            at += 1;
442        }
443        let ordered = ordered(&values, location, &work)?;
444        self.check(&work, location)?;
445        Ok(Fields {
446            graph,
447            values: values.into_owned(),
448            index: ordered.into_owned(),
449        })
450    }
451}