Skip to main content

hayro_syntax/object/
string.rs

1//! Strings.
2
3use crate::crypto::DecryptionTarget;
4use crate::filter::ascii_hex;
5use crate::object::Object;
6use crate::object::macros::object;
7use crate::reader::Reader;
8use crate::reader::{Readable, ReaderContext, ReaderExt, Skippable};
9use crate::trivia::is_white_space_character;
10use alloc::vec::Vec;
11use core::borrow::Borrow;
12use core::hash::{Hash, Hasher};
13use core::ops::Deref;
14use smallvec::SmallVec;
15
16#[derive(Clone)]
17enum StringInner<'a> {
18    Borrowed(&'a [u8]),
19    Owned(SmallVec<[u8; 23]>),
20}
21
22impl AsRef<[u8]> for StringInner<'_> {
23    fn as_ref(&self) -> &[u8] {
24        match self {
25            Self::Borrowed(data) => data,
26            Self::Owned(data) => data,
27        }
28    }
29}
30
31/// A PDF string object.
32#[derive(Clone)]
33pub struct String<'a>(StringInner<'a>);
34
35impl<'a> String<'a> {
36    /// Returns the string data as a byte slice.
37    pub fn as_bytes(&self) -> &[u8] {
38        self.as_ref()
39    }
40}
41
42impl Deref for String<'_> {
43    type Target = [u8];
44
45    fn deref(&self) -> &Self::Target {
46        self.as_ref()
47    }
48}
49
50impl AsRef<[u8]> for String<'_> {
51    fn as_ref(&self) -> &[u8] {
52        match &self.0 {
53            StringInner::Borrowed(data) => data,
54            StringInner::Owned(data) => data,
55        }
56    }
57}
58
59impl Borrow<[u8]> for String<'_> {
60    fn borrow(&self) -> &[u8] {
61        self.as_ref()
62    }
63}
64
65impl PartialEq for String<'_> {
66    fn eq(&self, other: &Self) -> bool {
67        self.as_ref() == other.as_ref()
68    }
69}
70
71impl Eq for String<'_> {}
72
73impl Hash for String<'_> {
74    fn hash<H: Hasher>(&self, state: &mut H) {
75        self.as_ref().hash(state);
76    }
77}
78
79impl core::fmt::Debug for String<'_> {
80    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
81        <[u8] as core::fmt::Debug>::fmt(self.as_ref(), f)
82    }
83}
84
85impl core::fmt::Display for String<'_> {
86    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
87        match core::str::from_utf8(self.as_bytes()) {
88            Ok(value) => write!(f, "{value:?}"),
89            Err(_) => write!(f, "{:?}", self.as_bytes()),
90        }
91    }
92}
93
94object!(String<'a>, String);
95
96impl Skippable for String<'_> {
97    fn skip(r: &mut Reader<'_>, _: bool) -> Option<()> {
98        match r.peek_byte()? {
99            b'<' => skip_hex(r),
100            b'(' => skip_literal(r),
101            _ => None,
102        }
103    }
104}
105
106impl<'a> Readable<'a> for String<'a> {
107    fn read(r: &mut Reader<'a>, ctx: &ReaderContext<'a>) -> Option<Self> {
108        let decoded = match r.peek_byte()? {
109            b'<' => StringInner::Owned(read_hex(r)?),
110            b'(' => read_literal(r)?,
111            _ => return None,
112        };
113
114        // Apply decryption if needed.
115        let final_data = if ctx.xref().needs_decryption(ctx) {
116            if let Some(obj_number) = ctx.obj_number() {
117                ctx.xref()
118                    .decrypt(obj_number, decoded.as_ref(), DecryptionTarget::String)
119                    .map(StringInner::from)
120                    .unwrap_or(decoded)
121            } else {
122                decoded
123            }
124        } else {
125            decoded
126        };
127
128        Some(Self(final_data))
129    }
130}
131
132impl From<Vec<u8>> for StringInner<'_> {
133    fn from(value: Vec<u8>) -> Self {
134        Self::Owned(SmallVec::from_vec(value))
135    }
136}
137
138fn skip_hex(r: &mut Reader<'_>) -> Option<()> {
139    r.forward_tag(b"<")?;
140    while let Some(b) = r.peek_byte() {
141        let is_hex = b.is_ascii_hexdigit();
142        let is_whitespace = is_white_space_character(b);
143
144        if !is_hex && !is_whitespace {
145            break;
146        }
147
148        r.read_byte()?;
149    }
150    r.forward_tag(b">")?;
151
152    Some(())
153}
154
155fn read_hex(r: &mut Reader<'_>) -> Option<SmallVec<[u8; 23]>> {
156    let start = r.offset();
157    skip_hex(r)?;
158    let end = r.offset();
159
160    // Exclude outer brackets.
161    let raw = r.range(start + 1..end - 1)?;
162    let decoded = ascii_hex::decode_into(raw)?;
163
164    Some(decoded)
165}
166
167fn skip_literal(r: &mut Reader<'_>) -> Option<()> {
168    r.forward_tag(b"(")?;
169    let mut bracket_counter = 1;
170
171    while bracket_counter > 0 {
172        let byte = r.read_byte()?;
173
174        match byte {
175            b'\\' => {
176                let _ = r.read_byte()?;
177            }
178            b'(' => bracket_counter += 1,
179            b')' => bracket_counter -= 1,
180            _ => {}
181        };
182    }
183
184    Some(())
185}
186
187fn read_literal<'a>(r: &mut Reader<'a>) -> Option<StringInner<'a>> {
188    let start = r.offset();
189    skip_literal(r)?;
190    let end = r.offset();
191
192    // Exclude outer parentheses.
193    let data = r.range(start + 1..end - 1)?;
194
195    if !data.iter().any(|b| matches!(b, b'\\' | b'\n' | b'\r')) {
196        return Some(StringInner::Borrowed(data));
197    }
198
199    let mut r = Reader::new(data);
200    let mut result = SmallVec::new();
201
202    while let Some(byte) = r.read_byte() {
203        match byte {
204            b'\\' => {
205                let next = r.read_byte()?;
206
207                if is_octal_digit(next) {
208                    let second = r.read_byte();
209                    let third = r.read_byte();
210
211                    let bytes = match (second, third) {
212                        (Some(n1), Some(n2)) => match (is_octal_digit(n1), is_octal_digit(n2)) {
213                            (true, true) => [next, n1, n2],
214                            (true, _) => {
215                                r.jump(r.offset() - 1);
216                                [b'0', next, n1]
217                            }
218                            _ => {
219                                r.jump(r.offset() - 2);
220                                [b'0', b'0', next]
221                            }
222                        },
223                        (Some(n1), None) => {
224                            if is_octal_digit(n1) {
225                                [b'0', next, n1]
226                            } else {
227                                r.jump(r.offset() - 1);
228                                [b'0', b'0', next]
229                            }
230                        }
231                        _ => [b'0', b'0', next],
232                    };
233
234                    let str = core::str::from_utf8(&bytes).unwrap();
235
236                    if let Ok(num) = u8::from_str_radix(str, 8) {
237                        result.push(num);
238                    } else {
239                        warn!("overflow occurred while parsing octal literal string");
240                    }
241                } else {
242                    match next {
243                        b'n' => result.push(0xA),
244                        b'r' => result.push(0xD),
245                        b't' => result.push(0x9),
246                        b'b' => result.push(0x8),
247                        b'f' => result.push(0xC),
248                        b'(' => result.push(b'('),
249                        b')' => result.push(b')'),
250                        b'\\' => result.push(b'\\'),
251                        b'\n' | b'\r' => {
252                            // A conforming reader shall disregard the REVERSE SOLIDUS
253                            // and the end-of-line marker following it when reading
254                            // the string; the resulting string value shall be
255                            // identical to that which would be read if the string
256                            // were not split.
257                            r.skip_eol_characters();
258                        }
259                        _ => result.push(next),
260                    }
261                }
262            }
263            b'(' | b')' => result.push(byte),
264            // An end-of-line marker appearing within a literal string
265            // without a preceding REVERSE SOLIDUS shall be treated as
266            // a byte value of (0Ah), irrespective of whether the end-of-line
267            // marker was a CARRIAGE RETURN (0Dh), a LINE FEED (0Ah), or both.
268            b'\n' | b'\r' => {
269                result.push(b'\n');
270                r.skip_eol_characters();
271            }
272            other => result.push(other),
273        }
274    }
275
276    Some(StringInner::Owned(result))
277}
278
279fn is_octal_digit(byte: u8) -> bool {
280    matches!(byte, b'0'..=b'7')
281}
282
283#[cfg(test)]
284mod tests {
285    use crate::object::String;
286    use crate::reader::Reader;
287    use crate::reader::ReaderExt;
288
289    #[test]
290    fn display() {
291        let normal = Reader::new(b"(Hello)")
292            .read_without_context::<String<'_>>()
293            .unwrap();
294        let binary = Reader::new(b"<00FF>")
295            .read_without_context::<String<'_>>()
296            .unwrap();
297
298        assert_eq!(format!("{normal}"), "\"Hello\"");
299        assert_eq!(format!("{binary}"), "[0, 255]");
300    }
301
302    #[test]
303    fn hex_string_empty() {
304        assert_eq!(
305            Reader::new(b"<>")
306                .read_without_context::<String<'_>>()
307                .unwrap()
308                .as_bytes(),
309            b""
310        );
311    }
312
313    #[test]
314    fn hex_string_1() {
315        assert_eq!(
316            Reader::new(b"<00010203>")
317                .read_without_context::<String<'_>>()
318                .unwrap()
319                .as_bytes(),
320            &[0x00, 0x01, 0x02, 0x03]
321        );
322    }
323
324    #[test]
325    fn hex_string_2() {
326        assert_eq!(
327            Reader::new(b"<000102034>")
328                .read_without_context::<String<'_>>()
329                .unwrap()
330                .as_bytes(),
331            &[0x00, 0x01, 0x02, 0x03, 0x40]
332        );
333    }
334
335    #[test]
336    fn hex_string_trailing_1() {
337        assert_eq!(
338            Reader::new(b"<000102034>dfgfg4")
339                .read_without_context::<String<'_>>()
340                .unwrap()
341                .as_bytes(),
342            &[0x00, 0x01, 0x02, 0x03, 0x40]
343        );
344    }
345
346    #[test]
347    fn hex_string_trailing_2() {
348        assert_eq!(
349            Reader::new(b"<1  3 4>dfgfg4")
350                .read_without_context::<String<'_>>()
351                .unwrap()
352                .as_bytes(),
353            &[0x13, 0x40]
354        );
355    }
356
357    #[test]
358    fn hex_string_trailing_3() {
359        assert_eq!(
360            Reader::new(b"<1>dfgfg4")
361                .read_without_context::<String<'_>>()
362                .unwrap()
363                .as_bytes(),
364            &[0x10]
365        );
366    }
367
368    #[test]
369    fn hex_string_invalid_1() {
370        assert!(
371            Reader::new(b"<")
372                .read_without_context::<String<'_>>()
373                .is_none()
374        );
375    }
376
377    #[test]
378    fn hex_string_invalid_2() {
379        assert!(
380            Reader::new(b"34AD")
381                .read_without_context::<String<'_>>()
382                .is_none()
383        );
384    }
385
386    #[test]
387    fn literal_string_empty() {
388        assert_eq!(
389            Reader::new(b"()")
390                .read_without_context::<String<'_>>()
391                .unwrap()
392                .as_bytes(),
393            b""
394        );
395    }
396
397    #[test]
398    fn literal_string_1() {
399        assert_eq!(
400            Reader::new(b"(Hi there.)")
401                .read_without_context::<String<'_>>()
402                .unwrap()
403                .as_bytes(),
404            b"Hi there."
405        );
406    }
407
408    #[test]
409    fn literal_string_2() {
410        assert!(
411            Reader::new(b"(Hi \\777)")
412                .read_without_context::<String<'_>>()
413                .is_some()
414        );
415    }
416
417    #[test]
418    fn literal_string_3() {
419        assert_eq!(
420            Reader::new(b"(Hi ) there.)")
421                .read_without_context::<String<'_>>()
422                .unwrap()
423                .as_bytes(),
424            b"Hi "
425        );
426    }
427
428    #[test]
429    fn literal_string_4() {
430        assert_eq!(
431            Reader::new(b"(Hi (()) there)")
432                .read_without_context::<String<'_>>()
433                .unwrap()
434                .as_bytes(),
435            b"Hi (()) there"
436        );
437    }
438
439    #[test]
440    fn literal_string_5() {
441        assert_eq!(
442            Reader::new(b"(Hi \\()")
443                .read_without_context::<String<'_>>()
444                .unwrap()
445                .as_bytes(),
446            b"Hi ("
447        );
448    }
449
450    #[test]
451    fn literal_string_6() {
452        assert_eq!(
453            Reader::new(b"(Hi \\\nthere)")
454                .read_without_context::<String<'_>>()
455                .unwrap()
456                .as_bytes(),
457            b"Hi there"
458        );
459    }
460
461    #[test]
462    fn literal_string_7() {
463        assert_eq!(
464            Reader::new(b"(Hi \\05354)")
465                .read_without_context::<String<'_>>()
466                .unwrap()
467                .as_bytes(),
468            b"Hi +54"
469        );
470    }
471
472    #[test]
473    fn literal_string_8() {
474        assert_eq!(
475            Reader::new(b"(\\3)")
476                .read_without_context::<String<'_>>()
477                .unwrap()
478                .as_bytes(),
479            b"\x03"
480        );
481    }
482
483    #[test]
484    fn literal_string_9() {
485        assert_eq!(
486            Reader::new(b"(\\36)")
487                .read_without_context::<String<'_>>()
488                .unwrap()
489                .as_bytes(),
490            b"\x1e"
491        );
492    }
493
494    #[test]
495    fn literal_string_10() {
496        assert_eq!(
497            Reader::new(b"(\\36ab)")
498                .read_without_context::<String<'_>>()
499                .unwrap()
500                .as_bytes(),
501            b"\x1eab"
502        );
503    }
504
505    #[test]
506    fn literal_string_11() {
507        assert_eq!(
508            Reader::new(b"(\\00Y)")
509                .read_without_context::<String<'_>>()
510                .unwrap()
511                .as_bytes(),
512            b"\0Y"
513        );
514    }
515
516    #[test]
517    fn literal_string_12() {
518        assert_eq!(
519            Reader::new(b"(\\0Y)")
520                .read_without_context::<String<'_>>()
521                .unwrap()
522                .as_bytes(),
523            b"\0Y"
524        );
525    }
526
527    #[test]
528    fn literal_string_trailing() {
529        assert_eq!(
530            Reader::new(b"(Hi there.)abcde")
531                .read_without_context::<String<'_>>()
532                .unwrap()
533                .as_bytes(),
534            b"Hi there."
535        );
536    }
537
538    #[test]
539    fn literal_string_invalid() {
540        assert_eq!(
541            Reader::new(b"(Hi \\778)")
542                .read_without_context::<String<'_>>()
543                .unwrap()
544                .as_bytes(),
545            b"Hi \x3F8"
546        );
547    }
548
549    #[test]
550    fn string_1() {
551        assert_eq!(
552            Reader::new(b"(Hi there.)")
553                .read_without_context::<String<'_>>()
554                .unwrap()
555                .as_bytes(),
556            b"Hi there."
557        );
558    }
559
560    #[test]
561    fn string_2() {
562        assert_eq!(
563            Reader::new(b"<00010203>")
564                .read_without_context::<String<'_>>()
565                .unwrap()
566                .as_bytes(),
567            &[0x00, 0x01, 0x02, 0x03]
568        );
569    }
570}