Skip to main content

tnef/
lib.rs

1//! A basic [TNEF] parser written in pure Rust.
2//!
3//! TNEF file contains a stream of records called "attributes". Using
4//! `TnefReader` you can read attributes stored in the provided TNEF buffer.
5//! At the moment we do not handle parsing of attribute data outside of
6//! attachment attributes.
7//!
8//! If you just want to unpack attachments stored in TNEF, you can use a
9//! convenience function `read_attachments`.
10//!
11//! # Usage example
12//! ```
13//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
14//! # let tnef_data = b"\
15//! #   \x78\x9f\x3e\x22\x23\x28\x01\x06\x90\x08\x00\x04\x00\x00\x00\x00\
16//! #   \x00\x01\x00\x01\x00\x01\x07\x90\x06\x00\x08\x00\x00\x00\xe3\x04\
17//! #   \x00\x00\x00\x00\x00\x00\xe7\x00\
18//! # ";
19//! for attribute in tnef::TnefReader::new(tnef_data)? {
20//!     let (id, data) = attribute?;
21//!     println!("{:?} {}", id, data.len());
22//! }
23//! # Ok(()) }
24//! ```
25//! [TNEF]: https://en.wikipedia.org/wiki/Transport_Neutral_Encapsulation_Format
26use byteorder::{LE, ByteOrder};
27use chrono::naive::NaiveDateTime;
28
29mod error;
30mod attr_ids;
31
32use error::Error;
33pub use attr_ids::*;
34
35/// TNEF reader.
36///
37/// Core functionality is accessible via `Iterator` trait.
38pub struct TnefReader<'a> {
39    src: &'a [u8],
40    code_page: u32,
41    msg_section: bool,
42    done: bool,
43}
44
45impl<'a> TnefReader<'a> {
46    /// Create a new reader from the provided buffer.
47    pub fn new(src: &'a [u8]) -> Result<Self, Error> {
48        let mut ret = Self {
49            src, code_page: 0, msg_section: true, done: false,
50        };
51        ret.read_header()?;
52        ret.read_version()?;
53        ret.code_page = ret.read_oem_code_page()?;
54        Ok(ret)
55    }
56
57    /// Get OEM code page.
58    pub fn get_code_page(&self) -> u32 {
59        self.code_page
60    }
61
62    fn read_attribute(&mut self)
63        -> Result<Option<(AttributeId, &'a [u8])>, Error>
64    {
65        if self.src.is_empty() { return Ok(None); }
66        let level = self.split_at(1)?[0];
67        match (level, self.msg_section) {
68            (0x01, true) | (0x02, false) => (),
69            (0x01, false) => return Err(Error::UnexpectedMessageAttribute),
70            (0x02, true) => self.msg_section = false,
71            _ => return Err(Error::InvalidAttributeLevel(level)),
72        }
73        let raw_id = self.read_u32()?;
74        let id = AttributeId::from_u32(self.msg_section, raw_id)?;
75        let len = self.read_u32()? as usize;
76        let msg = self.split_at(len)?;
77        self.verify_checksum(msg)?;
78        Ok(Some((id, msg)))
79    }
80
81    fn verify_checksum(&mut self, msg: &[u8]) -> Result<(), Error> {
82        let val = self.read_u16()?;
83        let sum: u16 = msg.iter()
84            .map(|b| u16::from(*b))
85            .fold(0, |sum, i| sum.wrapping_add(i));
86        if sum != val {
87            Err(Error::ChecksumMismatch)
88        } else {
89            Ok(())
90        }
91    }
92
93    fn read_u16(&mut self) -> Result<u16, Error> {
94        Ok(LE::read_u16(self.split_at(2)?))
95    }
96
97    fn read_u32(&mut self) -> Result<u32, Error> {
98        Ok(LE::read_u32(self.split_at(4)?))
99    }
100
101    fn split_at(&mut self, len: usize) -> Result<&'a [u8], Error> {
102        if self.src.len() < len { return Err(Error::UnexpectedEof); }
103        let (l, r) = {self.src}.split_at(len);
104        self.src = r;
105        Ok(l)
106    }
107
108    fn read_header(&mut self) -> Result<(), Error> {
109        let h = self.read_u32()?;
110        if h != 0x223e_9f78 {
111            return Err(Error::InvalidHeader);
112        }
113        // ignore LegacyKey
114        let _ = self.read_u16()?;
115        Ok(())
116    }
117
118    fn read_version(&mut self) -> Result<(), Error> {
119        let level = self.split_at(1)?[0];
120        let id = self.read_u32()?;
121        let len = self.read_u32()?;
122        if level != 0x01 || id != 0x0008_9006 || len != 4 {
123            return Err(Error::InvalidVersion);
124        }
125        let msg = self.split_at(4)?;
126        if msg != b"\x00\x00\x01\x00" { return Err(Error::InvalidVersion); }
127        self.verify_checksum(msg)?;
128        Ok(())
129    }
130
131    fn read_oem_code_page(&mut self) -> Result<u32, Error> {
132        let level = self.split_at(1)?[0];
133        let id = self.read_u32()?;
134        let len = self.read_u32()?;
135        if level != 0x01 || id != 0x0006_9007 || len != 8 {
136            return Err(Error::InvalidVersion);
137        }
138        let msg = self.split_at(8)?;
139        self.verify_checksum(msg)?;
140        let code_page = LE::read_u32(&msg[..4]);
141        let sec_code_page = LE::read_u32(&msg[4..]);
142        if sec_code_page != 0 {
143            return Err(Error::InvalidOemCodePage);
144        }
145
146        codepage::to_encoding(code_page as u16)
147            .ok_or(Error::InvalidOemCodePage)?;
148
149        Ok(code_page)
150    }
151}
152
153impl<'a> std::iter::Iterator for TnefReader<'a> {
154    type Item = Result<(AttributeId, &'a [u8]), Error>;
155
156    fn next(&mut self) -> Option<Self::Item> {
157        if self.done { return None; }
158        match self.read_attribute() {
159            Ok(Some(val)) => Some(Ok(val)),
160            Ok(None) => {
161                self.done = true;
162                None
163            }
164            Err(err) => {
165                self.done = true;
166                Some(Err(err))
167            }
168        }
169    }
170}
171
172impl<'a> std::iter::FusedIterator for TnefReader<'a> {}
173
174fn parse_datetime(data: &[u8]) -> Result<NaiveDateTime, Error> {
175    use chrono::naive::{NaiveDate, NaiveTime};
176    if data.len() != 14 { return Err(Error::InvlidDateTime); }
177    let year = i32::from(LE::read_u16(&data[0..2]));
178    let month = u32::from(LE::read_u16(&data[2..4]));
179    let day = u32::from(LE::read_u16(&data[4..6]));
180    let hour = u32::from(LE::read_u16(&data[6..8]));
181    let minute = u32::from(LE::read_u16(&data[8..10]));
182    let second = u32::from(LE::read_u16(&data[10..12]));
183    let _day_of_week = LE::read_u16(&data[12..14]);
184
185    let date = NaiveDate::from_ymd_opt(year, month, day)
186        .ok_or(Error::InvlidDateTime)?;
187    let time = NaiveTime::from_hms_opt(hour, minute, second)
188        .ok_or(Error::InvlidDateTime)?;
189    Ok(NaiveDateTime::new(date, time))
190}
191
192/// Attachment type.
193#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
194pub enum AttachType {
195    File,
196    Ole,
197}
198
199/// Attachment data flags
200#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
201pub enum AttachDataFlags {
202    FileDataDefault,
203    FileDataMacBinary,
204}
205
206/// Attachment rendering data.
207#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
208pub struct RendData {
209    pub attach_type: AttachType,
210    pub attach_position: u32,
211    pub render_width: u16,
212    pub render_height: u16,
213    pub flags: AttachDataFlags,
214}
215
216impl RendData {
217    fn parse(data: &[u8]) -> Result<Self, Error> {
218        if data.len() != 14 { return Err(Error::InvalidRendData); }
219        let attach_type = match LE::read_u16(&data[0..2]) {
220            0x0001 => AttachType::File,
221            0x0002 => AttachType::Ole,
222            _ => return Err(Error::InvalidRendData),
223        };
224        let attach_position = LE::read_u32(&data[2..6]);
225        let render_width = LE::read_u16(&data[6..8]);
226        let render_height = LE::read_u16(&data[8..10]);
227        let flags = match LE::read_u32(&data[10..14]) {
228            0x0000_0000 => AttachDataFlags::FileDataDefault,
229            0x0000_0001 => AttachDataFlags::FileDataMacBinary,
230            _ => return Err(Error::InvalidRendData),
231        };
232        Ok(Self {
233            attach_type, attach_position, render_width, render_height, flags,
234        })
235    }
236}
237
238fn parse_string(data: &[u8], code_page: u32) -> Result<String, Error> {
239    let n = data.len();
240    if data.is_empty() || data[n-1] != 0x00 {
241        return Err(Error::InvalidString);
242    }
243    let (s, malformed) = codepage::to_encoding(code_page as u16)
244        .ok_or(Error::InvalidString)?
245        .decode_with_bom_removal(&data[..n-1]);
246    if malformed { return Err(Error::InvalidString); }
247    Ok(s.to_string())
248}
249
250/// TNEF attachment.
251#[derive(Default)]
252pub struct RawAttachment<'a> {
253    pub data: Option<&'a [u8]>,
254    pub title: Option<String>,
255    pub meta: Option<&'a [u8]>,
256    pub create_date: Option<NaiveDateTime>,
257    pub modify_date: Option<NaiveDateTime>,
258    pub transport_filename: Option<String>,
259    pub rend_data: Option<RendData>,
260    pub props: Option<&'a [u8]>,
261}
262
263impl<'a> RawAttachment<'a> {
264    fn is_default(&self) -> bool {
265        match self {
266            RawAttachment {
267                data: None, title: None, meta: None,
268                create_date: None, modify_date: None,
269                transport_filename: None, rend_data: None,
270                props: None,
271            } => true,
272            _ => false,
273        }
274    }
275}
276
277/// TNEF attachment.
278#[derive(Debug, Clone)]
279pub struct Attachment<'a> {
280    pub title: String,
281    pub data: &'a [u8],
282    pub create_date: NaiveDateTime,
283    pub modify_date: NaiveDateTime,
284    pub rend_data: RendData,
285    pub props: &'a [u8],
286    pub meta: Option<&'a [u8]>,
287    pub transport_filename: Option<String>,
288}
289
290impl<'a> Attachment<'a> {
291    fn from_raw(raw: RawAttachment<'a>) -> Option<Self> {
292        match raw {
293            RawAttachment {
294                title: Some(title),
295                data: Some(data),
296                create_date: Some(create_date),
297                modify_date: Some(modify_date),
298                rend_data: Some(rend_data),
299                props: Some(props),
300                meta,
301                transport_filename,
302            } => Some(Attachment {
303                title, data, create_date, modify_date, rend_data, props,
304                meta, transport_filename,
305            }),
306            _ => None,
307        }
308    }
309}
310
311/// Convenience function for extracting attachments from TNEF data.
312///
313/// It assumes that attachments always contains the following fields:
314/// `title`, `data`, `create_date`, `modify_date`, `rend_data` and `props`.
315/// If one of those fields is missing the attachment will be ignored.
316pub fn read_attachments(buf: &[u8]) -> Result<Vec<Attachment>, Error> {
317    let r = TnefReader::new(&buf)?;
318    let code_page = r.get_code_page();
319    let mut buf = vec![];
320    let mut t = RawAttachment::default();
321    for attr in r {
322        let (id, data) = attr?;
323        let id = match id {
324            AttributeId::Message(_) => continue,
325            AttributeId::Attachment(v) => v,
326        };
327        // first attachment attribute must be AttachRendData
328        if t.is_default() && id != AttachAttrId::AttachRendData {
329            return Err(Error::AttachmentParsingFailure);
330        }
331        match id {
332            AttachAttrId::AttachRendData => {
333                t.rend_data = Some(RendData::parse(data)?);
334            },
335            AttachAttrId::Data => {
336                if t.data.is_none() {
337                    t.data = Some(data);
338                } else {
339                    return Err(Error::AttachmentParsingFailure);
340                }
341            }
342            AttachAttrId::Title => {
343                if t.title.is_none() {
344                    t.title = Some(parse_string(data, code_page)?);
345                } else {
346                    return Err(Error::AttachmentParsingFailure);
347                }
348            }
349            AttachAttrId::MetaFile => {
350                if t.meta.is_none() {
351                    t.meta = Some(data);
352                } else {
353                    return Err(Error::AttachmentParsingFailure);
354                }
355            }
356            AttachAttrId::CreateDate => {
357                if t.create_date.is_none() {
358                    t.create_date = Some(parse_datetime(data)?);
359                } else {
360                    return Err(Error::AttachmentParsingFailure);
361                }
362            }
363            AttachAttrId::ModifyDate => {
364                if t.modify_date.is_none() {
365                    t.modify_date = Some(parse_datetime(data)?);
366                } else {
367                    return Err(Error::AttachmentParsingFailure);
368                }
369            }
370            AttachAttrId::TransportFilename => {
371                if t.transport_filename.is_none() {
372                    t.transport_filename = Some(parse_string(data, code_page)?);
373                } else {
374                    return Err(Error::AttachmentParsingFailure);
375                }
376            }
377            // last attachment attribute must be Attachment
378            AttachAttrId::Attachment => {
379                if t.props.is_none() {
380                    t.props = Some(data);
381                } else {
382                    return Err(Error::AttachmentParsingFailure);
383                }
384                if let Some(att) = Attachment::from_raw(t) {
385                    buf.push(att);
386                }
387                t = RawAttachment::default();
388            }
389        }
390    }
391    Ok(buf)
392}