1 //! Utilities for formatting, printing, and user communication.
2 
3 use crate::Error;
4 use crate::Result;
5 
6 /// Converts buffers to and from hexadecimal numbers.
7 pub mod hex {
8     use std::io;
9 
10     use crate::Result;
11 
12     /// Encodes the given buffer as hexadecimal number.
encode<B: AsRef<[u8]>>(buffer: B) -> String13     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.
encode_pretty<B: AsRef<[u8]>>(buffer: B) -> String18     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.
decode<H: AsRef<str>>(hex: H) -> Result<Vec<u8>>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.
decode_pretty<H: AsRef<str>>(hex: H) -> Result<Vec<u8>>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)`.
dump<W: io::Write, B: AsRef<[u8]>>(sink: W, data: B) -> io::Result<()>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     /// # Example
41     ///
42     /// ```rust
43     ///  use sequoia_openpgp::fmt::hex;
44     ///
45     /// let mut dumper = hex::Dumper::new(Vec::new(), "");
46     /// dumper.write(&[0x89, 0x01, 0x33], "frame").unwrap();
47     /// dumper.write(&[0x04], "version").unwrap();
48     /// dumper.write(&[0x00], "type").unwrap();
49     ///
50     /// let buf = dumper.into_inner();
51     /// assert_eq!(
52     ///     ::std::str::from_utf8(&buf[..]).unwrap(),
53     ///     "00000000  89 01 33                                           frame\n\
54     ///      00000003           04                                        version\n\
55     ///      00000004              00                                     type\n\
56     ///      ");
57     /// ```
58     pub struct Dumper<W: io::Write> {
59         inner: W,
60         indent: String,
61         offset: usize,
62     }
63 
64     impl<W: io::Write> Dumper<W> {
65         /// Creates a new dumper.
66         ///
67         /// The dump is written to `inner`.  Every line is indented with
68         /// `indent`.
new<I: AsRef<str>>(inner: W, indent: I) -> Self69         pub fn new<I: AsRef<str>>(inner: W, indent: I) -> Self {
70             Dumper {
71                 inner,
72                 indent: indent.as_ref().into(),
73                 offset: 0,
74             }
75         }
76 
77         /// Returns the inner writer.
into_inner(self) -> W78         pub fn into_inner(self) -> W {
79             self.inner
80         }
81 
82         /// Writes a chunk of data.
83         ///
84         /// The `msg` is printed at the end of the first line.
write<B, M>(&mut self, buf: B, msg: M) -> io::Result<()> where B: AsRef<[u8]>, M: AsRef<str>,85         pub fn write<B, M>(&mut self, buf: B, msg: M) -> io::Result<()>
86             where B: AsRef<[u8]>,
87                   M: AsRef<str>,
88         {
89             let mut first = true;
90             self.write_labeled(buf.as_ref(), move |_, _| {
91                 if first {
92                     first = false;
93                     Some(msg.as_ref().into())
94                 } else {
95                     None
96                 }
97             })
98         }
99 
100         /// Writes a chunk of data with ASCII-representation.
101         ///
102         /// This produces output similar to `hd(1)`.
write_ascii<B>(&mut self, buf: B) -> io::Result<()> where B: AsRef<[u8]>,103         pub fn write_ascii<B>(&mut self, buf: B) -> io::Result<()>
104             where B: AsRef<[u8]>,
105         {
106             self.write_labeled(buf, |offset, data| {
107                 let mut l = String::new();
108                 for _ in 0..offset {
109                     l.push(' ');
110                 }
111                 for &c in data {
112                     l.push(if c < 32 {
113                         '.'
114                     } else if c < 128 {
115                         c.into()
116                     } else {
117                         '.'
118                     })
119                 }
120                 Some(l)
121             })
122         }
123 
124         /// Writes a chunk of data.
125         ///
126         /// For each line, the given function is called to compute a
127         /// label that printed at the end of the first line.  The
128         /// functions first argument is the offset in the current line
129         /// (0..16), the second the slice of the displayed data.
write_labeled<B, L>(&mut self, buf: B, mut labeler: L) -> io::Result<()> where B: AsRef<[u8]>, L: FnMut(usize, &[u8]) -> Option<String>,130         pub fn write_labeled<B, L>(&mut self, buf: B, mut labeler: L)
131                                 -> io::Result<()>
132             where B: AsRef<[u8]>,
133                   L: FnMut(usize, &[u8]) -> Option<String>,
134         {
135             let buf = buf.as_ref();
136             let mut first_label_offset = self.offset % 16;
137 
138             write!(self.inner, "{}{:08x} ", self.indent, self.offset)?;
139             for i in 0 .. self.offset % 16 {
140                 if i != 7 {
141                     write!(self.inner, "   ")?;
142                 } else {
143                     write!(self.inner, "    ")?;
144                 }
145             }
146 
147             let mut offset_printed = true;
148             let mut data_start = 0;
149             for (i, c) in buf.iter().enumerate() {
150                 if ! offset_printed {
151                     write!(self.inner,
152                            "\n{}{:08x} ", self.indent, self.offset)?;
153                     offset_printed = true;
154                 }
155 
156                 write!(self.inner, " {:02x}", c)?;
157                 self.offset += 1;
158                 match self.offset % 16 {
159                     0 => {
160                         if let Some(msg) = labeler(
161                             first_label_offset, &buf[data_start..i + 1])
162                         {
163                             write!(self.inner, "   {}", msg)?;
164                             // Only the first label is offset.
165                             first_label_offset = 0;
166                         }
167                         data_start = i + 1;
168                         offset_printed = false;
169                     },
170                     8 => write!(self.inner, " ")?,
171                     _ => (),
172                 }
173             }
174 
175             if let Some(msg) = labeler(
176                 first_label_offset, &buf[data_start..])
177             {
178                 for i in self.offset % 16 .. 16 {
179                     if i != 7 {
180                         write!(self.inner, "   ")?;
181                     } else {
182                         write!(self.inner, "    ")?;
183                     }
184                 }
185 
186                 write!(self.inner, "   {}", msg)?;
187             }
188             writeln!(self.inner)?;
189             Ok(())
190         }
191     }
192 }
193 
194 /// A helpful debugging function.
195 #[allow(dead_code)]
to_hex(s: &[u8], pretty: bool) -> String196 pub(crate) fn to_hex(s: &[u8], pretty: bool) -> String {
197     use std::fmt::Write;
198 
199     let mut result = String::new();
200     for (i, b) in s.iter().enumerate() {
201         // Add spaces every four digits to make the output more
202         // readable.
203         if pretty && i > 0 && i % 2 == 0 {
204             write!(&mut result, " ").unwrap();
205         }
206         write!(&mut result, "{:02X}", b).unwrap();
207     }
208     result
209 }
210 
211 /// A helpful function for converting a hexadecimal string to binary.
212 /// This function skips whitespace if `pretty` is set.
from_hex(hex: &str, pretty: bool) -> Result<Vec<u8>>213 pub(crate) fn from_hex(hex: &str, pretty: bool) -> Result<Vec<u8>> {
214     const BAD: u8 = 255u8;
215     const X: u8 = 'x' as u8;
216 
217     let mut nibbles = hex.chars().filter_map(|x| {
218         match x {
219             '0' => Some(0u8),
220             '1' => Some(1u8),
221             '2' => Some(2u8),
222             '3' => Some(3u8),
223             '4' => Some(4u8),
224             '5' => Some(5u8),
225             '6' => Some(6u8),
226             '7' => Some(7u8),
227             '8' => Some(8u8),
228             '9' => Some(9u8),
229             'a' | 'A' => Some(10u8),
230             'b' | 'B' => Some(11u8),
231             'c' | 'C' => Some(12u8),
232             'd' | 'D' => Some(13u8),
233             'e' | 'E' => Some(14u8),
234             'f' | 'F' => Some(15u8),
235             'x' | 'X' if pretty => Some(X),
236             _ if pretty && x.is_whitespace() => None,
237             _ => Some(BAD),
238         }
239     }).collect::<Vec<u8>>();
240 
241     if pretty && nibbles.len() >= 2 && nibbles[0] == 0 && nibbles[1] == X {
242         // Drop '0x' prefix.
243         nibbles.remove(0);
244         nibbles.remove(0);
245     }
246 
247     if nibbles.iter().any(|&b| b == BAD || b == X) {
248         // Not a hex character.
249         return
250             Err(Error::InvalidArgument("Invalid characters".into()).into());
251     }
252 
253     // We need an even number of nibbles.
254     if nibbles.len() % 2 != 0 {
255         return
256             Err(Error::InvalidArgument("Odd number of nibbles".into()).into());
257     }
258 
259     let bytes = nibbles.chunks(2).map(|nibbles| {
260         (nibbles[0] << 4) | nibbles[1]
261     }).collect::<Vec<u8>>();
262 
263     Ok(bytes)
264 }
265 
266 /// Formats the given time using ISO 8601.
267 ///
268 /// This is a no-dependency, best-effort mechanism.  If the given time
269 /// is not representable using unsigned UNIX time, we return the debug
270 /// formatting.
time(t: &std::time::SystemTime) -> String271 pub(crate) fn time(t: &std::time::SystemTime) -> String {
272     extern "C" {
273         fn strftime(
274             s: *mut libc::c_char,
275             max: libc::size_t,
276             format: *const libc::c_char,
277             tm: *const libc::tm,
278         ) -> usize;
279     }
280 
281     let t = match t.duration_since(std::time::UNIX_EPOCH) {
282         Ok(t) => t.as_secs() as libc::time_t,
283         Err(_) => return format!("{:?}", t),
284     };
285     let fmt = b"%Y-%m-%dT%H:%M:%SZ\x00";
286     assert_eq!(b"2020-03-26T10:08:10Z\x00".len(), 21);
287     let mut s = [0u8; 21];
288 
289     unsafe {
290         let mut tm: libc::tm = std::mem::zeroed();
291 
292         #[cfg(unix)]
293         libc::gmtime_r(&t, &mut tm);
294         #[cfg(windows)]
295         libc::gmtime_s(&mut tm, &t);
296 
297         strftime(s.as_mut_ptr() as *mut libc::c_char,
298                  s.len(),
299                  fmt.as_ptr() as *const libc::c_char,
300                  &tm);
301     }
302 
303     std::ffi::CStr::from_bytes_with_nul(&s)
304         .expect("strftime nul terminates string")
305         .to_string_lossy().into()
306 }
307 
308 #[cfg(test)]
309 mod test {
310     #[test]
from_hex()311     fn from_hex() {
312         use super::from_hex as fh;
313         assert_eq!(fh("", false).ok(), Some(vec![]));
314         assert_eq!(fh("0", false).ok(), None);
315         assert_eq!(fh("00", false).ok(), Some(vec![0x00]));
316         assert_eq!(fh("09", false).ok(), Some(vec![0x09]));
317         assert_eq!(fh("0f", false).ok(), Some(vec![0x0f]));
318         assert_eq!(fh("99", false).ok(), Some(vec![0x99]));
319         assert_eq!(fh("ff", false).ok(), Some(vec![0xff]));
320         assert_eq!(fh("000", false).ok(), None);
321         assert_eq!(fh("0000", false).ok(), Some(vec![0x00, 0x00]));
322         assert_eq!(fh("0009", false).ok(), Some(vec![0x00, 0x09]));
323         assert_eq!(fh("000f", false).ok(), Some(vec![0x00, 0x0f]));
324         assert_eq!(fh("0099", false).ok(), Some(vec![0x00, 0x99]));
325         assert_eq!(fh("00ff", false).ok(), Some(vec![0x00, 0xff]));
326         assert_eq!(fh("\t\n\x0c\r ", false).ok(), None);
327         assert_eq!(fh("a", false).ok(), None);
328         assert_eq!(fh("0x", false).ok(), None);
329         assert_eq!(fh("0x0", false).ok(), None);
330         assert_eq!(fh("0x00", false).ok(), None);
331     }
332 
333     #[test]
from_pretty_hex()334     fn from_pretty_hex() {
335         use super::from_hex as fh;
336         assert_eq!(fh(" ", true).ok(), Some(vec![]));
337         assert_eq!(fh(" 0", true).ok(), None);
338         assert_eq!(fh(" 00", true).ok(), Some(vec![0x00]));
339         assert_eq!(fh(" 09", true).ok(), Some(vec![0x09]));
340         assert_eq!(fh(" 0f", true).ok(), Some(vec![0x0f]));
341         assert_eq!(fh(" 99", true).ok(), Some(vec![0x99]));
342         assert_eq!(fh(" ff", true).ok(), Some(vec![0xff]));
343         assert_eq!(fh(" 00 0", true).ok(), None);
344         assert_eq!(fh(" 00 00", true).ok(), Some(vec![0x00, 0x00]));
345         assert_eq!(fh(" 00 09", true).ok(), Some(vec![0x00, 0x09]));
346         assert_eq!(fh(" 00 0f", true).ok(), Some(vec![0x00, 0x0f]));
347         assert_eq!(fh(" 00 99", true).ok(), Some(vec![0x00, 0x99]));
348         assert_eq!(fh(" 00 ff", true).ok(), Some(vec![0x00, 0xff]));
349         assert_eq!(fh("\t\n\x0c\r ", true).ok(), Some(vec![]));
350         // Fancy Unicode spaces are ok too:
351         assert_eq!(fh("     23", true).ok(), Some(vec![0x23]));
352         assert_eq!(fh("a", true).ok(), None);
353         assert_eq!(fh(" 0x", true).ok(), Some(vec![]));
354         assert_eq!(fh(" 0x0", true).ok(), None);
355         assert_eq!(fh(" 0x00", true).ok(), Some(vec![0x00]));
356     }
357 
358     quickcheck! {
359         fn hex_roundtrip(data: Vec<u8>) -> bool {
360             let hex = super::to_hex(&data, false);
361             data == super::from_hex(&hex, false).unwrap()
362         }
363     }
364 
365     quickcheck! {
366         fn pretty_hex_roundtrip(data: Vec<u8>) -> bool {
367             let hex = super::to_hex(&data, true);
368             data == super::from_hex(&hex, true).unwrap()
369         }
370     }
371 
372     #[test]
hex_dumper()373     fn hex_dumper() {
374         use super::hex::Dumper;
375 
376         let mut dumper = Dumper::new(Vec::new(), "III");
377         dumper.write(&[0x89, 0x01, 0x33], "frame").unwrap();
378         let buf = dumper.into_inner();
379         assert_eq!(
380             ::std::str::from_utf8(&buf[..]).unwrap(),
381             "III00000000  \
382              89 01 33                                           \
383              frame\n");
384 
385         let mut dumper = Dumper::new(Vec::new(), "III");
386         dumper.write(&[0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01], "frame")
387             .unwrap();
388         let buf = dumper.into_inner();
389         assert_eq!(
390             ::std::str::from_utf8(&buf[..]).unwrap(),
391             "III00000000  \
392              89 01 33 89 01 33 89 01                            \
393              frame\n");
394 
395         let mut dumper = Dumper::new(Vec::new(), "III");
396         dumper.write(&[0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01,
397                        0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01], "frame")
398             .unwrap();
399         let buf = dumper.into_inner();
400         assert_eq!(
401             ::std::str::from_utf8(&buf[..]).unwrap(),
402             "III00000000  \
403              89 01 33 89 01 33 89 01  89 01 33 89 01 33 89 01   \
404              frame\n");
405 
406         let mut dumper = Dumper::new(Vec::new(), "III");
407         dumper.write(&[0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01,
408                        0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01,
409                        0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01,
410                        0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01], "frame")
411             .unwrap();
412         let buf = dumper.into_inner();
413         assert_eq!(
414             ::std::str::from_utf8(&buf[..]).unwrap(),
415             "III00000000  \
416              89 01 33 89 01 33 89 01  89 01 33 89 01 33 89 01   \
417              frame\n\
418              III00000010  \
419              89 01 33 89 01 33 89 01  89 01 33 89 01 33 89 01\n");
420 
421         let mut dumper = Dumper::new(Vec::new(), "");
422         dumper.write(&[0x89, 0x01, 0x33], "frame").unwrap();
423         dumper.write(&[0x04], "version").unwrap();
424         dumper.write(&[0x00], "type").unwrap();
425         let buf = dumper.into_inner();
426         assert_eq!(
427             ::std::str::from_utf8(&buf[..]).unwrap(),
428             "00000000  89 01 33                                           \
429              frame\n\
430              00000003           04                                        \
431              version\n\
432              00000004              00                                     \
433              type\n\
434              ");
435     }
436 
437     #[test]
time()438     fn time() {
439         use super::time;
440         use crate::types::Timestamp;
441         let t = |epoch| -> std::time::SystemTime {
442             Timestamp::from(epoch).into()
443         };
444         assert_eq!(&time(&t(1585217290)), "2020-03-26T10:08:10Z");
445     }
446 }
447