JTNEF - Java TNEF package
What is TNEF?Transport-Neutral Encapsulation Format (TNEF) is Microsoft's non-standard format for encapsulating mail which has any non-plain-text content or properties (such as rich text, embedded OLE objects, voting buttons, and sometimes just attachments). Whether or not a given message is encoded using TNEF is determined by the Outlook default settings, per-recipient setting, Exchange Server settings, and message type and content.
Once a TNEF message is used, the entire message, including all the original attachments and properties, is encapsulated in a single attachment of mime type "application/ms-tnef" added to the message to be sent over the Internet. This attachment is usually named "WINMAIL.DAT", and when sent to any non-MS mail client, is useless, and makes access to the original message attachments impossible.
What is the Java TNEF package?The Java TNEF package is an open-source implementation of a TNEF message handler, which can be used as a command-line utility or integrated into Java-based mail applications to extract the original message content.
It has been in use in many production systems around the world for over a decade, including products by small start-ups, large open-source service providers, and well-known multinational corporations.
How do I use the Java TNEF package?The Java TNEF package is written in pure Java, runs on JDK 1.5 or later, and requires no special installation - just add the jar file to your classpath.
It is also available on Maven Central at the artifact coordinates
If you are an end user getting strange attachments named "WINMAIL.DAT" or "ATT00001.DAT", instead of other expected attachments, you can simply run the net.freeutils.tnef.TNEF class from the command line to extract the original attachments from such a TNEF file.
If you are a Java developer working on a mail client or server, and need to handle TNEF attachments (because whether you like it or not, they're out there in real-world messages), you have several choices:
- Low-level: you can use the TNEFInputStream class to read TNEF attributes, which are the basic unit in a TNEF stream, and do with them as you please.
Middle-level: the net.freeutils.tnef package gives you access to
the entire TNEF content through simple Java objects representing
the underlying TNEF data structures. You can use these classes to
access all TNEF attributes and MAPI properties that were sent with
For example, you can choose to implement voting buttons or receipt notifications in your Java application by finding and interpreting the appropriate MAPI properties. This requires knowledge of the MAPI properties and their meaning.
High-level: The net.freeutils.tnef.TNEF class is a simple example
of using these middle-level classes to display the message
properties and extract the attachments. You can use it directly
from your application, or just browse the source code for an
example of how to do things yourself.
The net.freeutils.tnef.mime package gives you high-level access to the TNEF message using the JavaMail API. The TNEFMime class is a simple example of using these classes, and allows you to extract a TNEF attachment from a mime message (which can then be processed using the TNEF or TNEFMime classes) or to convert a TNEF attachment or a message containing a TNEF attachment to an equivalent standard mime message with the original header fields and attachments, including read receipt notification conversion and contact to vCard conversion, etc.
The net.freeutils.tnef.msg package contains a .msg file parser. The Msg class uses the Apache POI library and provides a simple API for parsing a .msg file into a net.freeutils.tnef.Message instance for access to its MAPI properties and attachments.
If you're interested in the format of the PR_RTF_COMPRESSED MAPI property, please read on here.
What's New?In version 2.0.0:
- Overhauled entire codebase with extensive internal refactoring.
- Upgraded codebase to use Java 5 syntax where appropriate.
- Removed deprecated TNEFUtils.toGUID/calculateCRC32/decompressRTF methods.
- Changed relevant classes to implement Closeable.
- Added TNEFUtils.closeAll utility method.
- Improved bulk RawInputStream.read performance.
- Improved explicit closing of RawInputStreams during exception handling.
- Fixed RawInputStream.skip return value when underlying stream stalls.
- Fixed RawInputStream.available on large data streams (over 2G).
- Moved property parsing from MAPIProps to MAPIProp.
- Changed MAPIProp/MAPIProps constructors to varargs.
- Reorganized and added MAPIProp property ID constants.
- Added MAPIProp.getTypeSize method.
- Changed MAPIValue.getValue to return PT_CURRENCY/PT_INT8BYTE values as long rather than long.
- Changed MAPIValue.toString to use ISO-8601 format for dates.
- Added TNEFUtils.formatDate utility method.
- Fixed PT_APPTIME value parsing.
- Fixed rare PT_BOOLEAN incorrect values (when padding is non-zero).
- Fixed GUID string endianness and reorganized MAPIProp GUID constants.
- Changed RawDataSource.getInputStream to throw IOException if data is null.
- Changed IOExceptions to EOFExceptions on unexpected end of stream.
- Changed toString implementations and semantics to be more consistent.
- Extracted TNEFUtils.appendHexString/toHexString utility methods for integers.
- Added protections against Denial of Service attacks and OutOfMemoryErrors.
- Fixed TNEF.extractAttachments potentially writing outside of output directory.
- Improved CompressedRTFInputStream decompression performance.
- Added CompressedRTFInputStream.calculateCRC32 method for incremental calculation.
- Fixed CompressedRTFInputStream large uncompressed data parsing.
- Fixed CompressedRTFInputStream underflow handling during uncompressed data parsing.
- Changed TNEFMime to use pluggable converters.
- Refactored handlers into ContactConverter, ReadReceiptConverter and MessageConverter classes.
- Changed ContactField constructor parameter order and added varargs overload.
- Fixed contact conversion when subject (contact name) is missing.
- Fixed conversion of some contact fields with null or empty values.
- Added Msg.processMessage(InputStream) convenience method.
- Extracted Properties class for Msg property-related processing.
- Reimplemented Msg properties header parsing.
- Improved Msg.processAttachment distinguishing between attachments and nested messages.
- Added Msg attachment extraction from embedded objects which have an Ole10Native stream.
- Fixed Msg named property pre-defined constant GUID lookups.
- Fixed Msg multi-valued property processing.
- Improved javadocs.
In version 1.9.0:
- Migrated to Maven build system, directory structure and artifact conventions.
- Added OSGi headers to jar manifest.
- Upgraded dependencies.
- Fixed javadoc errors when building with JDK 8.
- Improved javadocs and misc. minor refactorings.
In version 1.8.0:
- Fixed Msg processing of document entries with uppercase names.
- Fixed Msg processing when the standard name ID entries are missing.
- Changed CompressedRTFInputStream.decompressRTF to allow empty compressed data.
- Fixed CompressedRTFInputStream.moreCompressed to check CRC also on empty compressed data.
- Fixed Attachment.getFilename throwing exception when no MAPI properties exist.
- Added simple TNEFInputStream.toString method showing the stream key.
- Extracted TNEF.extractAttachments method.
- Simplified TNEF.extractContent printing to standard output.
- Fixed TNEF.extractContent duplicate output of nested messages.
- Added explicit close methods to Attachment/Attr/MAPIProp/MAPIProps/MAPIValue/Message classes.
- Added aggressive explicit closing of resources (without waiting for finalization).
- Improved javadocs.
In version 1.7.0:
- Dropped support for JDK 1.4 and earlier.
- Added support for PR_TRANSPORT_MESSAGE_HEADERS property in TNEFMime for better reproduction of original headers.
- Changed TNEFMime conversion to replace empty multipart with empty text/plain part, preventing JavaMail exception.
- Fixed CompressedRTFInputStream support of uncompressed streams.
- Added explicit closing of RawInputStreams where possible.
- Changed TNEF command line utility to use RawInputStream.
- Made the second command line argument (outputdir) of TNEF and Msg optional, using current directory as default.
- Added Msg command line usage instructions.
- Improved javadocs and misc. minor refactorings.
In version 1.6.0:
- Added RawInputStream.toByteArray() overload with max bytes to return.
- Reduced memory allocation in RawInputStream.toString() for large streams.
- Added CompressedRTFInputStream class and moved all compressed-RTF related functionality into it.
- Changed RTF decompression end condition to be a self-referencing reference instead of using size field.
- Changed RTF decompression to skip CRC check on uncompressed data.
- Fixed Msg handling of message signature attachments containing an image.
- Fixed Msg processing of multivalued MAPI types (MV_FLAG).
- Fixed MAPIPropName.equals() comparison.
- Added Message.getOEMCodePage() method.
- Fixed handling of attAttachTransportFilename attribute.
- Changed Message.getMAPIProps() to throw IOException.
- Added Exchange property ID constants.
- Improved javadocs and misc. minor refactorings.
In version 1.5.0:
- Changed Msg.processRecipients() to return array instead of List and fixed ClassCaseException.
- Added PR_HTML and PR_BODY_HTML properties and their conversion in TNEFMime.
- Made text body parts converted by TNEFMime use the UTF-8 encoding explicitly.
- Fixed RawInputStream mark/reset implementation.
- Added thrown IOException if attribute length is greater than actual available bytes in TNEFInputStream.readAttr().
- Fixed NullPointerException in ReadReceiptHandler when a field is missing.
- Added TNEFMime command line option to directly convert a TNEF attachment into a MIME file.
- Improved documentation, javadocs, formatting and misc. minor refactorings.
In version 1.4.0:
- Fixed attachment filename to be taken from TNEF attributes if it cannot be determined from the MAPI props.
- Added support for converting a contact field with multiple values to multiple vCard fields in ContactHandler.
- Fixed equals-hashCode contract of MAPIPropName.
- Improved documentation, misc. minor fixes and applied some FindBugs recommendations.
- Introduced the GUID class with immutability guarantee and convenience methods, now used wherever guids are needed.
- Fixed RawInputStream to always handle end of stream conditions properly.
- Added initial proof-of-concept .msg file handling in the new msg package and Msg class.
- Fixed Attachment to remove leading GUID only on PT_OBJECTs.
- Added Attr constructor which accepts an arbitrary data object (not just a RawInputStream).
- Added an empty constructor to Message and extracted a read() method for easier subclassing and external use.
- Added an embed parameter to TNEFMime.convert() for making converted TNEF message embedding optional.
In version 1.3.1:
- Fixed TNEFMime conversion when the TNEF attachment is the root mime part, rather than an attachment.
- Added experimental support for read receipt notifications (RFC 2298) in the new ReadReceiptHandler class.
- Added a name field to RawDataSource, which is being used to properly support attachment filenames.
- Reorganized and significantly improved vCard (contact) support in the new ContactHandler class.
- Updated and added many vCard fields, including additional contact devices and X509 certificates.
- Improved documentation and misc. minor fixes.
In version 1.3.0:
- Fixed backward compatibility with JDK 1.3.1.
- Added TNEFUtils.replace() method, which is used instead of String.replaceAll().
- Fixed PR_ATTACH_DATA_OBJ properties to provide the content data without the leading GUID.
- Fixed robustness of reading from streams, using read loop instead of single read.
- Added TNEFUTils.calculateChecksum() overloads that accept a partial byte array or a RawInputStream.
- Improved TNEFInputStream.readAttr() to reuse RawInputStream and avoid reading data into memory.
- Added RawInputStream.readFully() method, and modified RawInputStream to use it internally.
- Added RawInputStream.readXXX() methods for the primitive TNEF types.
- Added MAPIValue.getRawData().
- Changed ByteArrayInputStream returned by MAPIValue.getValue() with PT_OBJECT to RawInputStream.
- Changed internal MAPIValue data to be stored as RawInputStream.
- Added constructors to all objects that parse from a RawInputStream.
- Improved TNEF.main to catch Throwable rather than Exception.
- Modified a RawInputStream constructor contract - it now starts at current position of given RawInputStream.
- Removed byte array constructors.
- Fixed RawInputStream.available(), which is now used internally.
- Modified everything for minimal memory use, by using RawInputStream everywhere.
- Fixed parsing of PT_SHORT type properties.
- Fixed parsing of PT_CLSID type properties.
- Added URL (PR_BUSINESS_HOME_PAGE) to vCard.
- Added common CDO PropSetId GUID constants to MAPIProp for convenient use with MAPIPropNames.
- Added TNEFUtils.toGUID() convenience method to convert a GUID string to a GUID byte array.
- Added overloaded MAPIProp.findProp(), getProp() and getPropValue() methods that work with MAPIPropNames.
- Added MAPIPropName.equals() method.
- Fixed vCard conversion of email field, using the CDO named property.
- Reorganized contact to vCard conversion code.
- Fixed escaped character handling in vCard conversion.
- Updated to vCard version 3.0.
- Added TNEFUtils.calculateCRC32() method and its use in decompressRTF().
- Added BufferedInputStream wrapping for files in RawInputStream.
- Fixed up the javadocs.
In version 1.2.3:
- Added getMAPIProps convenience method to Message.
- Changed atpWord attributes (attMessageClass) to return a string value.
- Added experimental vCard (IPM.Contact) processing in TNEFMime.
- Added RTF bodypart to converted message in TNEFMime if PR_RTF_COMPRESSED property exists.
- Updated Attachment.getFilename javadoc to explicitly mention that filename may be null.
- Added VCARD (IPM.Contact) support in TNEFMime.
- Added text/rtf bodypart when PR_RTF_COMPRESSED property exists in TNEFMime.
- Fixed TNEFMime handling when attachment has no filename.
- Added jtnef.checksum.ignore system property to ignore invalid checksums.
- Added TNEFInputStream.setChecksumIgnore method to ignore invalid checksums.
In version 1.2.2:
- Fixed casting bug in TNEFMime.
- Fixed overwriting of text of inner rfc822 message in TNEFMime.
- Added getPropValue convenience method in MAPIProps.
- Added finalizer in RawInputStream to close the underlying resources when no longer used.
- Added utility method in TNEFUtils to check if a mime type is TNEF (includes application/vnd.ms-tnef).
- Fixed Attachment.writeTo method to use a copy of the stream and leave the main stream untouched.
- Fixed Attachment.writeTo when an attachment has no content.
- Optimized Attachment.writeTo by using a buffer.
- Fixed up the javadocs.
In version 1.2.1:
- Added TNEFUtils.decompressRTF method which can decompress PR_RTF_COMPRESSED properties.
- TNEFInputStream now uses RawInputStream internally to reduce memory footprint.
- Fixed TNEFInputStream when used with file-based constructors (using RawInputStream).
- Fixed TNEFUtils bytes-to-unsigned methods to be truly unsigned.
In version 1.2:
- Added getAttribute(int ID) methods to Message and Attachment for easier access to attributes.
- Attributes of type atpDate are now always in GMT and with 0 milliseconds.
- Fixed attRecipTable attributes with multiple recipients.
- Fixed the MAPIProps unused constructor.
- Added GUID to MAPIPropName display.
- Added MAPIProps container for easier access to property collections.
- Changed MAPIValue.getValue() return object types to better reflect the MAPI types:
- PT_INT -> Integer
- PT_ERROR -> Integer
- PT_BOOLEAN -> Boolean
- PT_FLOAT -> Float
- PT_DOUBLE -> Double
- Fixed RawInputStream.toByteArray() to work properly when the raw source is a byte array.
- Fixed RawInputStream.newStream() to return proper length for -1 parameter.
- Added TNEFUtils.getU64() method.
- Changed non-Unicode string decoding to use "ISO8859-1" encoding (see TNEFUtils.createString() JavaDoc). This allows the original bytes to be reconstructed.
In version 1.1:
- TNEFMime command line now takes an options parameter.
- TNEFMime command line supports option to extract a TNEF attachment from a MIME file.
- Added getKey() method to TNEFInputStream to retrieve stream key.
- Fixed handling of named properties of type MNID_STRING.
- Fixed handling of properties of type PT_NULL.
- Fixed Unicode support for properties of type PT_UNICODE_STRING.
- Fixed handling of some multivalued properties (MV_FLAG).
- Added toString() methods to Address, TRPAddress and RawInputStream for friendlier display.
- Limited hex dump length for long binary fields.
- Changed non-Unicode string decoding to use "Cp1252" encoding (see TNEFUtils.createString() JavaDoc).
In version 1.0:
- This is the first release of the Java TNEF package.
LicenseThe JTNEF Package is provided under the GNU General Public License agreement.
For non-GPL commercial licensing please contact the author.
DonateIf you like it, why not give something back?
ContactYou can contact the author via e-mail at:
Please write in with any bugs, suggestions, fixes, contributions, or just to drop a good word and let me know you've found the JTNEF Package useful and you'd like it to keep being maintained.
For updates and additional information, you can always visit the website at: