/* ktnefparser.cpp Copyright (C) 2002 Michael Goffioul This file is part of KTNEF, the KDE TNEF support library/program. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file * This file is part of the API for handling TNEF data and * defines the KTNEFParser class. * * @author Michael Goffioul */ #include "ktnefparser.h" #include "ktnefattach.h" #include "ktnefproperty.h" #include "ktnefmessage.h" #include "ktnefdefs.h" #include #include #include #include #include #include #include #include using namespace KTnef; //@cond PRIVATE typedef struct { quint16 type; quint16 tag; QVariant value; struct { quint32 type; QVariant value; } name; } MAPI_value; //@endcond //@cond IGNORE void clearMAPIName( MAPI_value &mapi ); void clearMAPIValue( MAPI_value &mapi, bool clearName = true ); QString readMAPIString( QDataStream &stream, bool isUnicode = false, bool align = true, int len = -1 ); quint16 readMAPIValue( QDataStream &stream, MAPI_value &mapi ); QDateTime readTNEFDate( QDataStream &stream ); QString readTNEFAddress( QDataStream &stream ); QByteArray readTNEFData( QDataStream &stream, quint32 len ); QVariant readTNEFAttribute( QDataStream &stream, quint16 type, quint32 len ); QDateTime formatTime( quint32 lowB, quint32 highB ); QString formatRecipient( const QMap &props ); //@endcond //------------------------------------------------------------------------------ /** * Private class that helps to provide binary compatibility between releases. * @internal */ //@cond PRIVATE class KTnef::KTNEFParser::ParserPrivate { public: ParserPrivate() { defaultdir_ = "/tmp/"; current_ = 0; deleteDevice_ = false; device_ = 0; message_ = new KTNEFMessage; } ~ParserPrivate() { delete message_; } bool decodeAttachment(); bool decodeMessage(); bool extractAttachmentTo( KTNEFAttach *att, const QString &dirname ); void checkCurrent( int key ); bool readMAPIProperties( QMap& props, KTNEFAttach *attach = 0 ); bool parseDevice(); void deleteDevice(); QDataStream stream_; QIODevice *device_; bool deleteDevice_; QString defaultdir_; KTNEFAttach *current_; KTNEFMessage *message_; }; //@endcond KTNEFParser::KTNEFParser() : d( new ParserPrivate ) { } KTNEFParser::~KTNEFParser() { d->deleteDevice(); delete d; } KTNEFMessage *KTNEFParser::message() const { return d->message_; } void KTNEFParser::ParserPrivate::deleteDevice() { if ( deleteDevice_ ) { delete device_; } device_ = 0; deleteDevice_ = false; } bool KTNEFParser::ParserPrivate::decodeMessage() { quint32 i1, i2, off; quint16 u, tag, type; QVariant value; // read (type+name) stream_ >> i1; u = 0; tag = ( i1 & 0x0000FFFF ); type = ( ( i1 & 0xFFFF0000 ) >> 16 ); // read data length stream_ >> i2; // offset after reading the value off = device_->pos() + i2; switch ( tag ) { case attAIDOWNER: { uint tmp; stream_ >> tmp; value.setValue( tmp ); message_->addProperty( 0x0062, MAPI_TYPE_ULONG, value ); kDebug() << "Message Owner Appointment ID" << "(length=" << i2 << ")"; break; } case attREQUESTRES: stream_ >> u; message_->addProperty( 0x0063, MAPI_TYPE_UINT16, u ); value = ( bool )u; kDebug() << "Message Request Response" << "(length=" << i2 << ")"; break; case attDATERECD: value = readTNEFDate( stream_ ); message_->addProperty( 0x0E06, MAPI_TYPE_TIME, value ); kDebug() << "Message Receive Date" << "(length=" << i2 << ")"; break; case attMSGCLASS: value = readMAPIString( stream_, false, false, i2 ); message_->addProperty( 0x001A, MAPI_TYPE_STRING8, value ); kDebug() << "Message Class" << "(length=" << i2 << ")"; break; case attMSGPRIORITY: stream_ >> u; message_->addProperty( 0x0026, MAPI_TYPE_ULONG, 2-u ); value = u; kDebug() << "Message Priority" << "(length=" << i2 << ")"; break; case attMAPIPROPS: kDebug() << "Message MAPI Properties" << "(length=" << i2 << ")"; { int nProps = message_->properties().count(); i2 += device_->pos(); readMAPIProperties( message_->properties(), 0 ); device_->seek( i2 ); kDebug() << "Properties:" << message_->properties().count(); value = QString( "< %1 properties >" ). arg( message_->properties().count() - nProps ); } break; case attTNEFVERSION: { uint tmp; stream_ >> tmp; value.setValue( tmp ); kDebug() << "Message TNEF Version" << "(length=" << i2 << ")"; } break; case attFROM: message_->addProperty( 0x0024, MAPI_TYPE_STRING8, readTNEFAddress( stream_ ) ); device_->seek( device_->pos() - i2 ); value = readTNEFData( stream_, i2 ); kDebug() << "Message From" << "(length=" << i2 << ")"; break; case attSUBJECT: value = readMAPIString( stream_, false, false, i2 ); message_->addProperty( 0x0037, MAPI_TYPE_STRING8, value ); kDebug() << "Message Subject" << "(length=" << i2 << ")"; break; case attDATESENT: value = readTNEFDate( stream_ ); message_->addProperty( 0x0039, MAPI_TYPE_TIME, value ); kDebug() << "Message Date Sent" << "(length=" << i2 << ")"; break; case attMSGSTATUS: { quint8 c; quint32 flag = 0; stream_ >> c; if ( c & fmsRead ) { flag |= MSGFLAG_READ; } if ( !( c & fmsModified ) ) { flag |= MSGFLAG_UNMODIFIED; } if ( c & fmsSubmitted ) { flag |= MSGFLAG_SUBMIT; } if ( c & fmsHasAttach ) { flag |= MSGFLAG_HASATTACH; } if ( c & fmsLocal ) { flag |= MSGFLAG_UNSENT; } message_->addProperty( 0x0E07, MAPI_TYPE_ULONG, flag ); value = c; } kDebug() << "Message Status" << "(length=" << i2 << ")"; break; case attRECIPTABLE: { quint32 rows; QList recipTable; stream_ >> rows; for ( uint i=0; i props; readMAPIProperties( props, 0 ); recipTable << formatRecipient( props ); } message_->addProperty( 0x0E12, MAPI_TYPE_STRING8, recipTable ); device_->seek( device_->pos() - i2 ); value = readTNEFData( stream_, i2 ); } kDebug() << "Message Recipient Table" << "(length=" << i2 << ")"; break; case attBODY: value = readMAPIString( stream_, false, false, i2 ); message_->addProperty( 0x1000, MAPI_TYPE_STRING8, value ); kDebug() << "Message Body" << "(length=" << i2 << ")"; break; case attDATEMODIFIED: value = readTNEFDate( stream_ ); message_->addProperty( 0x3008, MAPI_TYPE_TIME, value ); kDebug() << "Message Date Modified" << "(length=" << i2 << ")"; break; case attMSGID: value = readMAPIString( stream_, false, false, i2 ); message_->addProperty( 0x300B, MAPI_TYPE_STRING8, value ); kDebug() << "Message ID" << "(length=" << i2 << ")"; break; case attOEMCODEPAGE: value = readTNEFData( stream_, i2 ); kDebug() << "Message OEM Code Page" << "(length=" << i2 << ")"; break; default: value = readTNEFAttribute( stream_, type, i2 ); //kDebug().form( "Message: type=%x, length=%d, check=%x\n", i1, i2, u ); break; } // skip data if ( device_->pos() != off && !device_->seek( off ) ) { return false; } // get checksum stream_ >> u; // add TNEF attribute message_->addAttribute( tag, type, value, true ); //kDebug() << "stream:" << device_->pos(); return true; } bool KTNEFParser::ParserPrivate::decodeAttachment() { quint32 i; quint16 tag, type, u; QVariant value; QString str; stream_ >> i; // i <- attribute type & name tag = ( i & 0x0000FFFF ); type = ( ( i & 0xFFFF0000 ) >> 16 ); stream_ >> i; // i <- data length checkCurrent( tag ); switch ( tag ) { case attATTACHTITLE: value = readMAPIString( stream_, false, false, i ); current_->setName( value.toString() ); kDebug() << "Attachment Title:" << current_->name(); break; case attATTACHDATA: current_->setSize( i ); current_->setOffset( device_->pos() ); device_->seek( device_->pos() + i ); value = QString( "< size=%1 >" ).arg( i ); kDebug() << "Attachment Data: size=" << i; break; case attATTACHMENT: // try to get attachment info i += device_->pos(); readMAPIProperties( current_->properties(), current_ ); device_->seek( i ); current_->setIndex( current_->property( MAPI_TAG_INDEX ).toUInt() ); current_->setDisplaySize( current_->property( MAPI_TAG_SIZE ).toUInt() ); str = current_->property( MAPI_TAG_DISPLAYNAME ).toString(); if ( !str.isEmpty() ) { current_->setDisplayName( str ); } current_->setFileName( current_->property( MAPI_TAG_FILENAME ). toString() ); str = current_->property( MAPI_TAG_MIMETAG ).toString(); if ( !str.isEmpty() ) { current_->setMimeTag( str ); } current_->setExtension( current_->property( MAPI_TAG_EXTENSION ). toString() ); value = QString( "< %1 properties >" ). arg( current_->properties().count() ); break; case attATTACHMODDATE: value = readTNEFDate( stream_ ); kDebug() << "Attachment Modification Date:" << value.toString(); break; case attATTACHCREATEDATE: value = readTNEFDate( stream_ ); kDebug() << "Attachment Creation Date:" << value.toString(); break; case attATTACHMETAFILE: kDebug() << "Attachment Metafile: size=" << i; //value = QString( "< size=%1 >" ).arg( i ); //device_->seek( device_->pos()+i ); value = readTNEFData( stream_, i ); break; default: value = readTNEFAttribute( stream_, type, i ); kDebug() << "Attachment unknown field: tag=" << hex << tag << ", length=" << dec << i; break; } stream_ >> u; // u <- checksum // add TNEF attribute current_->addAttribute( tag, type, value, true ); //kDebug() << "stream:" << device_->pos(); return true; } void KTNEFParser::setDefaultExtractDir( const QString &dirname ) { d->defaultdir_ = dirname; } bool KTNEFParser::ParserPrivate::parseDevice() { quint16 u; quint32 i; quint8 c; message_->clearAttachments(); delete current_; current_ = 0; if ( !device_->open( QIODevice::ReadOnly ) ) { kDebug() << "Couldn't open device"; return false; } stream_.setDevice( device_ ); stream_.setByteOrder( QDataStream::LittleEndian ); stream_ >> i; if ( i == TNEF_SIGNATURE ) { stream_ >> u; kDebug().nospace() << "Attachment cross reference key: 0x" << hex << qSetFieldWidth( 4 ) << qSetPadChar( '0' ) << u; //kDebug() << "stream:" << device_->pos(); while ( !stream_.atEnd() ) { stream_ >> c; switch( c ) { case LVL_MESSAGE: if ( !decodeMessage() ) { goto end; } break; case LVL_ATTACHMENT: if ( !decodeAttachment() ) { goto end; } break; default: kDebug() << "Unknown Level:" << c << ", at offset" << device_->pos(); goto end; } } if ( current_ ) { checkCurrent( attATTACHDATA ); // this line has the effect to append the // attachment, if it has data. If not it does // nothing, and the attachment will be discarded delete current_; current_ = 0; } return true; } else { kDebug() << "This is not a TNEF file"; end: device_->close(); return false; } } bool KTNEFParser::extractFile( const QString &filename ) const { KTNEFAttach *att = d->message_->attachment( filename ); if ( !att ) { return false; } return d->extractAttachmentTo( att, d->defaultdir_ ); } bool KTNEFParser::ParserPrivate::extractAttachmentTo( KTNEFAttach *att, const QString &dirname ) { QString filename = dirname + '/'; if ( !att->fileName().isEmpty()) { filename += att->fileName(); } else { filename += att->name(); } if ( filename.endsWith( '/') ) { return false; } if ( !device_->isOpen() ) { return false; } if ( !device_->seek( att->offset() ) ) { return false; } KSaveFile outfile( filename ); if ( !outfile.open() ) { return false; } quint32 len = att->size(), sz( 16384 ); int n( 0 ); char *buf = new char[sz]; bool ok( true ); while ( ok && len > 0 ) { n = device_->read( buf, qMin( sz, len ) ); if ( n < 0 ) { ok = false; } else { len -= n; if ( outfile.write( buf, n ) != n ) { ok = false; } } } delete [] buf; return ok; } bool KTNEFParser::extractAll() { QList l = d->message_->attachmentList(); QList::const_iterator it = l.constBegin(); for ( ; it != l.constEnd(); ++it ) { if ( !d->extractAttachmentTo( *it, d->defaultdir_ ) ) { return false; } } return true; } bool KTNEFParser::extractFileTo( const QString &filename, const QString &dirname ) const { kDebug() << "Extracting attachment: filename=" << filename << ", dir=" << dirname; KTNEFAttach *att = d->message_->attachment( filename ); if ( !att ) { return false; } return d->extractAttachmentTo( att, dirname ); } bool KTNEFParser::openFile( const QString &filename ) const { d->deleteDevice(); delete d->message_; d->message_ = new KTNEFMessage(); d->device_ = new QFile( filename ); d->deleteDevice_ = true; return d->parseDevice(); } bool KTNEFParser::openDevice( QIODevice *device ) { d->deleteDevice(); d->device_ = device; return d->parseDevice(); } void KTNEFParser::ParserPrivate::checkCurrent( int key ) { if ( !current_ ) { current_ = new KTNEFAttach(); } else { if ( current_->attributes().contains( key ) ) { if ( current_->offset() >= 0 ) { if ( current_->name().isEmpty() ) { current_->setName( "Unnamed" ); } if ( current_->mimeTag().isEmpty() ) { // No mime type defined in the TNEF structure, // try to find it from the attachment filename // and/or content (using at most 32 bytes) KMimeType::Ptr mimetype; if ( !current_->fileName().isEmpty() ) { mimetype = KMimeType::findByPath( current_->fileName(), 0, true ); } if ( !mimetype ) { return; // FIXME } if ( mimetype->name() == "application/octet-stream" && current_->size() > 0 ) { int oldOffset = device_->pos(); QByteArray buffer( qMin( 32, current_->size() ), '\0' ); device_->seek( current_->offset() ); device_->read( buffer.data(), buffer.size() ); mimetype = KMimeType::findByContent( buffer ); device_->seek( oldOffset ); } current_->setMimeTag( mimetype->name() ); } message_->addAttachment( current_ ); current_ = 0; } else { // invalid attachment, skip it delete current_; current_ = 0; } current_ = new KTNEFAttach(); } } } //------------------------------------------------------------------------------ //@cond IGNORE #define ALIGN( n, b ) if ( n & ( b-1 ) ) { n = ( n + b ) & ~( b-1 ); } #define ISVECTOR( m ) ( ( ( m ).type & 0xF000 ) == MAPI_TYPE_VECTOR ) void clearMAPIName( MAPI_value &mapi ) { mapi.name.value.clear(); } void clearMAPIValue( MAPI_value &mapi, bool clearName ) { mapi.value.clear(); if ( clearName ) { clearMAPIName( mapi ); } } QDateTime formatTime( quint32 lowB, quint32 highB ) { QDateTime dt; quint64 u64; u64 = highB; u64 <<= 32; u64 |= lowB; u64 -= 116444736000000000LL; u64 /= 10000000; if ( u64 <= 0xffffffffU ) { dt.setTime_t( ( unsigned int )u64 ); } else { kWarning().nospace() << "Invalid date: low byte=" << showbase << qSetFieldWidth( 8 ) << qSetPadChar( '0' ) << lowB << ", high byte=" << highB; dt.setTime_t( 0xffffffffU ); } return dt; } QString formatRecipient( const QMap &props ) { QString s, dn, addr, t; QMap::ConstIterator it; if ( ( it = props.find( 0x3001 ) ) != props.end() ) { dn = ( *it )->valueString(); } if ( ( it = props.find( 0x3003 ) ) != props.end() ) { addr = ( *it )->valueString(); } if ( ( it = props.find( 0x0C15 ) ) != props.end() ) { switch ( ( *it )->value().toInt() ) { case 0: t = "From:"; break; case 1: t = "To:"; break; case 2: t = "Cc:"; break; case 3: t = "Bcc:"; break; } } if ( !t.isEmpty() ) { s.append( t ); } if ( !dn.isEmpty() ) { s.append( ' ' + dn ); } if ( !addr.isEmpty() && addr != dn ) { s.append( " <" + addr + '>' ); } return s.trimmed(); } QDateTime readTNEFDate( QDataStream &stream ) { // 14-bytes long quint16 y, m, d, hh, mm, ss, dm; stream >> y >> m >> d >> hh >> mm >> ss >> dm; return QDateTime( QDate( y, m, d ), QTime( hh, mm, ss ) ); } QString readTNEFAddress( QDataStream &stream ) { quint16 totalLen, strLen, addrLen; QString s; stream >> totalLen >> totalLen >> strLen >> addrLen; s.append( readMAPIString( stream, false, false, strLen ) ); s.append( " <" ); s.append( readMAPIString( stream, false, false, addrLen ) ); s.append( ">" ); quint8 c; for ( int i=8+strLen+addrLen; i> c; } return s; } QByteArray readTNEFData( QDataStream &stream, quint32 len ) { QByteArray array( len, '\0' ); if ( len > 0 ) { stream.readRawData( array.data(), len ); } return array; } QVariant readTNEFAttribute( QDataStream &stream, quint16 type, quint32 len ) { switch ( type ) { case atpTEXT: case atpSTRING: return readMAPIString( stream, false, false, len ); case atpDATE: return readTNEFDate( stream ); default: return readTNEFData( stream, len ); } } QString readMAPIString( QDataStream &stream, bool isUnicode, bool align, int len_ ) { quint32 len; char *buf = 0; if ( len_ == -1 ) { stream >> len; } else { len = len_; } quint32 fullLen = len; if ( align ) { ALIGN( fullLen, 4 ); } buf = new char[ len ]; stream.readRawData( buf, len ); quint8 c; for ( uint i=len; i> c; } QString res; if ( isUnicode ) { res = QString::fromUtf16( ( const unsigned short *)buf ); } else { res = QString::fromLocal8Bit( buf ); } delete [] buf; return res; } quint16 readMAPIValue( QDataStream &stream, MAPI_value &mapi ) { quint32 d; clearMAPIValue( mapi ); stream >> d; mapi.type = ( d & 0x0000FFFF ); mapi.tag = ( ( d & 0xFFFF0000 ) >> 16 ); if ( mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE ) { // skip GUID stream >> d >> d >> d >> d; // name type stream >> mapi.name.type; // name if ( mapi.name.type == 0 ) { uint tmp; stream >> tmp; mapi.name.value.setValue( tmp ); } else if ( mapi.name.type == 1 ) { mapi.name.value.setValue( readMAPIString( stream, true ) ); } } int n = 1; QVariant value; if ( ISVECTOR( mapi ) ) { stream >> n; mapi.value = QList(); } for ( int i=0; i> d; value.setValue( d & 0x0000FFFF ); break; case MAPI_TYPE_BOOLEAN: case MAPI_TYPE_ULONG: { uint tmp; stream >> tmp; value.setValue( tmp ); } break; case MAPI_TYPE_FLOAT: // FIXME: Don't we have to set the value here stream >> d; break; case MAPI_TYPE_DOUBLE: { double tmp; stream >> tmp; value.setValue( tmp ); } break; case MAPI_TYPE_TIME: { quint32 lowB, highB; stream >> lowB >> highB; value = formatTime( lowB, highB ); } break; case MAPI_TYPE_USTRING: case MAPI_TYPE_STRING8: // in case of a vector'ed value, the number of elements // has already been read in the upper for-loop if ( ISVECTOR( mapi ) ) { d = 1; } else { stream >> d; } for ( uint i=0; i> d; } for ( uint i=0; i> len; value = QByteArray( len, '\0' ); if ( len > 0 ) { int fullLen = len; ALIGN( fullLen, 4 ); stream.readRawData( value.toByteArray().data(), len ); quint8 c; for ( int i=len; i> c; } // FIXME: Shouldn't we do something with the value??? } } break; default: mapi.type = MAPI_TYPE_NONE; break; } if ( ISVECTOR( mapi ) ) { QList lst = mapi.value.toList(); lst << value; mapi.value.setValue( lst ); } else { mapi.value = value; } } return mapi.tag; } //@endcond bool KTNEFParser::ParserPrivate::readMAPIProperties( QMap & props, KTNEFAttach *attach ) { quint32 n; MAPI_value mapi; KTNEFProperty *p; QMap::ConstIterator it; bool foundAttachment = false; // some initializations mapi.type = MAPI_TYPE_NONE; mapi.value.clear(); // get number of properties stream_ >> n; kDebug() << "MAPI Properties:" << n; for ( uint i=0; isetDisplayName( "Embedded Message" ); kDebug() << "MAPI Embedded Message: size=" << data.size(); } device_->seek( device_->pos() + ( len-4 ) ); break; } else if ( mapi.type == MAPI_TYPE_BINARY && attach && attach->offset() < 0 ) { foundAttachment = true; int len = mapi.value.toByteArray().size(); ALIGN( len, 4 ); attach->setSize( len ); attach->setOffset( device_->pos() - len ); attach->addAttribute( attATTACHDATA, atpBYTE, QString( "< size=%1 >" ).arg( len ), false ); } } kDebug() << "MAPI data: size=" << mapi.value.toByteArray().size(); break; default: { QString mapiname = ""; if ( mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE ) { if ( mapi.name.type == 0 ) { mapiname = QString().sprintf( " [name = 0x%04x]", mapi.name.value.toUInt() ); } else { mapiname = QString( " [name = %1]" ).arg( mapi.name.value.toString() ); } } switch ( mapi.type & 0x0FFF ) { case MAPI_TYPE_UINT16: kDebug().nospace() << "(tag=" << hex << mapi.tag << ") MAPI short" << mapiname.toLatin1().data() << ":" << hex << mapi.value.toUInt(); break; case MAPI_TYPE_ULONG: kDebug().nospace() << "(tag=" << hex << mapi.tag << ") MAPI long" << mapiname.toLatin1().data() << ":" << hex << mapi.value.toUInt(); break; case MAPI_TYPE_BOOLEAN: kDebug().nospace() << "(tag=" << hex << mapi.tag << ") MAPI boolean" << mapiname.toLatin1().data() << ":" << mapi.value.toBool(); break; case MAPI_TYPE_TIME: kDebug().nospace() << "(tag=" << hex << mapi.tag << ") MAPI time" << mapiname.toLatin1().data() << ":" << mapi.value.toString().toLatin1().data(); break; case MAPI_TYPE_USTRING: case MAPI_TYPE_STRING8: kDebug().nospace() << "(tag=" << hex << mapi.tag << ") MAPI string" << mapiname.toLatin1().data() << ":size=" << mapi.value.toByteArray().size() << mapi.value.toString(); break; case MAPI_TYPE_BINARY: kDebug().nospace() << "(tag=" << hex << mapi.tag << ") MAPI binary" << mapiname.toLatin1().data() << ":size=" << mapi.value.toByteArray().size(); break; } } break; } // do not remove potential existing similar entry if ( ( it = props.constFind( key ) ) == props.constEnd() ) { p = new KTNEFProperty( key, ( mapi.type & 0x0FFF ), mapi.value, mapi.name.value ); props[ p->key() ] = p; } //kDebug() << "stream:" << device_->pos(); } if ( foundAttachment && attach ) { attach->setIndex( attach->property( MAPI_TAG_INDEX ).toUInt() ); attach->setDisplaySize( attach->property( MAPI_TAG_SIZE ).toUInt() ); QString str = attach->property( MAPI_TAG_DISPLAYNAME ).toString(); if ( !str.isEmpty() ) { attach->setDisplayName( str ); } attach->setFileName( attach->property( MAPI_TAG_FILENAME ).toString() ); str = attach->property( MAPI_TAG_MIMETAG ).toString(); if ( !str.isEmpty() ) { attach->setMimeTag( str ); } attach->setExtension( attach->property( MAPI_TAG_EXTENSION ).toString() ); if ( attach->name().isEmpty() ) { attach->setName( attach->fileName() ); } } return true; }