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    loop {
203        // An end-of-line marker appearing within a literal string
204        // without a preceding REVERSE SOLIDUS shall be treated as
205        // a byte value of (0Ah), irrespective of whether the end-of-line
206        // marker was a CARRIAGE RETURN (0Dh), a LINE FEED (0Ah), or both.
207        if r.read_eol().is_some() {
208            result.push(b'\n');
209            continue;
210        }
211
212        let Some(byte) = r.read_byte() else {
213            break;
214        };
215
216        match byte {
217            b'\\' => {
218                // A conforming reader shall disregard the REVERSE SOLIDUS
219                // and the end-of-line marker following it when reading
220                // the string; the resulting string value shall be
221                // identical to that which would be read if the string
222                // were not split.
223                if r.read_eol().is_some() {
224                    continue;
225                }
226
227                let next = r.read_byte()?;
228
229                if is_octal_digit(next) {
230                    let second = r.read_byte();
231                    let third = r.read_byte();
232
233                    let bytes = match (second, third) {
234                        (Some(n1), Some(n2)) => match (is_octal_digit(n1), is_octal_digit(n2)) {
235                            (true, true) => [next, n1, n2],
236                            (true, _) => {
237                                r.jump(r.offset() - 1);
238                                [b'0', next, n1]
239                            }
240                            _ => {
241                                r.jump(r.offset() - 2);
242                                [b'0', b'0', next]
243                            }
244                        },
245                        (Some(n1), None) => {
246                            if is_octal_digit(n1) {
247                                [b'0', next, n1]
248                            } else {
249                                r.jump(r.offset() - 1);
250                                [b'0', b'0', next]
251                            }
252                        }
253                        _ => [b'0', b'0', next],
254                    };
255
256                    let str = core::str::from_utf8(&bytes).unwrap();
257
258                    if let Ok(num) = u8::from_str_radix(str, 8) {
259                        result.push(num);
260                    }
261                } else {
262                    match next {
263                        b'n' => result.push(0xA),
264                        b'r' => result.push(0xD),
265                        b't' => result.push(0x9),
266                        b'b' => result.push(0x8),
267                        b'f' => result.push(0xC),
268                        b'(' => result.push(b'('),
269                        b')' => result.push(b')'),
270                        b'\\' => result.push(b'\\'),
271                        _ => result.push(next),
272                    }
273                }
274            }
275            b'(' | b')' => result.push(byte),
276            other => result.push(other),
277        }
278    }
279
280    Some(StringInner::Owned(result))
281}
282
283fn is_octal_digit(byte: u8) -> bool {
284    matches!(byte, b'0'..=b'7')
285}
286
287#[cfg(test)]
288mod tests {
289    use crate::object::String;
290    use crate::reader::Reader;
291    use crate::reader::ReaderExt;
292
293    #[test]
294    fn display() {
295        let normal = Reader::new(b"(Hello)")
296            .read_without_context::<String<'_>>()
297            .unwrap();
298        let binary = Reader::new(b"<00FF>")
299            .read_without_context::<String<'_>>()
300            .unwrap();
301
302        assert_eq!(format!("{normal}"), "\"Hello\"");
303        assert_eq!(format!("{binary}"), "[0, 255]");
304    }
305
306    #[test]
307    fn hex_string_empty() {
308        assert_eq!(
309            Reader::new(b"<>")
310                .read_without_context::<String<'_>>()
311                .unwrap()
312                .as_bytes(),
313            b""
314        );
315    }
316
317    #[test]
318    fn hex_string_1() {
319        assert_eq!(
320            Reader::new(b"<00010203>")
321                .read_without_context::<String<'_>>()
322                .unwrap()
323                .as_bytes(),
324            &[0x00, 0x01, 0x02, 0x03]
325        );
326    }
327
328    #[test]
329    fn hex_string_2() {
330        assert_eq!(
331            Reader::new(b"<000102034>")
332                .read_without_context::<String<'_>>()
333                .unwrap()
334                .as_bytes(),
335            &[0x00, 0x01, 0x02, 0x03, 0x40]
336        );
337    }
338
339    #[test]
340    fn hex_string_trailing_1() {
341        assert_eq!(
342            Reader::new(b"<000102034>dfgfg4")
343                .read_without_context::<String<'_>>()
344                .unwrap()
345                .as_bytes(),
346            &[0x00, 0x01, 0x02, 0x03, 0x40]
347        );
348    }
349
350    #[test]
351    fn hex_string_trailing_2() {
352        assert_eq!(
353            Reader::new(b"<1  3 4>dfgfg4")
354                .read_without_context::<String<'_>>()
355                .unwrap()
356                .as_bytes(),
357            &[0x13, 0x40]
358        );
359    }
360
361    #[test]
362    fn hex_string_trailing_3() {
363        assert_eq!(
364            Reader::new(b"<1>dfgfg4")
365                .read_without_context::<String<'_>>()
366                .unwrap()
367                .as_bytes(),
368            &[0x10]
369        );
370    }
371
372    #[test]
373    fn hex_string_invalid_1() {
374        assert!(
375            Reader::new(b"<")
376                .read_without_context::<String<'_>>()
377                .is_none()
378        );
379    }
380
381    #[test]
382    fn hex_string_invalid_2() {
383        assert!(
384            Reader::new(b"34AD")
385                .read_without_context::<String<'_>>()
386                .is_none()
387        );
388    }
389
390    #[test]
391    fn literal_string_empty() {
392        assert_eq!(
393            Reader::new(b"()")
394                .read_without_context::<String<'_>>()
395                .unwrap()
396                .as_bytes(),
397            b""
398        );
399    }
400
401    #[test]
402    fn literal_string_1() {
403        assert_eq!(
404            Reader::new(b"(Hi there.)")
405                .read_without_context::<String<'_>>()
406                .unwrap()
407                .as_bytes(),
408            b"Hi there."
409        );
410    }
411
412    #[test]
413    fn literal_string_2() {
414        assert!(
415            Reader::new(b"(Hi \\777)")
416                .read_without_context::<String<'_>>()
417                .is_some()
418        );
419    }
420
421    #[test]
422    fn literal_string_3() {
423        assert_eq!(
424            Reader::new(b"(Hi ) there.)")
425                .read_without_context::<String<'_>>()
426                .unwrap()
427                .as_bytes(),
428            b"Hi "
429        );
430    }
431
432    #[test]
433    fn literal_string_4() {
434        assert_eq!(
435            Reader::new(b"(Hi (()) there)")
436                .read_without_context::<String<'_>>()
437                .unwrap()
438                .as_bytes(),
439            b"Hi (()) there"
440        );
441    }
442
443    #[test]
444    fn literal_string_5() {
445        assert_eq!(
446            Reader::new(b"(Hi \\()")
447                .read_without_context::<String<'_>>()
448                .unwrap()
449                .as_bytes(),
450            b"Hi ("
451        );
452    }
453
454    #[test]
455    fn literal_string_6() {
456        assert_eq!(
457            Reader::new(b"(Hi \\\nthere)")
458                .read_without_context::<String<'_>>()
459                .unwrap()
460                .as_bytes(),
461            b"Hi there"
462        );
463    }
464
465    #[test]
466    fn literal_string_7() {
467        assert_eq!(
468            Reader::new(b"(Hi \\05354)")
469                .read_without_context::<String<'_>>()
470                .unwrap()
471                .as_bytes(),
472            b"Hi +54"
473        );
474    }
475
476    #[test]
477    fn literal_string_8() {
478        assert_eq!(
479            Reader::new(b"(\\3)")
480                .read_without_context::<String<'_>>()
481                .unwrap()
482                .as_bytes(),
483            b"\x03"
484        );
485    }
486
487    #[test]
488    fn literal_string_9() {
489        assert_eq!(
490            Reader::new(b"(\\36)")
491                .read_without_context::<String<'_>>()
492                .unwrap()
493                .as_bytes(),
494            b"\x1e"
495        );
496    }
497
498    #[test]
499    fn literal_string_10() {
500        assert_eq!(
501            Reader::new(b"(\\36ab)")
502                .read_without_context::<String<'_>>()
503                .unwrap()
504                .as_bytes(),
505            b"\x1eab"
506        );
507    }
508
509    #[test]
510    fn literal_string_11() {
511        assert_eq!(
512            Reader::new(b"(\\00Y)")
513                .read_without_context::<String<'_>>()
514                .unwrap()
515                .as_bytes(),
516            b"\0Y"
517        );
518    }
519
520    #[test]
521    fn literal_string_12() {
522        assert_eq!(
523            Reader::new(b"(\\0Y)")
524                .read_without_context::<String<'_>>()
525                .unwrap()
526                .as_bytes(),
527            b"\0Y"
528        );
529    }
530
531    #[test]
532    fn literal_string_eol() {
533        let read = |data: &[u8]| {
534            Reader::new(data)
535                .read_without_context::<String<'_>>()
536                .unwrap()
537                .as_bytes()
538                .to_vec()
539        };
540
541        assert_eq!(read(b"(a\n\nb)"), b"a\n\nb");
542        assert_eq!(read(b"(a\r\n\r\nb)"), b"a\n\nb");
543        assert_eq!(read(b"(a\r\rb)"), b"a\n\nb");
544        assert_eq!(read(b"(a\\\n\nb)"), b"a\nb");
545        assert_eq!(read(b"(a\\\r\n\r\nb)"), b"a\nb");
546    }
547
548    #[test]
549    fn literal_string_trailing() {
550        assert_eq!(
551            Reader::new(b"(Hi there.)abcde")
552                .read_without_context::<String<'_>>()
553                .unwrap()
554                .as_bytes(),
555            b"Hi there."
556        );
557    }
558
559    #[test]
560    fn literal_string_invalid() {
561        assert_eq!(
562            Reader::new(b"(Hi \\778)")
563                .read_without_context::<String<'_>>()
564                .unwrap()
565                .as_bytes(),
566            b"Hi \x3F8"
567        );
568    }
569
570    #[test]
571    fn string_1() {
572        assert_eq!(
573            Reader::new(b"(Hi there.)")
574                .read_without_context::<String<'_>>()
575                .unwrap()
576                .as_bytes(),
577            b"Hi there."
578        );
579    }
580
581    #[test]
582    fn string_2() {
583        assert_eq!(
584            Reader::new(b"<00010203>")
585                .read_without_context::<String<'_>>()
586                .unwrap()
587                .as_bytes(),
588            &[0x00, 0x01, 0x02, 0x03]
589        );
590    }
591}