/*************************************************************************** mimeheader.cc - description ------------------- begin : Fri Oct 20 2000 copyright : (C) 2000 by Sven Carstens email : s.carstens@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mimeheader.h" #include "mimehdrline.h" #include "mailheader.h" #include "rfcdecoder.h" #include // #include #include #include #include #include #include #include #include mimeHeader::mimeHeader (): typeList (17, false), dispositionList (17, false) { // Case insensitive hashes are killing us. Also are they too small? originalHdrLines.setAutoDelete (true); additionalHdrLines.setAutoDelete (false); // is also in original lines nestedParts.setAutoDelete (true); typeList.setAutoDelete (true); dispositionList.setAutoDelete (true); nestedMessage = NULL; contentLength = 0; contentType = "application/octet-stream"; } mimeHeader::~mimeHeader () { } /* TQPtrList mimeHeader::getAllParts() { TQPtrList retVal; // caller is responsible for clearing retVal.setAutoDelete( false ); nestedParts.setAutoDelete( false ); // shallow copy retVal = nestedParts; // can't have duplicate pointers nestedParts.clear(); // restore initial state nestedParts.setAutoDelete( true ); return retVal; } */ void mimeHeader::addHdrLine (mimeHdrLine * aHdrLine) { mimeHdrLine *addLine = new mimeHdrLine (aHdrLine); if (addLine) { originalHdrLines.append (addLine); if (tqstrnicmp (addLine->getLabel (), "Content-", 8)) { additionalHdrLines.append (addLine); } else { int skip; const char *aCStr = addLine->getValue ().data (); TQDict < TQString > *aList = 0; skip = mimeHdrLine::parseSeparator (';', aCStr); if (skip > 0) { int cut = 0; if (skip >= 2) { if (aCStr[skip - 1] == '\r') cut++; if (aCStr[skip - 1] == '\n') cut++; if (aCStr[skip - 2] == '\r') cut++; if (aCStr[skip - 1] == ';') cut++; } TQCString mimeValue = TQCString (aCStr, skip - cut + 1); // cutting of one because of 0x00 if (!tqstricmp (addLine->getLabel (), "Content-Disposition")) { aList = &dispositionList; _contentDisposition = mimeValue; } else if (!tqstricmp (addLine->getLabel (), "Content-Type")) { aList = &typeList; contentType = mimeValue; } else if (!tqstricmp (addLine->getLabel (), "Content-Transfer-Encoding")) { contentEncoding = mimeValue; } else if (!tqstricmp (addLine->getLabel (), "Content-ID")) { contentID = mimeValue; } else if (!tqstricmp (addLine->getLabel (), "Content-Description")) { _contentDescription = mimeValue; } else if (!tqstricmp (addLine->getLabel (), "Content-MD5")) { contentMD5 = mimeValue; } else if (!tqstricmp (addLine->getLabel (), "Content-Length")) { contentLength = mimeValue.toULong (); } else { additionalHdrLines.append (addLine); } // cout << addLine->getLabel().data() << ": '" << mimeValue.data() << "'" << endl; aCStr += skip; while ((skip = mimeHdrLine::parseSeparator (';', aCStr))) { if (skip > 0) { addParameter (TQCString (aCStr, skip).simplifyWhiteSpace(), aList); // cout << "-- '" << aParm.data() << "'" << endl; mimeValue = TQCString (addLine->getValue ().data (), skip); aCStr += skip; } else break; } } } } } void mimeHeader::addParameter (const TQCString& aParameter, TQDict < TQString > *aList) { if ( !aList ) return; TQString *aValue; TQCString aLabel; int pos = aParameter.find ('='); // cout << aParameter.left(pos).data(); aValue = new TQString (); aValue->setLatin1 (aParameter.right (aParameter.length () - pos - 1)); aLabel = aParameter.left (pos); if ((*aValue)[0] == '"') *aValue = aValue->mid (1, aValue->length () - 2); aList->insert (aLabel, aValue); // cout << "=" << aValue->data() << endl; } TQString mimeHeader::getDispositionParm (const TQCString& aStr) { return getParameter (aStr, &dispositionList); } TQString mimeHeader::getTypeParm (const TQCString& aStr) { return getParameter (aStr, &typeList); } void mimeHeader::setDispositionParm (const TQCString& aLabel, const TQString& aValue) { setParameter (aLabel, aValue, &dispositionList); return; } void mimeHeader::setTypeParm (const TQCString& aLabel, const TQString& aValue) { setParameter (aLabel, aValue, &typeList); } TQDictIterator < TQString > mimeHeader::getDispositionIterator () { return TQDictIterator < TQString > (dispositionList); } TQDictIterator < TQString > mimeHeader::getTypeIterator () { return TQDictIterator < TQString > (typeList); } TQPtrListIterator < mimeHdrLine > mimeHeader::getOriginalIterator () { return TQPtrListIterator < mimeHdrLine > (originalHdrLines); } TQPtrListIterator < mimeHdrLine > mimeHeader::getAdditionalIterator () { return TQPtrListIterator < mimeHdrLine > (additionalHdrLines); } void mimeHeader::outputHeader (mimeIO & useIO) { if (!getDisposition ().isEmpty ()) { useIO.outputMimeLine (TQCString ("Content-Disposition: ") + getDisposition () + outputParameter (&dispositionList)); } if (!getType ().isEmpty ()) { useIO.outputMimeLine (TQCString ("Content-Type: ") + getType () + outputParameter (&typeList)); } if (!getDescription ().isEmpty ()) useIO.outputMimeLine (TQCString ("Content-Description: ") + getDescription ()); if (!getID ().isEmpty ()) useIO.outputMimeLine (TQCString ("Content-ID: ") + getID ()); if (!getMD5 ().isEmpty ()) useIO.outputMimeLine (TQCString ("Content-MD5: ") + getMD5 ()); if (!getEncoding ().isEmpty ()) useIO.outputMimeLine (TQCString ("Content-Transfer-Encoding: ") + getEncoding ()); TQPtrListIterator < mimeHdrLine > ait = getAdditionalIterator (); while (ait.current ()) { useIO.outputMimeLine (ait.current ()->getLabel () + ": " + ait.current ()->getValue ()); ++ait; } useIO.outputMimeLine (TQCString ("")); } TQString mimeHeader::getParameter (const TQCString& aStr, TQDict < TQString > *aDict) { TQString retVal, *found; if (aDict) { //see if it is a normal parameter found = aDict->find (aStr); if (!found) { //might be a continuated or encoded parameter found = aDict->find (aStr + "*"); if (!found) { //continuated parameter TQString decoded, encoded; int part = 0; do { TQCString search; search.setNum (part); search = aStr + "*" + search; found = aDict->find (search); if (!found) { found = aDict->find (search + "*"); if (found) encoded += rfcDecoder::encodeRFC2231String (*found); } else { encoded += *found; } part++; } while (found); if (encoded.find ('\'') >= 0) { retVal = rfcDecoder::decodeRFC2231String (encoded.local8Bit ()); } else { retVal = rfcDecoder::decodeRFC2231String (TQCString ("''") + encoded.local8Bit ()); } } else { //simple encoded parameter retVal = rfcDecoder::decodeRFC2231String (found->local8Bit ()); } } else { retVal = *found; } } return retVal; } void mimeHeader::setParameter (const TQCString& aLabel, const TQString& aValue, TQDict < TQString > *aDict) { bool encoded = true; uint vlen, llen; TQString val = aValue; if (aDict) { //see if it needs to get encoded if (encoded && aLabel.find ('*') == -1) { val = rfcDecoder::encodeRFC2231String (aValue); } //kdDebug(7116) << "mimeHeader::setParameter() - val = '" << val << "'" << endl; //see if it needs to be truncated vlen = val.length(); llen = aLabel.length(); if (vlen + llen + 4 > 80 && llen < 80 - 8 - 2 ) { const int limit = 80 - 8 - 2 - (int)llen; // the -2 is there to allow extending the length of a part of val // by 1 or 2 in order to prevent an encoded character from being // split in half int i = 0; TQString shortValue; TQCString shortLabel; while (!val.isEmpty ()) { int partLen; // the length of the next part of the value if ( limit >= int(vlen) ) { // the rest of the value fits completely into one continued header partLen = vlen; } else { partLen = limit; // make sure that we don't split an encoded char in half if ( val[partLen-1] == '%' ) { partLen += 2; } else if ( partLen > 1 && val[partLen-2] == '%' ) { partLen += 1; } // make sure partLen does not exceed vlen (could happen in case of // an incomplete encoded char) if ( partLen > int(vlen) ) { partLen = vlen; } } shortValue = val.left( partLen ); shortLabel.setNum (i); shortLabel = aLabel + "*" + shortLabel; val = val.right( vlen - partLen ); vlen = vlen - partLen; if (encoded) { if (i == 0) { shortValue = "''" + shortValue; } shortLabel += "*"; } //kdDebug(7116) << "mimeHeader::setParameter() - shortLabel = '" << shortLabel << "'" << endl; //kdDebug(7116) << "mimeHeader::setParameter() - shortValue = '" << shortValue << "'" << endl; //kdDebug(7116) << "mimeHeader::setParameter() - val = '" << val << "'" << endl; aDict->insert (shortLabel, new TQString (shortValue)); i++; } } else { aDict->insert (aLabel, new TQString (val)); } } } TQCString mimeHeader::outputParameter (TQDict < TQString > *aDict) { TQCString retVal; if (aDict) { TQDictIterator < TQString > it (*aDict); while (it.current ()) { retVal += (";\n\t" + it.currentKey () + "=").latin1 (); if (it.current ()->find (' ') > 0 || it.current ()->find (';') > 0) { retVal += '"' + it.current ()->utf8 () + '"'; } else { retVal += it.current ()->utf8 (); } // << it.current()->utf8() << "'"; ++it; } retVal += "\n"; } return retVal; } void mimeHeader::outputPart (mimeIO & useIO) { TQPtrListIterator < mimeHeader > nestedParts = getNestedIterator (); TQCString boundary; if (!getTypeParm ("boundary").isEmpty ()) boundary = getTypeParm ("boundary").latin1 (); outputHeader (useIO); if (!getPreBody ().isEmpty ()) useIO.outputMimeLine (getPreBody ()); if (getNestedMessage ()) getNestedMessage ()->outputPart (useIO); while (nestedParts.current ()) { if (!boundary.isEmpty ()) useIO.outputMimeLine ("--" + boundary); nestedParts.current ()->outputPart (useIO); ++nestedParts; } if (!boundary.isEmpty ()) useIO.outputMimeLine ("--" + boundary + "--"); if (!getPostBody ().isEmpty ()) useIO.outputMimeLine (getPostBody ()); } int mimeHeader::parsePart (mimeIO & useIO, const TQString& boundary) { int retVal = 0; bool mbox = false; TQCString preNested, postNested; mbox = parseHeader (useIO); kdDebug(7116) << "mimeHeader::parsePart - parsing part '" << getType () << "'" << endl; if (!tqstrnicmp (getType (), "Multipart", 9)) { retVal = parseBody (useIO, preNested, getTypeParm ("boundary")); //this is a message in mime format stuff setPreBody (preNested); int localRetVal; do { mimeHeader *aHeader = new mimeHeader; // set default type for multipart/digest if (!tqstrnicmp (getType (), "Multipart/Digest", 16)) aHeader->setType ("Message/RFC822"); localRetVal = aHeader->parsePart (useIO, getTypeParm ("boundary")); addNestedPart (aHeader); } while (localRetVal); //get nested stuff } if (!tqstrnicmp (getType (), "Message/RFC822", 14)) { mailHeader *msgHeader = new mailHeader; retVal = msgHeader->parsePart (useIO, boundary); setNestedMessage (msgHeader); } else { retVal = parseBody (useIO, postNested, boundary, mbox); //just a simple part remaining setPostBody (postNested); } return retVal; } int mimeHeader::parseBody (mimeIO & useIO, TQCString & messageBody, const TQString& boundary, bool mbox) { TQCString inputStr; TQCString buffer; TQString partBoundary; TQString partEnd; int retVal = 0; //default is last part if (!boundary.isEmpty ()) { partBoundary = TQString ("--") + boundary; partEnd = TQString ("--") + boundary + "--"; } while (useIO.inputLine (inputStr)) { //check for the end of all parts if (!partEnd.isEmpty () && !tqstrnicmp (inputStr, partEnd.latin1 (), partEnd.length () - 1)) { retVal = 0; //end of these parts break; } else if (!partBoundary.isEmpty () && !tqstrnicmp (inputStr, partBoundary.latin1 (), partBoundary.length () - 1)) { retVal = 1; //continue with next part break; } else if (mbox && inputStr.find ("From ") == 0) { retVal = 0; // end of mbox break; } buffer += inputStr; if (buffer.length () > 16384) { messageBody += buffer; buffer = ""; } } messageBody += buffer; return retVal; } bool mimeHeader::parseHeader (mimeIO & useIO) { bool mbox = false; bool first = true; mimeHdrLine my_line; TQCString inputStr; kdDebug(7116) << "mimeHeader::parseHeader - starting parsing" << endl; while (useIO.inputLine (inputStr)) { int appended; if (inputStr.find ("From ") != 0 || !first) { first = false; appended = my_line.appendStr (inputStr); if (!appended) { addHdrLine (&my_line); appended = my_line.setStr (inputStr); } if (appended <= 0) break; } else { mbox = true; first = false; } inputStr = (const char *) NULL; } kdDebug(7116) << "mimeHeader::parseHeader - finished parsing" << endl; return mbox; } mimeHeader * mimeHeader::bodyPart (const TQString & _str) { // see if it is nested a little deeper int pt = _str.find('.'); if (pt != -1) { TQString tempStr = _str; mimeHeader *tempPart; tempStr = _str.right (_str.length () - pt - 1); if (nestedMessage) { kdDebug(7116) << "mimeHeader::bodyPart - recursing message" << endl; tempPart = nestedMessage->nestedParts.at (_str.left(pt).toULong() - 1); } else { kdDebug(7116) << "mimeHeader::bodyPart - recursing mixed" << endl; tempPart = nestedParts.at (_str.left(pt).toULong() - 1); } if (tempPart) tempPart = tempPart->bodyPart (tempStr); return tempPart; } kdDebug(7116) << "mimeHeader::bodyPart - returning part " << _str << endl; // or pick just the plain part if (nestedMessage) { kdDebug(7116) << "mimeHeader::bodyPart - message" << endl; return nestedMessage->nestedParts.at (_str.toULong () - 1); } kdDebug(7116) << "mimeHeader::bodyPart - mixed" << endl; return nestedParts.at (_str.toULong () - 1); } void mimeHeader::serialize(TQDataStream& stream) { int nestedcount = nestedParts.count(); if (nestedParts.isEmpty() && nestedMessage) nestedcount = 1; stream << nestedcount << contentType << TQString (getTypeParm ("name")) << _contentDescription << _contentDisposition << contentEncoding << contentLength << partSpecifier; // serialize nested message if (nestedMessage) nestedMessage->serialize(stream); // serialize nested parts if (!nestedParts.isEmpty()) { TQPtrListIterator < mimeHeader > it(nestedParts); mimeHeader* part; while ( (part = it.current()) != 0 ) { ++it; part->serialize(stream); } } } #ifdef KMAIL_COMPATIBLE // compatibility subroutines TQString mimeHeader::bodyDecoded () { kdDebug(7116) << "mimeHeader::bodyDecoded" << endl; TQByteArray temp; temp = bodyDecodedBinary (); return TQString::fromLatin1 (temp.data (), temp.count ()); } TQByteArray mimeHeader::bodyDecodedBinary () { TQByteArray retVal; if (contentEncoding.find ("quoted-printable", 0, false) == 0) retVal = KCodecs::quotedPrintableDecode(postMultipartBody); else if (contentEncoding.find ("base64", 0, false) == 0) KCodecs::base64Decode(postMultipartBody, retVal); else retVal = postMultipartBody; kdDebug(7116) << "mimeHeader::bodyDecodedBinary - size is " << retVal.size () << endl; return retVal; } void mimeHeader::setBodyEncodedBinary (const TQByteArray & _arr) { setBodyEncoded (_arr); } void mimeHeader::setBodyEncoded (const TQByteArray & _arr) { TQByteArray setVal; kdDebug(7116) << "mimeHeader::setBodyEncoded - in size " << _arr.size () << endl; if (contentEncoding.find ("quoted-printable", 0, false) == 0) setVal = KCodecs::quotedPrintableEncode(_arr); else if (contentEncoding.find ("base64", 0, false) == 0) KCodecs::base64Encode(_arr, setVal); else setVal.duplicate (_arr); kdDebug(7116) << "mimeHeader::setBodyEncoded - out size " << setVal.size () << endl; postMultipartBody.duplicate (setVal); kdDebug(7116) << "mimeHeader::setBodyEncoded - out size " << postMultipartBody.size () << endl; } TQString mimeHeader::iconName () { TQString fileName; // FIXME: bug? Why throw away this data? fileName = KMimeType::mimeType (contentType.lower ())->icon (TQString(), false); fileName = TDEGlobal::instance ()->iconLoader ()->iconPath (fileName, TDEIcon::Desktop); // if (fileName.isEmpty()) // fileName = TDEGlobal::instance()->iconLoader()->iconPath( "unknown", TDEIcon::Desktop ); return fileName; } void mimeHeader::setNestedMessage (mailHeader * inPart, bool destroy) { // if(nestedMessage && destroy) delete nestedMessage; nestedMessage = inPart; } TQString mimeHeader::headerAsString () { mimeIOTQString myIO; outputHeader (myIO); return myIO.getString (); } TQString mimeHeader::magicSetType (bool aAutoDecode) { TQString mimetype; TQByteArray body; KMimeMagicResult *result; KMimeMagic::self ()->setFollowLinks (TRUE); // is it necessary ? if (aAutoDecode) body = bodyDecodedBinary (); else body = postMultipartBody; result = KMimeMagic::self ()->findBufferType (body); mimetype = result->mimeType (); contentType = mimetype; return mimetype; } #endif