Skip to main content

zenmeta/rewrite/
mod.rs

1//! Common bounded packet processing for codec-owned metadata rewrite planners.
2//! Public codec signatures use zencodec's request and plan types; these helpers
3//! remain implementation details that can evolve with zenmeta independently.
4use alloc::{borrow::Cow, vec::Vec};
5use core::ops::Range;
6use zencodec::{
7    Orientation, enough,
8    metadata_rewrite::{Action, Compression, Entry, Error, Plan, RewriteRequest, Services},
9};
10mod exif;
11
12/// Shared request validation and metadata packet processing.
13#[derive(Clone)]
14pub struct Context<'a> {
15    request: RewriteRequest<'a>,
16}
17impl<'a> core::ops::Deref for Context<'a> {
18    type Target = RewriteRequest<'a>;
19    fn deref(&self) -> &Self::Target {
20        &self.request
21    }
22}
23impl<'a> Context<'a> {
24    /// Validate a metadata-only request before invoking a container walker.
25    pub fn new(request: &RewriteRequest<'a>) -> Result<Self, Error> {
26        let context = Self {
27            request: request.clone(),
28        };
29        context.check()?;
30        if (request.edit_xmp() || request.normalize_icc()) && request.services().is_none() {
31            return Err(Error::Unsupported("metadata service not supplied"));
32        }
33        Ok(context)
34    }
35    /// Poll cancellation while preserving the caller's stop reason.
36    pub fn check(&self) -> Result<(), Error> {
37        self.pulse()
38            .unwrap_or(&zencodec::NoPulse)
39            .check()
40            .map_err(|reason| match reason {
41                enough::StopReason::Cancelled => Error::Cancelled,
42                other => Error::Stopped(other),
43            })?;
44        if self.input().len() > self.max_input() {
45            return Err(Error::Limit(zencodec::LimitKind::InputSize));
46        }
47        Ok(())
48    }
49    /// Enforce the parsed/expanded packet or regenerated metadata budget.
50    pub fn packet(&self, size: usize) -> Result<(), Error> {
51        self.check()?;
52        if size > self.max_metadata() {
53            Err(Error::Limit(zencodec::LimitKind::MetadataSize))
54        } else {
55            Ok(())
56        }
57    }
58    /// Reuse the parent policy and limits for a contained image.
59    pub fn with_input(mut self, input: &'a [u8]) -> Self {
60        self.request = self.request.with_input(input);
61        self
62    }
63    /// Override descriptive XMP handling for a contained image.
64    pub fn with_xmp_editing(mut self, enabled: bool) -> Self {
65        self.request = self.request.with_xmp_editing(enabled);
66        self
67    }
68    /// Require an application service provider.
69    pub fn services(&self) -> Result<&dyn Services, Error> {
70        self.request
71            .services()
72            .ok_or(Error::Unsupported("metadata service not supplied"))
73    }
74    /// Apply explicitly enabled descriptive XMP editing within packet limits.
75    pub fn xmp(&self, xml: &[u8]) -> Result<Option<Vec<u8>>, Error> {
76        self.packet(xml.len())?;
77        if !self.edit_xmp() {
78            self.check()?;
79            return if self.policy().fields().xmp.keeps() {
80                let mut kept = Vec::new();
81                kept.try_reserve_exact(xml.len())
82                    .map_err(|_| Error::Allocation)?;
83                kept.extend_from_slice(xml);
84                Ok(Some(kept))
85            } else {
86                Ok(None)
87            };
88        }
89        let out = self
90            .services()?
91            .rewrite_xmp(xml, self.max_metadata(), self.pulse())?;
92        if let Some(ref bytes) = out {
93            self.packet(bytes.len())?;
94        }
95        self.check()?;
96        Ok(out)
97    }
98    /// Normalize a known ICC profile while retaining rendering dependencies.
99    pub fn icc(&self, bytes: &[u8]) -> Result<Option<Vec<u8>>, Error> {
100        self.packet(bytes.len())?;
101        if matches!(self.policy().fields().icc, zencodec::IccRetention::Drop) {
102            return Err(Error::Unsupported(
103                "removing an ICC profile requires explicit color conversion",
104            ));
105        }
106        if !self.normalize_icc() {
107            return Ok(None);
108        }
109        let out = self
110            .services()?
111            .normalize_icc(bytes, self.max_metadata(), self.pulse())?;
112        if let Some(ref bytes) = out {
113            self.packet(bytes.len())?;
114            if bytes.len() < 128 || bytes.get(36..40) != Some(b"acsp") {
115                return Err(Error::Service("normalizer returned an invalid ICC header"));
116            }
117        }
118        self.check()?;
119        Ok(out)
120    }
121    /// Expand a metadata packet through a bounded caller service.
122    pub fn decompress(&self, kind: Compression, bytes: &[u8]) -> Result<Vec<u8>, Error> {
123        self.packet(bytes.len())?;
124        let out = self
125            .services()?
126            .decompress(kind, bytes, self.max_metadata(), self.pulse())?;
127        self.packet(out.len())?;
128        self.check()?;
129        Ok(out)
130    }
131    /// Validate and filter EXIF while preserving orientation.
132    pub fn exif(&self, bytes: &[u8]) -> Result<Vec<u8>, Error> {
133        self.packet(bytes.len())?;
134        exif::validate(self, bytes)?;
135        let exif = zencodec::exif::Exif::parse(bytes).ok_or(Error::Malformed("EXIF"))?;
136        let orientation = exif.orientation().unwrap_or(Orientation::Identity);
137        let filtered = crate::exif::filter(&exif, &self.policy().fields().exif);
138        if filtered.orientation().unwrap_or(Orientation::Identity) != orientation {
139            return Err(Error::Unsupported(
140                "removing orientation requires explicit pixel transformation",
141            ));
142        }
143        let output = crate::exif::serialize(&filtered);
144        self.packet(output.len())?;
145        Ok(output)
146    }
147}
148
149/// Fallible, bounded assembly shared by codec-specific rewrite planners.
150#[derive(Debug, Default)]
151pub struct PlanBuilder<'a> {
152    parts: Vec<Cow<'a, [u8]>>,
153    entries: Vec<Entry>,
154    size: usize,
155    rewritten: usize,
156    input: Option<&'a [u8]>,
157}
158impl<'a> PlanBuilder<'a> {
159    /// Start an empty plan; each carrier operation checks its request budget.
160    pub fn new() -> Self {
161        Self::default()
162    }
163    /// Record one input carrier and an optional output chunk, enforcing budgets.
164    pub fn record(
165        &mut self,
166        req: &Context<'a>,
167        range: Range<usize>,
168        carrier: &'static str,
169        action: Action,
170        bytes: Option<Cow<'a, [u8]>>,
171    ) -> Result<(), Error> {
172        req.check()?;
173        if range.start > range.end || range.end > req.input().len() {
174            return Err(Error::Malformed("rewrite source range"));
175        }
176        let input = req.input();
177        if self
178            .input
179            .is_some_and(|previous| !core::ptr::eq(previous, input))
180        {
181            return Err(Error::Malformed("mixed rewrite inputs"));
182        }
183        if self.entries.len() >= req.max_carriers() {
184            return Err(Error::Limit(zencodec::LimitKind::MetadataCarriers));
185        }
186        let added = bytes.as_ref().map_or(0, |b| b.len());
187        let size = self
188            .size
189            .checked_add(added)
190            .filter(|n| *n <= req.max_output())
191            .ok_or(Error::Limit(zencodec::LimitKind::OutputSize))?;
192        let rewritten = self
193            .rewritten
194            .checked_add(
195                bytes
196                    .as_ref()
197                    .filter(|b| matches!(b, Cow::Owned(_)))
198                    .map_or(0, |b| b.len()),
199            )
200            .ok_or(Error::Limit(zencodec::LimitKind::MetadataRewriteSize))?;
201        if rewritten > req.max_metadata() {
202            return Err(Error::Limit(zencodec::LimitKind::MetadataRewriteSize));
203        }
204        self.entries.try_reserve(1).map_err(|_| Error::Allocation)?;
205        if bytes.is_some() {
206            self.parts.try_reserve(1).map_err(|_| Error::Allocation)?;
207        }
208        self.entries.push(Entry::new(range, carrier, action));
209        if let Some(bytes) = bytes {
210            self.parts.push(bytes);
211        }
212        self.size = size;
213        self.rewritten = rewritten;
214        self.input = Some(input);
215        Ok(())
216    }
217
218    /// Retain a validated range from the encoded input.
219    pub fn keep(
220        &mut self,
221        req: &Context<'a>,
222        range: Range<usize>,
223        name: &'static str,
224    ) -> Result<(), Error> {
225        let bytes = req
226            .input()
227            .get(range.clone())
228            .ok_or(Error::Malformed("rewrite source range"))?;
229        self.record(req, range, name, Action::Keep, Some(Cow::Borrowed(bytes)))
230    }
231    /// Record a removed input carrier.
232    pub fn remove(
233        &mut self,
234        req: &Context<'a>,
235        range: Range<usize>,
236        name: &'static str,
237    ) -> Result<(), Error> {
238        self.record(req, range, name, Action::Remove, None)
239    }
240    /// Current encoded output size.
241    pub fn output_len(&self) -> usize {
242        self.size
243    }
244    /// Insert a generated carrier immediately after the first output chunk.
245    pub fn inject_after_first(
246        &mut self,
247        req: &Context<'a>,
248        bytes: Vec<u8>,
249        name: &'static str,
250    ) -> Result<(), Error> {
251        if self.parts.is_empty() {
252            return Err(Error::Malformed("missing leading carrier"));
253        }
254        self.record(req, 0..0, name, Action::Rewrite, Some(Cow::Owned(bytes)))?;
255        let part = self
256            .parts
257            .pop()
258            .ok_or(Error::Malformed("missing generated carrier"))?;
259        self.parts.insert(1, part);
260        Ok(())
261    }
262    /// Remove a previously retained carrier before inserting its replacement.
263    pub fn remove_kept_range(
264        &mut self,
265        req: &Context<'a>,
266        range: &Range<usize>,
267    ) -> Result<(), Error> {
268        req.check()?;
269        let input = req.input();
270        if self
271            .input
272            .is_some_and(|previous| !core::ptr::eq(previous, input))
273        {
274            return Err(Error::Malformed("mixed rewrite inputs"));
275        }
276        let source = req
277            .input()
278            .get(range.clone())
279            .ok_or(Error::Malformed("rewrite source range"))?;
280        let index = self.parts.iter().position(|p| matches!(p, Cow::Borrowed(b) if b.as_ptr() == source.as_ptr() && b.len() == source.len()))
281            .ok_or(Error::Malformed("missing retained carrier"))?;
282        self.size -= self.parts.remove(index).len();
283        for entry in &mut self.entries {
284            if entry.source_range() == *range {
285                *entry = Entry::new(range.clone(), entry.carrier(), Action::Remove);
286            }
287        }
288        Ok(())
289    }
290    /// Append a contained-image plan, translating its report to parent offsets.
291    pub fn append(&mut self, req: &Context<'a>, other: Self, offset: usize) -> Result<(), Error> {
292        req.check()?;
293        let input = req.input();
294        if self
295            .input
296            .is_some_and(|previous| !core::ptr::eq(previous, input))
297        {
298            return Err(Error::Malformed("mixed rewrite inputs"));
299        }
300        if offset
301            .checked_add(other.input.map_or(0, <[u8]>::len))
302            .is_none_or(|end| end > req.input().len())
303        {
304            return Err(Error::Malformed("contained input range"));
305        }
306        if let Some(child_input) = other.input {
307            // Range arithmetic was checked above. A matching length alone
308            // cannot prove that translated report offsets describe this input.
309            let expected = &input[offset..offset + child_input.len()];
310            if !core::ptr::eq(child_input, expected) {
311                return Err(Error::Malformed("contained input identity"));
312            }
313        }
314        let size = self
315            .size
316            .checked_add(other.size)
317            .filter(|n| *n <= req.max_output())
318            .ok_or(Error::Limit(zencodec::LimitKind::OutputSize))?;
319        let rewritten = self
320            .rewritten
321            .checked_add(other.rewritten)
322            .ok_or(Error::Limit(zencodec::LimitKind::MetadataRewriteSize))?;
323        if rewritten > req.max_metadata() {
324            return Err(Error::Limit(zencodec::LimitKind::MetadataRewriteSize));
325        }
326        if self
327            .entries
328            .len()
329            .checked_add(other.entries.len())
330            .is_none_or(|n| n > req.max_carriers())
331        {
332            return Err(Error::Limit(zencodec::LimitKind::MetadataCarriers));
333        }
334        for entry in &other.entries {
335            let range = entry.source_range();
336            if range.start.checked_add(offset).is_none()
337                || range
338                    .end
339                    .checked_add(offset)
340                    .is_none_or(|end| end > req.input().len())
341            {
342                return Err(Error::Malformed("report offset"));
343            }
344        }
345        self.parts
346            .try_reserve(other.parts.len())
347            .map_err(|_| Error::Allocation)?;
348        self.entries
349            .try_reserve(other.entries.len())
350            .map_err(|_| Error::Allocation)?;
351        for entry in other.entries {
352            let range = entry.source_range();
353            self.entries.push(Entry::new(
354                range.start + offset..range.end + offset,
355                entry.carrier(),
356                entry.action(),
357            ));
358        }
359        self.input = Some(input);
360        self.parts.extend(other.parts);
361        self.size = size;
362        self.rewritten = rewritten;
363        Ok(())
364    }
365    /// Finish with the stable zencodec-owned plan type.
366    pub fn finish(self) -> Result<Plan<'a>, Error> {
367        let plan = Plan::from_parts(self.input.map_or(0, <[u8]>::len), self.parts, self.entries)?;
368        if plan.output_len() != self.size {
369            return Err(Error::Malformed("rewrite length accounting"));
370        }
371        Ok(plan)
372    }
373}