Skip to main content

sequoia_openpgp/
fmt.rs

1//! Utilities for formatting, printing, and user communication.
2
3use crate::Error;
4use crate::Result;
5
6/// Converts buffers to and from hexadecimal numbers.
7pub mod hex {
8    use std::io;
9
10    use crate::Result;
11
12    /// Encodes the given buffer as hexadecimal number.
13    pub fn encode<B: AsRef<[u8]>>(buffer: B) -> String {
14        super::to_hex(buffer.as_ref(), false)
15    }
16
17    /// Encodes the given buffer as hexadecimal number with spaces.
18    pub fn encode_pretty<B: AsRef<[u8]>>(buffer: B) -> String {
19        super::to_hex(buffer.as_ref(), true)
20    }
21
22    /// Decodes the given hexadecimal number.
23    pub fn decode<H: AsRef<str>>(hex: H) -> Result<Vec<u8>> {
24        super::from_hex(hex.as_ref(), false)
25    }
26
27    /// Decodes the given hexadecimal number, ignoring whitespace.
28    pub fn decode_pretty<H: AsRef<str>>(hex: H) -> Result<Vec<u8>> {
29        super::from_hex(hex.as_ref(), true)
30    }
31
32    /// Dumps binary data, like `hd(1)`.
33    pub fn dump<W: io::Write, B: AsRef<[u8]>>(sink: W, data: B)
34                                              -> io::Result<()> {
35        Dumper::new(sink, "").write_ascii(data)
36    }
37
38    /// Writes annotated hex dumps, like hd(1).
39    ///
40    /// # Examples
41    ///
42    /// ```rust
43    /// # fn main() -> sequoia_openpgp::Result<()> {
44    /// use sequoia_openpgp::fmt::hex;
45    ///
46    /// let mut dumper = hex::Dumper::new(Vec::new(), "");
47    /// dumper.write(&[0x89, 0x01, 0x33], "frame")?;
48    /// dumper.write(&[0x04], "version")?;
49    /// dumper.write(&[0x00], "type")?;
50    ///
51    /// let buf = dumper.into_inner();
52    /// assert_eq!(
53    ///     ::std::str::from_utf8(&buf[..])?,
54    ///     "00000000  89 01 33                                           frame\n\
55    ///      00000003           04                                        version\n\
56    ///      00000004              00                                     type\n\
57    ///      ");
58    /// # Ok(()) }
59    /// ```
60    pub struct Dumper<W: io::Write> {
61        inner: W,
62        indent: String,
63        offset: usize,
64    }
65
66    assert_send_and_sync!(Dumper<W> where W: io::Write);
67
68    impl<W: io::Write> Dumper<W> {
69        /// Creates a new dumper.
70        ///
71        /// The dump is written to `inner`.  Every line is indented with
72        /// `indent`.
73        pub fn new<I: AsRef<str>>(inner: W, indent: I) -> Self {
74            Self::with_offset(inner, indent, 0)
75        }
76
77        /// Creates a new dumper starting at the given offset.
78        ///
79        /// The dump is written to `inner`.  Every line is indented with
80        /// `indent`.
81        pub fn with_offset<I>(inner: W, indent: I, offset: usize) -> Self
82        where
83            I: AsRef<str>,
84        {
85            Dumper {
86                inner,
87                indent: indent.as_ref().into(),
88                offset,
89            }
90        }
91
92        /// Returns the inner writer.
93        pub fn into_inner(self) -> W {
94            self.inner
95        }
96
97        /// Writes a chunk of data.
98        ///
99        /// The `msg` is printed at the end of the first line.
100        pub fn write<B, M>(&mut self, buf: B, msg: M) -> io::Result<()>
101            where B: AsRef<[u8]>,
102                  M: AsRef<str>,
103        {
104            let mut first = true;
105            self.write_labeled(buf.as_ref(), move |_, _| {
106                if first {
107                    first = false;
108                    Some(msg.as_ref().into())
109                } else {
110                    None
111                }
112            })
113        }
114
115        /// Writes a chunk of data with ASCII-representation.
116        ///
117        /// This produces output similar to `hd(1)`.
118        pub fn write_ascii<B>(&mut self, buf: B) -> io::Result<()>
119            where B: AsRef<[u8]>,
120        {
121            self.write_labeled(buf, |offset, data| {
122                let mut l = String::new();
123                for _ in 0..offset {
124                    l.push(' ');
125                }
126                for &c in data {
127                    l.push(if c < 32 {
128                        '.'
129                    } else if c < 128 {
130                        c.into()
131                    } else {
132                        '.'
133                    })
134                }
135                Some(l)
136            })
137        }
138
139        /// Writes a chunk of data.
140        ///
141        /// For each line, the given function is called to compute a
142        /// label that printed at the end of the first line.  The
143        /// functions first argument is the offset in the current line
144        /// (0..16), the second the slice of the displayed data.
145        pub fn write_labeled<B, L>(&mut self, buf: B, mut labeler: L)
146                                -> io::Result<()>
147            where B: AsRef<[u8]>,
148                  L: FnMut(usize, &[u8]) -> Option<String>,
149        {
150            let buf = buf.as_ref();
151            let mut first_label_offset = self.offset % 16;
152
153            write!(self.inner, "{}{:08x} ", self.indent, self.offset)?;
154            for i in 0 .. self.offset % 16 {
155                if i != 7 {
156                    write!(self.inner, "   ")?;
157                } else {
158                    write!(self.inner, "    ")?;
159                }
160            }
161
162            let mut offset_printed = true;
163            let mut data_start = 0;
164            for (i, c) in buf.iter().enumerate() {
165                if ! offset_printed {
166                    write!(self.inner,
167                           "\n{}{:08x} ", self.indent, self.offset)?;
168                    offset_printed = true;
169                }
170
171                write!(self.inner, " {:02x}", c)?;
172                self.offset += 1;
173                match self.offset % 16 {
174                    0 => {
175                        if let Some(msg) = Some(&buf[data_start..i + 1])
176                            .filter(|b| ! b.is_empty())
177                            .and_then(|b| labeler(first_label_offset, b))
178                        {
179                            write!(self.inner, "   {}", msg)?;
180                            // Only the first label is offset.
181                            first_label_offset = 0;
182                        }
183                        data_start = i + 1;
184                        offset_printed = false;
185                    },
186                    8 => write!(self.inner, " ")?,
187                    _ => (),
188                }
189            }
190
191            if let Some(msg) = Some(&buf[data_start..])
192                .filter(|b| ! b.is_empty())
193                .and_then(|b| labeler(first_label_offset, b))
194            {
195                for i in self.offset % 16 .. 16 {
196                    if i != 7 {
197                        write!(self.inner, "   ")?;
198                    } else {
199                        write!(self.inner, "    ")?;
200                    }
201                }
202
203                write!(self.inner, "   {}", msg)?;
204            }
205            writeln!(self.inner)?;
206            Ok(())
207        }
208    }
209}
210
211/// A helpful debugging function.
212#[allow(dead_code)]
213pub(crate) fn to_hex(s: &[u8], pretty: bool) -> String {
214    use std::fmt::Write;
215
216    let mut result = String::new();
217    for (i, b) in s.iter().enumerate() {
218        // Add spaces every four digits to make the output more
219        // readable.
220        if pretty && i > 0 && i % 2 == 0 {
221            write!(&mut result, " ").unwrap();
222        }
223        write!(&mut result, "{:02X}", b).unwrap();
224    }
225    result
226}
227
228/// A helpful function for converting a hexadecimal string to binary.
229/// This function skips whitespace if `pretty` is set.
230pub(crate) fn from_hex(hex: &str, pretty: bool) -> Result<Vec<u8>> {
231    const BAD: u8 = 255u8;
232    const X: u8 = b'x';
233
234    let mut nibbles = hex.chars().filter_map(|x| {
235        match x {
236            '0' => Some(0u8),
237            '1' => Some(1u8),
238            '2' => Some(2u8),
239            '3' => Some(3u8),
240            '4' => Some(4u8),
241            '5' => Some(5u8),
242            '6' => Some(6u8),
243            '7' => Some(7u8),
244            '8' => Some(8u8),
245            '9' => Some(9u8),
246            'a' | 'A' => Some(10u8),
247            'b' | 'B' => Some(11u8),
248            'c' | 'C' => Some(12u8),
249            'd' | 'D' => Some(13u8),
250            'e' | 'E' => Some(14u8),
251            'f' | 'F' => Some(15u8),
252            'x' | 'X' if pretty => Some(X),
253            _ if pretty && x.is_whitespace() => None,
254            _ => Some(BAD),
255        }
256    }).collect::<Vec<u8>>();
257
258    if pretty && nibbles.len() >= 2 && nibbles[0] == 0 && nibbles[1] == X {
259        // Drop '0x' prefix.
260        nibbles.remove(0);
261        nibbles.remove(0);
262    }
263
264    if nibbles.iter().any(|&b| b == BAD || b == X) {
265        // Not a hex character.
266        return
267            Err(Error::InvalidArgument("Invalid characters".into()).into());
268    }
269
270    // We need an even number of nibbles.
271    if nibbles.len() % 2 != 0 {
272        nibbles.insert(0, 0);
273    }
274
275    let bytes = nibbles.chunks(2).map(|nibbles| {
276        (nibbles[0] << 4) | nibbles[1]
277    }).collect::<Vec<u8>>();
278
279    Ok(bytes)
280}
281
282/// Formats the given time using ISO 8601.
283///
284/// This is a no-dependency, best-effort mechanism.  If the given time
285/// is not representable using unsigned UNIX time, we return the debug
286/// formatting.
287pub(crate) fn time(t: &std::time::SystemTime) -> String {
288    // Actually use a chrono dependency for WASM since there's no strftime
289    // (except for WASI).
290    #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
291        chrono::DateTime::<chrono::Utc>::from(t.clone())
292            .format("%Y-%m-%dT%H:%M:%SZ")
293            .to_string()
294    }
295    #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] {
296        extern "C" {
297            fn strftime(
298                s: *mut libc::c_char,
299                max: libc::size_t,
300                format: *const libc::c_char,
301                tm: *const libc::tm,
302            ) -> usize;
303        }
304
305        let t = match t.duration_since(std::time::UNIX_EPOCH) {
306            Ok(t) => t.as_secs() as libc::time_t,
307            Err(_) => return format!("{:?}", t),
308        };
309        let fmt = b"%Y-%m-%dT%H:%M:%SZ\x00";
310        assert_eq!(b"2020-03-26T10:08:10Z\x00".len(), 21);
311        let mut s = [0u8; 21];
312
313        unsafe {
314            let mut tm: libc::tm = std::mem::zeroed();
315
316            #[cfg(unix)]
317            libc::gmtime_r(&t, &mut tm);
318            #[cfg(windows)]
319            libc::gmtime_s(&mut tm, &t);
320
321            strftime(s.as_mut_ptr() as *mut libc::c_char,
322                     s.len(),
323                     fmt.as_ptr() as *const libc::c_char,
324                     &tm);
325        }
326
327        std::ffi::CStr::from_bytes_with_nul(&s)
328            .expect("strftime nul terminates string")
329            .to_string_lossy().into()
330    }
331}
332
333#[cfg(test)]
334mod test {
335    use crate::fmt::hex;
336
337    #[test]
338    fn from_hex() {
339        use super::from_hex as fh;
340        assert_eq!(fh("", false).ok(), Some(vec![]));
341        assert_eq!(fh("0", false).ok(), Some(vec![0x00]));
342        assert_eq!(fh("00", false).ok(), Some(vec![0x00]));
343        assert_eq!(fh("09", false).ok(), Some(vec![0x09]));
344        assert_eq!(fh("0f", false).ok(), Some(vec![0x0f]));
345        assert_eq!(fh("99", false).ok(), Some(vec![0x99]));
346        assert_eq!(fh("ff", false).ok(), Some(vec![0xff]));
347        assert_eq!(fh("000", false).ok(), Some(vec![0x00, 0x00]));
348        assert_eq!(fh("0000", false).ok(), Some(vec![0x00, 0x00]));
349        assert_eq!(fh("0009", false).ok(), Some(vec![0x00, 0x09]));
350        assert_eq!(fh("000f", false).ok(), Some(vec![0x00, 0x0f]));
351        assert_eq!(fh("0099", false).ok(), Some(vec![0x00, 0x99]));
352        assert_eq!(fh("00ff", false).ok(), Some(vec![0x00, 0xff]));
353        assert_eq!(fh("\t\n\x0c\r ", false).ok(), None);
354        assert_eq!(fh("a", false).ok(), Some(vec![0x0a]));
355        assert_eq!(fh("0x", false).ok(), None);
356        assert_eq!(fh("0x0", false).ok(), None);
357        assert_eq!(fh("0x00", false).ok(), None);
358    }
359
360    #[test]
361    fn from_pretty_hex() {
362        use super::from_hex as fh;
363        assert_eq!(fh(" ", true).ok(), Some(vec![]));
364        assert_eq!(fh(" 0", true).ok(), Some(vec![0x00]));
365        assert_eq!(fh(" 00", true).ok(), Some(vec![0x00]));
366        assert_eq!(fh(" 09", true).ok(), Some(vec![0x09]));
367        assert_eq!(fh(" 0f", true).ok(), Some(vec![0x0f]));
368        assert_eq!(fh(" 99", true).ok(), Some(vec![0x99]));
369        assert_eq!(fh(" ff", true).ok(), Some(vec![0xff]));
370        assert_eq!(fh(" 00 0", true).ok(), Some(vec![0x00, 0x00]));
371        assert_eq!(fh(" 00 00", true).ok(), Some(vec![0x00, 0x00]));
372        assert_eq!(fh(" 00 09", true).ok(), Some(vec![0x00, 0x09]));
373        assert_eq!(fh(" 00 0f", true).ok(), Some(vec![0x00, 0x0f]));
374        assert_eq!(fh(" 00 99", true).ok(), Some(vec![0x00, 0x99]));
375        assert_eq!(fh(" 00 ff", true).ok(), Some(vec![0x00, 0xff]));
376        assert_eq!(fh("\t\n\x0c\r ", true).ok(), Some(vec![]));
377        // Fancy Unicode spaces are ok too:
378        assert_eq!(fh("     23", true).ok(), Some(vec![0x23]));
379        assert_eq!(fh("a", true).ok(), Some(vec![0x0a]));
380        assert_eq!(fh(" 0x", true).ok(), Some(vec![]));
381        assert_eq!(fh(" 0x0", true).ok(), Some(vec![0x00]));
382        assert_eq!(fh(" 0x00", true).ok(), Some(vec![0x00]));
383    }
384
385    quickcheck! {
386        fn hex_roundtrip(data: Vec<u8>) -> bool {
387            let hex = super::to_hex(&data, false);
388            data == super::from_hex(&hex, false).unwrap()
389        }
390    }
391
392    quickcheck! {
393        fn pretty_hex_roundtrip(data: Vec<u8>) -> bool {
394            let hex = super::to_hex(&data, true);
395            data == super::from_hex(&hex, true).unwrap()
396        }
397    }
398
399    #[test]
400    fn hex_decode_accepts_mixed_case() {
401        assert_eq!(hex::decode("aBcD").unwrap(), vec![0xAB, 0xCD]);
402    }
403
404    #[test]
405    fn hex_decode_odd_nibbles_are_left_padded() {
406        assert_eq!(hex::decode("ABC").unwrap(), vec![0x0A, 0xBC]);
407    }
408
409    #[test]
410    fn hex_decode_rejects_invalid_characters() {
411        assert!(hex::decode("GG").is_err());
412        assert!(hex::decode_pretty("0xGG").is_err());
413    }
414
415    #[test]
416    fn hex_dumper() {
417        use super::hex::Dumper;
418
419        let mut dumper = Dumper::new(Vec::new(), "III");
420        dumper.write(&[0x89, 0x01, 0x33], "frame").unwrap();
421        let buf = dumper.into_inner();
422        assert_eq!(
423            ::std::str::from_utf8(&buf[..]).unwrap(),
424            "III00000000  \
425             89 01 33                                           \
426             frame\n");
427
428        let mut dumper = Dumper::new(Vec::new(), "III");
429        dumper.write(&[0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01], "frame")
430            .unwrap();
431        let buf = dumper.into_inner();
432        assert_eq!(
433            ::std::str::from_utf8(&buf[..]).unwrap(),
434            "III00000000  \
435             89 01 33 89 01 33 89 01                            \
436             frame\n");
437
438        let mut dumper = Dumper::new(Vec::new(), "III");
439        dumper.write(&[0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01,
440                       0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01], "frame")
441            .unwrap();
442        let buf = dumper.into_inner();
443        assert_eq!(
444            ::std::str::from_utf8(&buf[..]).unwrap(),
445            "III00000000  \
446             89 01 33 89 01 33 89 01  89 01 33 89 01 33 89 01   \
447             frame\n");
448
449        let mut dumper = Dumper::new(Vec::new(), "III");
450        dumper.write(&[0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01,
451                       0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01,
452                       0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01,
453                       0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01], "frame")
454            .unwrap();
455        let buf = dumper.into_inner();
456        assert_eq!(
457            ::std::str::from_utf8(&buf[..]).unwrap(),
458            "III00000000  \
459             89 01 33 89 01 33 89 01  89 01 33 89 01 33 89 01   \
460             frame\n\
461             III00000010  \
462             89 01 33 89 01 33 89 01  89 01 33 89 01 33 89 01\n");
463
464        let mut dumper = Dumper::new(Vec::new(), "");
465        dumper.write(&[0x89, 0x01, 0x33], "frame").unwrap();
466        dumper.write(&[0x04], "version").unwrap();
467        dumper.write(&[0x00], "type").unwrap();
468        let buf = dumper.into_inner();
469        assert_eq!(
470            ::std::str::from_utf8(&buf[..]).unwrap(),
471            "00000000  89 01 33                                           \
472             frame\n\
473             00000003           04                                        \
474             version\n\
475             00000004              00                                     \
476             type\n\
477             ");
478    }
479
480    quickcheck! {
481        fn hex_encode_decode_roundtrip_qc(data: Vec<u8>) -> bool {
482            hex::decode(hex::encode(&data)).unwrap() == data
483        }
484    }
485
486    #[test]
487    fn hex_encode_formats_uppercase() {
488        assert_eq!(hex::encode(&[0xAB, 0xCD, 0xEF]), "ABCDEF");
489    }
490
491    quickcheck! {
492        fn hex_pretty_accepts_0x_prefix_qc(data: Vec<u8>) -> bool {
493            let s = format!("0x{}", hex::encode(&data));
494            hex::decode_pretty(&s).unwrap() == data
495        }
496    }
497
498    #[test]
499    fn hex_pretty_accepts_bare_0x_as_empty() {
500        assert_eq!(hex::decode_pretty("0x").unwrap(), Vec::<u8>::new());
501    }
502
503    quickcheck! {
504        fn hex_pretty_encode_decode_roundtrip_qc(data: Vec<u8>) -> bool {
505            hex::decode_pretty(hex::encode_pretty(&data)).unwrap() == data
506        }
507    }
508
509    #[test]
510    fn hex_pretty_accepts_prefix_and_spaces() {
511        assert_eq!(hex::decode_pretty("  0xAB CD \n").unwrap(),
512                   vec![0xAB, 0xCD]);
513    }
514
515    #[test]
516    fn hex_pretty_rejects_input_with_several_0x() {
517        assert!(hex::decode_pretty("0x0x0x").is_err());
518    }
519    #[test]
520    fn hex_pretty_rejects_stray_x() {
521        assert!(hex::decode_pretty("ABxC").is_err());
522    }
523
524    #[test]
525    fn hex_pretty_rejects_x_when_not_part_of_leading_prefix() {
526        assert!(hex::decode_pretty("1x").is_err());
527    }
528
529    #[test]
530    fn hex_pretty_whitespace_invariant() {
531        let data = vec![0x01, 0x23, 0x45, 0x67, 0x89];
532        let base = hex::encode_pretty(&data);
533        let noisy = format!(" \n{}\t\n", base.replace(" ", " \t "));
534        assert_eq!(hex::decode_pretty(&noisy).unwrap(), data);
535    }
536
537    #[test]
538    fn time() {
539        use super::time;
540        use crate::types::Timestamp;
541        let t = |epoch| -> std::time::SystemTime {
542            Timestamp::from(epoch).into()
543        };
544        assert_eq!(&time(&t(1585217290)), "2020-03-26T10:08:10Z");
545    }
546}