kmail

kmmsgpart.cpp

00001 // kmmsgpart.cpp
00002 
00003 #include <config.h>
00004 #include <kmimemagic.h>
00005 #include <kmimetype.h>
00006 #include <kdebug.h>
00007 #include <kmdcodec.h>
00008 
00009 #include "kmmsgpart.h"
00010 #include "kmkernel.h"
00011 #include "kmmessage.h"
00012 #include "globalsettings.h"
00013 #include "util.h"
00014 
00015 #include <kasciistringtools.h>
00016 #include <kmime_charfreq.h>
00017 #include <kmime_codecs.h>
00018 #include <mimelib/enum.h>
00019 #include <mimelib/utility.h>
00020 #include <mimelib/string.h>
00021 
00022 #include <kiconloader.h>
00023 #include <qtextcodec.h>
00024 
00025 #include <assert.h>
00026 
00027 using namespace KMime;
00028 
00029 //-----------------------------------------------------------------------------
00030 KMMessagePart::KMMessagePart()
00031   : mType("text"), mSubtype("plain"), mCte("7bit"), mBodyDecodedSize(0),
00032     mParent(0), mLoadHeaders(false), mLoadPart(false)
00033 {
00034 }
00035 
00036 //-----------------------------------------------------------------------------
00037 KMMessagePart::KMMessagePart( QDataStream & stream )
00038   : mParent(0), mLoadHeaders(false), mLoadPart(false)
00039 {
00040   unsigned long size;
00041   stream >> mOriginalContentTypeStr >> mName >> mContentDescription
00042     >> mContentDisposition >> mCte >> size >> mPartSpecifier;
00043 
00044   KPIM::kAsciiToLower( mContentDisposition.data() );
00045   KPIM::kAsciiToUpper( mOriginalContentTypeStr.data() );
00046 
00047   // set the type
00048   int sep = mOriginalContentTypeStr.find('/');
00049   mType = mOriginalContentTypeStr.left(sep);
00050   mSubtype = mOriginalContentTypeStr.mid(sep+1);
00051 
00052   mBodyDecodedSize = size;
00053 }
00054 
00055 
00056 //-----------------------------------------------------------------------------
00057 KMMessagePart::~KMMessagePart()
00058 {
00059 }
00060 
00061 
00062 //-----------------------------------------------------------------------------
00063 void KMMessagePart::clear()
00064 {
00065   mOriginalContentTypeStr = QCString();
00066   mType = "text";
00067   mSubtype = "plain";
00068   mCte = "7bit";
00069   mContentDescription = QCString();
00070   mContentDisposition = QCString();
00071   mBody.truncate( 0 );
00072   mAdditionalCTypeParamStr = QCString();
00073   mName = QString::null;
00074   mParameterAttribute = QCString();
00075   mParameterValue = QString::null;
00076   mCharset = QCString();
00077   mPartSpecifier = QString::null;
00078   mBodyDecodedSize = 0;
00079   mParent = 0;
00080   mLoadHeaders = false;
00081   mLoadPart = false;
00082 }
00083 
00084 
00085 //-----------------------------------------------------------------------------
00086 void KMMessagePart::duplicate( const KMMessagePart & msgPart )
00087 {
00088   // copy the data of msgPart
00089   *this = msgPart;
00090   // detach the explicitely shared QByteArray
00091   mBody.detach();
00092 }
00093 
00094 //-----------------------------------------------------------------------------
00095 int KMMessagePart::decodedSize(void) const
00096 {
00097   if (mBodyDecodedSize < 0)
00098     mBodyDecodedSize = bodyDecodedBinary().size();
00099   return mBodyDecodedSize;
00100 }
00101 
00102 
00103 //-----------------------------------------------------------------------------
00104 void KMMessagePart::setBody(const QCString &aStr)
00105 {
00106   KMail::Util::setFromQCString( mBody, aStr );
00107 
00108   int enc = cte();
00109   if (enc == DwMime::kCte7bit || enc == DwMime::kCte8bit || enc == DwMime::kCteBinary)
00110     mBodyDecodedSize = mBody.size();
00111   else
00112     mBodyDecodedSize = -1; // Can't know the decoded size
00113 }
00114 
00115 void KMMessagePart::setBody(const DwString &aStr)
00116 {
00117   mBody.duplicate( aStr.c_str(), aStr.length() );
00118 
00119   int enc = cte();
00120   if (enc == DwMime::kCte7bit || enc == DwMime::kCte8bit || enc == DwMime::kCteBinary)
00121     mBodyDecodedSize = mBody.size();
00122   else
00123     mBodyDecodedSize = -1; // Can't know the decoded size
00124 }
00125 
00126 void KMMessagePart::setBody(const QByteArray &aStr)
00127 {
00128   mBody = aStr;
00129 
00130   int enc = cte();
00131   if (enc == DwMime::kCte7bit || enc == DwMime::kCte8bit || enc == DwMime::kCteBinary)
00132     mBodyDecodedSize = mBody.size();
00133   else
00134     mBodyDecodedSize = -1; // Can't know the decoded size
00135 }
00136 
00137 void KMMessagePart::setBodyFromUnicode( const QString & str ) {
00138   QCString encoding = KMMsgBase::autoDetectCharset( charset(), KMMessage::preferredCharsets(), str );
00139   if ( encoding.isEmpty() )
00140     encoding = "utf-8";
00141   const QTextCodec * codec = KMMsgBase::codecForName( encoding );
00142   assert( codec );
00143   QValueList<int> dummy;
00144   setCharset( encoding );
00145   setBodyAndGuessCte( codec->fromUnicode( str ), dummy, false /* no 8bit */ );
00146 }
00147 
00148 const QTextCodec * KMMessagePart::codec() const {
00149   const QTextCodec * c = KMMsgBase::codecForName( charset() );
00150 
00151   if ( !c ) {
00152     // Ok, no override and nothing in the message, let's use the fallback
00153     // the user configured
00154     c = KMMsgBase::codecForName( GlobalSettings::self()->fallbackCharacterEncoding().latin1() );
00155   }
00156   if ( !c )
00157     // no charset means us-ascii (RFC 2045), so using local encoding should
00158     // be okay
00159     c = kmkernel->networkCodec();
00160   assert( c );
00161   return c;
00162 }
00163 
00164 QString KMMessagePart::bodyToUnicode(const QTextCodec* codec) const {
00165   if ( !codec )
00166     // No codec was given, so try the charset in the mail
00167     codec = this->codec();
00168   assert( codec );
00169 
00170   return codec->toUnicode( bodyDecoded() );
00171 }
00172 
00173 void KMMessagePart::setCharset( const QCString & c ) {
00174   if ( type() != DwMime::kTypeText )
00175     kdWarning()
00176       << "KMMessagePart::setCharset(): trying to set a charset for a non-textual mimetype." << endl
00177       << "Fix this caller:" << endl
00178       << "====================================================================" << endl
00179       << kdBacktrace( 5 ) << endl
00180       << "====================================================================" << endl;
00181   mCharset = c;
00182 }
00183 
00184 //-----------------------------------------------------------------------------
00185 void KMMessagePart::setBodyEncoded(const QCString& aStr)
00186 {
00187   mBodyDecodedSize = aStr.size() - 1; // same as aStr.length() but faster - assuming no embedded nuls
00188   switch (cte())
00189   {
00190   case DwMime::kCteQuotedPrintable:
00191   case DwMime::kCteBase64:
00192     {
00193       Codec * codec = Codec::codecForName( cteStr() );
00194       assert( codec );
00195       // we can't use the convenience function here, since aStr is not
00196       // a QByteArray...:
00197       mBody.resize( codec->maxEncodedSizeFor( mBodyDecodedSize ) );
00198       QCString::ConstIterator iit = aStr.data();
00199       QCString::ConstIterator iend = aStr.data() + mBodyDecodedSize;
00200       QByteArray::Iterator oit = mBody.begin();
00201       QByteArray::ConstIterator oend = mBody.end();
00202       if ( !codec->encode( iit, iend, oit, oend ) )
00203     kdWarning(5006) << codec->name()
00204             << " codec lies about it's maxEncodedSizeFor( "
00205             << mBodyDecodedSize << " ). Result truncated!" << endl;
00206       mBody.truncate( oit - mBody.begin() );
00207       break;
00208     }
00209   default:
00210     kdWarning(5006) << "setBodyEncoded: unknown encoding '" << cteStr()
00211             << "'. Assuming binary." << endl;
00212     // fall through
00213   case DwMime::kCte7bit:
00214   case DwMime::kCte8bit:
00215   case DwMime::kCteBinary:
00216     // This is slow and memory hungry - consider using setBodyEncodedBinary instead!
00217     mBody.duplicate( aStr.data(), mBodyDecodedSize );
00218     break;
00219   }
00220 }
00221 
00222 void KMMessagePart::setBodyAndGuessCte(const QByteArray& aBuf,
00223                        QValueList<int> & allowedCte,
00224                        bool allow8Bit,
00225                                        bool willBeSigned )
00226 {
00227   mBodyDecodedSize = aBuf.size();
00228 
00229   CharFreq cf( aBuf ); // save to pass null arrays...
00230 
00231   allowedCte = KMMessage::determineAllowedCtes( cf, allow8Bit, willBeSigned );
00232 
00233 #ifndef NDEBUG
00234   DwString dwCte;
00235   DwCteEnumToStr(allowedCte[0], dwCte);
00236   kdDebug(5006) << "CharFreq returned " << cf.type() << "/"
00237         << cf.printableRatio() << " and I chose "
00238         << dwCte.c_str() << endl;
00239 #endif
00240 
00241   setCte( allowedCte[0] ); // choose best fitting
00242   setBodyEncodedBinary( aBuf );
00243 }
00244 
00245 void KMMessagePart::setBodyAndGuessCte(const QCString& aBuf,
00246                        QValueList<int> & allowedCte,
00247                        bool allow8Bit,
00248                                        bool willBeSigned )
00249 {
00250   mBodyDecodedSize = aBuf.size() - 1; // same as aStr.length() but faster - assuming no embedded nuls
00251 
00252   CharFreq cf( aBuf.data(), mBodyDecodedSize ); // save to pass null strings
00253 
00254   allowedCte = KMMessage::determineAllowedCtes( cf, allow8Bit, willBeSigned );
00255 
00256 #ifndef NDEBUG
00257   DwString dwCte;
00258   DwCteEnumToStr(allowedCte[0], dwCte);
00259   kdDebug(5006) << "CharFreq returned " << cf.type() << "/"
00260         << cf.printableRatio() << " and I chose "
00261         << dwCte.c_str() << endl;
00262 #endif
00263 
00264   setCte( allowedCte[0] ); // choose best fitting
00265   setBodyEncoded( aBuf );
00266 }
00267 
00268 //-----------------------------------------------------------------------------
00269 void KMMessagePart::setBodyEncodedBinary(const QByteArray& aStr)
00270 {
00271   mBodyDecodedSize = aStr.size();
00272   if (aStr.isEmpty())
00273   {
00274     mBody.resize(0);
00275     return;
00276   }
00277 
00278   switch (cte())
00279   {
00280   case DwMime::kCteQuotedPrintable:
00281   case DwMime::kCteBase64:
00282     {
00283       Codec * codec = Codec::codecForName( cteStr() );
00284       assert( codec );
00285       // Nice: We can use the convenience function :-)
00286       mBody = codec->encode( aStr );
00287       // QP encoding does CRLF -> LF conversion, which can change the size after decoding again
00288       // and a size mismatch triggers an assert in various other methods
00289       mBodyDecodedSize = -1;
00290       break;
00291     }
00292   default:
00293     kdWarning(5006) << "setBodyEncodedBinary: unknown encoding '" << cteStr()
00294             << "'. Assuming binary." << endl;
00295     // fall through
00296   case DwMime::kCte7bit:
00297   case DwMime::kCte8bit:
00298   case DwMime::kCteBinary:
00299     //mBody.duplicate( aStr );
00300     mBody = aStr;
00301     // Caller has to detach before it modifies aStr!
00302     break;
00303   }
00304 }
00305 
00306 void KMMessagePart::setMessageBody( const QByteArray& aBuf )
00307 {
00308   CharFreq cf( aBuf ); // it's safe to pass null arrays
00309   mBodyDecodedSize = aBuf.size();
00310 
00311   int cte;
00312   switch ( cf.type() ) {
00313   case CharFreq::SevenBitText:
00314   case CharFreq::SevenBitData:
00315      cte = DwMime::kCte7bit;
00316      break;
00317   case CharFreq::EightBitText:
00318   case CharFreq::EightBitData:
00319      cte = DwMime::kCte8bit;
00320      break;
00321   default:
00322      kdWarning(5006) << "Calling " << k_funcinfo
00323          << " with something containing neither 7 nor 8 bit text!"
00324          << " Fix this caller: " << kdBacktrace() << endl;
00325   }
00326   setCte( cte );
00327   setBodyEncodedBinary( aBuf );
00328 }
00329 
00330 //-----------------------------------------------------------------------------
00331 QByteArray KMMessagePart::bodyDecodedBinary() const
00332 {
00333   if (mBody.isEmpty()) return QByteArray();
00334   QByteArray result;
00335 
00336   switch (cte())
00337   {
00338     case DwMime::kCte7bit:
00339     case DwMime::kCte8bit:
00340     case DwMime::kCteBinary:
00341       result.duplicate(mBody);
00342       break;
00343     default:
00344       if ( const Codec * codec = Codec::codecForName( cteStr() ) )
00345         // Nice: we can use the convenience function :-)
00346         result = codec->decode( mBody );
00347       else {
00348         kdWarning(5006) << "bodyDecodedBinary: unknown encoding '" << cteStr()
00349                         << "'. Assuming binary." << endl;
00350     result.duplicate(mBody);
00351       }
00352   }
00353 
00354   assert( mBodyDecodedSize < 0
00355       || (unsigned int)mBodyDecodedSize == result.size() );
00356   if ( mBodyDecodedSize < 0 )
00357     mBodyDecodedSize = result.size(); // cache the decoded size.
00358 
00359   return result;
00360 }
00361 
00362 QCString KMMessagePart::bodyDecoded(void) const
00363 {
00364   if (mBody.isEmpty()) return QCString("");
00365   bool decodeBinary = false;
00366   QCString result;
00367   int len;
00368 
00369   switch (cte())
00370   {
00371     case DwMime::kCte7bit:
00372     case DwMime::kCte8bit:
00373     case DwMime::kCteBinary:
00374     {
00375       decodeBinary = true;
00376       break;
00377     }
00378     default:
00379       if ( const Codec * codec = Codec::codecForName( cteStr() ) ) {
00380         // We can't use the codec convenience functions, since we must
00381         // return a QCString, not a QByteArray:
00382         int bufSize = codec->maxDecodedSizeFor( mBody.size() ) + 1; // trailing NUL
00383         result.resize( bufSize );
00384         QByteArray::ConstIterator iit = mBody.begin();
00385         QCString::Iterator oit = result.begin();
00386         QCString::ConstIterator oend = result.begin() + bufSize;
00387         if ( !codec->decode( iit, mBody.end(), oit, oend ) )
00388           kdWarning(5006) << codec->name()
00389                           << " lies about it's maxDecodedSizeFor( "
00390                           << mBody.size() << " ). Result truncated!" << endl;
00391         len = oit - result.begin();
00392         result.truncate( len ); // adds trailing NUL
00393       } else {
00394         kdWarning(5006) << "bodyDecoded: unknown encoding '" << cteStr()
00395                         << "'. Assuming binary." << endl;
00396         decodeBinary = true;
00397       }
00398   }
00399 
00400   if ( decodeBinary ) {
00401     len = mBody.size();
00402     KMail::Util::setFromByteArray( result, mBody );
00403   }
00404 
00405   // Calls length -> slow
00406   //kdWarning( result.length() != (unsigned int)len, 5006 )
00407   //  << "KMMessagePart::bodyDecoded(): body is binary but used as text!" << endl;
00408 
00409   result = result.replace( "\r\n", "\n" ); // CRLF -> LF conversion
00410 
00411   assert( mBodyDecodedSize < 0 || mBodyDecodedSize == len );
00412   if ( mBodyDecodedSize < 0 )
00413     mBodyDecodedSize = len; // cache decoded size
00414 
00415   return result;
00416 }
00417 
00418 
00419 //-----------------------------------------------------------------------------
00420 void KMMessagePart::magicSetType(bool aAutoDecode)
00421 {
00422   KMimeMagic::self()->setFollowLinks( true ); // is it necessary ?
00423 
00424   const QByteArray body = ( aAutoDecode ) ? bodyDecodedBinary() : mBody ;
00425   KMimeMagicResult * result = KMimeMagic::self()->findBufferType( body );
00426 
00427   QString mimetype = result->mimeType();
00428   const int sep = mimetype.find('/');
00429   mType = mimetype.left(sep).latin1();
00430   mSubtype = mimetype.mid(sep+1).latin1();
00431 }
00432 
00433 
00434 //-----------------------------------------------------------------------------
00435 QString KMMessagePart::iconName( int size ) const
00436 {
00437   QCString mimeType( mType + "/" + mSubtype );
00438   KPIM::kAsciiToLower( mimeType.data() );
00439 
00440   QString fileName =
00441     KMimeType::mimeType( mimeType )->icon( QString::null, false );
00442   if ( fileName.isEmpty() )
00443   {
00444     fileName = this->fileName();
00445     if ( fileName.isEmpty() ) fileName = this->name();
00446     if ( !fileName.isEmpty() )
00447     {
00448       fileName = KMimeType::findByPath( "/tmp/"+fileName, 0, true )->icon( QString::null, true );
00449     }
00450   }
00451 
00452   fileName =
00453     KGlobal::instance()->iconLoader()->iconPath( fileName, size );
00454   return fileName;
00455 }
00456 
00457 
00458 //-----------------------------------------------------------------------------
00459 int KMMessagePart::type() const {
00460   return DwTypeStrToEnum(DwString(mType));
00461 }
00462 
00463 
00464 //-----------------------------------------------------------------------------
00465 void KMMessagePart::setType(int aType)
00466 {
00467   DwString dwType;
00468   DwTypeEnumToStr(aType, dwType);
00469   mType = dwType.c_str();
00470 }
00471 
00472 //-----------------------------------------------------------------------------
00473 int KMMessagePart::subtype() const {
00474   return DwSubtypeStrToEnum(DwString(mSubtype));
00475 }
00476 
00477 
00478 //-----------------------------------------------------------------------------
00479 void KMMessagePart::setSubtype(int aSubtype)
00480 {
00481   DwString dwSubtype;
00482   DwSubtypeEnumToStr(aSubtype, dwSubtype);
00483   mSubtype = dwSubtype.c_str();
00484 }
00485 
00486 //-----------------------------------------------------------------------------
00487 QCString KMMessagePart::parameterAttribute(void) const
00488 {
00489   return mParameterAttribute;
00490 }
00491 
00492 //-----------------------------------------------------------------------------
00493 QString KMMessagePart::parameterValue(void) const
00494 {
00495   return mParameterValue;
00496 }
00497 
00498 //-----------------------------------------------------------------------------
00499 void KMMessagePart::setParameter(const QCString &attribute,
00500                                  const QString &value)
00501 {
00502   mParameterAttribute = attribute;
00503   mParameterValue = value;
00504 }
00505 
00506 //-----------------------------------------------------------------------------
00507 QCString KMMessagePart::contentTransferEncodingStr(void) const
00508 {
00509   return mCte;
00510 }
00511 
00512 
00513 //-----------------------------------------------------------------------------
00514 int KMMessagePart::contentTransferEncoding(void) const
00515 {
00516   return DwCteStrToEnum(DwString(mCte));
00517 }
00518 
00519 
00520 //-----------------------------------------------------------------------------
00521 void KMMessagePart::setContentTransferEncodingStr(const QCString &aStr)
00522 {
00523     mCte = aStr;
00524 }
00525 
00526 
00527 //-----------------------------------------------------------------------------
00528 void KMMessagePart::setContentTransferEncoding(int aCte)
00529 {
00530   DwString dwCte;
00531   DwCteEnumToStr(aCte, dwCte);
00532   mCte = dwCte.c_str();
00533 
00534 }
00535 
00536 
00537 //-----------------------------------------------------------------------------
00538 QString KMMessagePart::contentDescription(void) const
00539 {
00540   return KMMsgBase::decodeRFC2047String(mContentDescription, charset());
00541 }
00542 
00543 
00544 //-----------------------------------------------------------------------------
00545 void KMMessagePart::setContentDescription(const QString &aStr)
00546 {
00547   QCString encoding = KMMsgBase::autoDetectCharset(charset(),
00548     KMMessage::preferredCharsets(), aStr);
00549   if (encoding.isEmpty()) encoding = "utf-8";
00550   mContentDescription = KMMsgBase::encodeRFC2047String(aStr, encoding);
00551 }
00552 
00553 
00554 //-----------------------------------------------------------------------------
00555 QString KMMessagePart::fileName(void) const
00556 {
00557   QCString str;
00558 
00559   // Allow for multiple filname*0, filename*1, ... params (defined by RFC 2231)
00560   // in the Content-Disposision
00561   if ( mContentDisposition.contains( "filename*", false ) ) {
00562 
00563     // It's RFC 2231 encoded, so extract the file name with the 2231 method
00564     str = KMMsgBase::extractRFC2231HeaderField( mContentDisposition, "filename" );
00565     return KMMsgBase::decodeRFC2231String(str);
00566 
00567   } else {
00568 
00569     // Standard RFC 2047-encoded
00570     // search the start of the filename
00571     int startOfFilename = mContentDisposition.find("filename=", 0, false);
00572     if (startOfFilename < 0)
00573       return QString::null;
00574     startOfFilename += 9;
00575 
00576     // search the end of the filename
00577     int endOfFilename;
00578     if ( '"' == mContentDisposition[startOfFilename] ) {
00579       startOfFilename++; // the double quote isn't part of the filename
00580       endOfFilename = mContentDisposition.find('"', startOfFilename) - 1;
00581     }
00582     else {
00583       endOfFilename = mContentDisposition.find(';', startOfFilename) - 1;
00584     }
00585     if (endOfFilename < 0)
00586       endOfFilename = 32767;
00587 
00588     const QCString str = mContentDisposition.mid(startOfFilename,
00589                                 endOfFilename-startOfFilename+1)
00590                            .stripWhiteSpace();
00591     return KMMsgBase::decodeRFC2047String(str, charset());
00592   }
00593 
00594   return QString::null;
00595 }
00596 
00597 QCString KMMessagePart::body() const
00598 {
00599   return QCString( mBody.data(), mBody.size() + 1 ); // space for trailing NUL
00600 }
00601 
00602 DwString KMMessagePart::dwBody() const
00603 {
00604   return KMail::Util::dwString( mBody );
00605 }