You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdepim/kioslaves/imap4/mimeheader.cc

746 lines
20 KiB

/***************************************************************************
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 <tqregexp.h>
// #include <iostream.h>
#include <kglobal.h>
#include <kinstance.h>
#include <kiconloader.h>
#include <kmimetype.h>
#include <kmimemagic.h>
#include <kmdcodec.h>
#include <kdebug.h>
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> mimeHeader::getAllParts()
{
TQPtrList<mimeHeader> 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 (qstrnicmp (addLine->getLabel (), "Content-", 8))
{
additionalHdrLines.append (addLine);
}
else
{
int skip;
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 (!qstricmp (addLine->getLabel (), "Content-Disposition"))
{
aList = &dispositionList;
_contentDisposition = mimeValue;
}
else if (!qstricmp (addLine->getLabel (), "Content-Type"))
{
aList = &typeList;
contentType = mimeValue;
}
else
if (!qstricmp (addLine->getLabel (), "Content-Transfer-Encoding"))
{
contentEncoding = mimeValue;
}
else if (!qstricmp (addLine->getLabel (), "Content-ID"))
{
contentID = mimeValue;
}
else if (!qstricmp (addLine->getLabel (), "Content-Description"))
{
_contentDescription = mimeValue;
}
else if (!qstricmp (addLine->getLabel (), "Content-MD5"))
{
contentMD5 = mimeValue;
}
else if (!qstricmp (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.tqfind ('=');
// 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->tqfind (aStr);
if (!found)
{
//might be a continuated or encoded parameter
found = aDict->tqfind (aStr + "*");
if (!found)
{
//continuated parameter
TQString decoded, encoded;
int part = 0;
do
{
TQCString search;
search.setNum (part);
search = aStr + "*" + search;
found = aDict->tqfind (search);
if (!found)
{
found = aDict->tqfind (search + "*");
if (found)
encoded += rfcDecoder::encodeRFC2231String (*found);
}
else
{
encoded += *found;
}
part++;
}
while (found);
if (encoded.tqfind ('\'') >= 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.tqfind ('*') == -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 ()->tqfind (' ') > 0 || it.current ()->tqfind (';') > 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 (!qstrnicmp (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 (!qstrnicmp (getType (), "Multipart/Digest", 16))
aHeader->setType ("Message/RFC822");
localRetVal = aHeader->parsePart (useIO, getTypeParm ("boundary"));
addNestedPart (aHeader);
}
while (localRetVal); //get nested stuff
}
if (!qstrnicmp (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 ()
&& !qstrnicmp (inputStr, partEnd.latin1 (), partEnd.length () - 1))
{
retVal = 0; //end of these parts
break;
}
else if (!partBoundary.isEmpty ()
&& !qstrnicmp (inputStr, partBoundary.latin1 (),
partBoundary.length () - 1))
{
retVal = 1; //continue with next part
break;
}
else if (mbox && inputStr.tqfind ("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.tqfind ("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.tqfind('.');
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::tqfromLatin1 (temp.data (), temp.count ());
}
TQByteArray
mimeHeader::bodyDecodedBinary ()
{
TQByteArray retVal;
if (contentEncoding.tqfind ("quoted-printable", 0, false) == 0)
retVal = KCodecs::quotedPrintableDecode(postMultipartBody);
else if (contentEncoding.tqfind ("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.tqfind ("quoted-printable", 0, false) == 0)
setVal = KCodecs::quotedPrintableEncode(_arr);
else if (contentEncoding.tqfind ("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 =
KGlobal::instance ()->iconLoader ()->iconPath (fileName, KIcon::Desktop);
// if (fileName.isEmpty())
// fileName = KGlobal::instance()->iconLoader()->iconPath( "unknown", KIcon::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