|
|
|
/*
|
|
|
|
kmime_content.cpp
|
|
|
|
|
|
|
|
KMime, the KDE internet mail/usenet news message library.
|
|
|
|
Copyright (c) 2001 the KMime authors.
|
|
|
|
See file AUTHORS for details
|
|
|
|
|
|
|
|
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.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software Foundation,
|
|
|
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US
|
|
|
|
*/
|
|
|
|
#include "kmime_content.h"
|
|
|
|
#include "kmime_parsers.h"
|
|
|
|
|
|
|
|
#include <kcharsets.h>
|
|
|
|
#include <kmdcodec.h>
|
|
|
|
#include <kglobal.h>
|
|
|
|
#include <klocale.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
|
|
|
|
#include <tqtextcodec.h>
|
|
|
|
|
|
|
|
using namespace KMime;
|
|
|
|
|
|
|
|
namespace KMime {
|
|
|
|
|
|
|
|
Content::Content()
|
|
|
|
: c_ontents(0), h_eaders(0), f_orceDefaultCS(false)
|
|
|
|
{
|
|
|
|
d_efaultCS = cachedCharset("ISO-8859-1");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Content::Content(const TQCString &h, const TQCString &b)
|
|
|
|
: c_ontents(0), h_eaders(0), f_orceDefaultCS(false)
|
|
|
|
{
|
|
|
|
d_efaultCS = cachedCharset("ISO-8859-1");
|
|
|
|
h_ead=h.copy();
|
|
|
|
b_ody=b.copy();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Content::~Content()
|
|
|
|
{
|
|
|
|
delete c_ontents;
|
|
|
|
delete h_eaders;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Content::setContent(TQStrList *l)
|
|
|
|
{
|
|
|
|
//tqDebug("Content::setContent(TQStrList *l) : start");
|
|
|
|
h_ead.resize(0);
|
|
|
|
b_ody.resize(0);
|
|
|
|
|
|
|
|
//usage of textstreams is much faster than simply appending the strings
|
|
|
|
TQTextStream hts(h_ead, IO_WriteOnly),
|
|
|
|
bts(b_ody, IO_WriteOnly);
|
|
|
|
hts.setEncoding(TQTextStream::Latin1);
|
|
|
|
bts.setEncoding(TQTextStream::Latin1);
|
|
|
|
|
|
|
|
bool isHead=true;
|
|
|
|
for(char *line=l->first(); line; line=l->next()) {
|
|
|
|
if(isHead && line[0]=='\0') {
|
|
|
|
isHead=false;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if(isHead)
|
|
|
|
hts << line << "\n";
|
|
|
|
else
|
|
|
|
bts << line << "\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
//terminate strings
|
|
|
|
hts << '\0';
|
|
|
|
bts << '\0';
|
|
|
|
|
|
|
|
//tqDebug("Content::setContent(TQStrList *l) : finished");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Content::setContent(const TQCString &s)
|
|
|
|
{
|
|
|
|
int pos=s.find("\n\n", 0);
|
|
|
|
if(pos>-1) {
|
|
|
|
h_ead=s.left(++pos); //header *must* end with "\n" !!
|
|
|
|
b_ody=s.mid(pos+1, s.length()-pos-1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
h_ead=s;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//parse the message, split multiple parts
|
|
|
|
void Content::parse()
|
|
|
|
{
|
|
|
|
//tqDebug("void Content::parse() : start");
|
|
|
|
delete h_eaders;
|
|
|
|
h_eaders=0;
|
|
|
|
|
|
|
|
// check this part has already been partioned into subparts.
|
|
|
|
// if this is the case, we will not try to reparse the body
|
|
|
|
// of this part.
|
|
|
|
if ((b_ody.size() == 0) && (c_ontents != 0) && !c_ontents->isEmpty()) {
|
|
|
|
// reparse all sub parts
|
|
|
|
for(Content *c=c_ontents->first(); c; c=c_ontents->next())
|
|
|
|
c->parse();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
delete c_ontents;
|
|
|
|
c_ontents=0;
|
|
|
|
|
|
|
|
Headers::ContentType *ct=contentType();
|
|
|
|
TQCString tmp;
|
|
|
|
Content *c;
|
|
|
|
Headers::contentCategory cat;
|
|
|
|
|
|
|
|
// just "text" as mimetype is suspicious, perhaps this article was
|
|
|
|
// generated by broken software, better check for uuencoded binaries
|
|
|
|
if (ct->mimeType()=="text")
|
|
|
|
ct->setMimeType("invalid/invalid");
|
|
|
|
|
|
|
|
if(ct->isText())
|
|
|
|
return; //nothing to do
|
|
|
|
|
|
|
|
if(ct->isMultipart()) { //this is a multipart message
|
|
|
|
tmp=ct->boundary(); //get boundary-parameter
|
|
|
|
|
|
|
|
if(!tmp.isEmpty()) {
|
|
|
|
Parser::MultiPart mpp(b_ody, tmp);
|
|
|
|
if(mpp.parse()) { //at least one part found
|
|
|
|
|
|
|
|
c_ontents=new List();
|
|
|
|
c_ontents->setAutoDelete(true);
|
|
|
|
|
|
|
|
if(ct->isSubtype("alternative")) //examine category for the sub-parts
|
|
|
|
cat=Headers::CCalternativePart;
|
|
|
|
else
|
|
|
|
cat=Headers::CCmixedPart; //default to "mixed"
|
|
|
|
|
|
|
|
QCStringList parts=mpp.parts();
|
|
|
|
QCStringList::Iterator it;
|
|
|
|
for(it=parts.begin(); it!=parts.end(); ++it) { //create a new Content for every part
|
|
|
|
c=new Content();
|
|
|
|
c->setContent(*it);
|
|
|
|
c->parse();
|
|
|
|
c->contentType()->setCategory(cat); //set category of the sub-part
|
|
|
|
c_ontents->append(c);
|
|
|
|
//tqDebug("part:\n%s\n\n%s", c->h_ead.data(), c->b_ody.left(100).data());
|
|
|
|
}
|
|
|
|
|
|
|
|
//the whole content is now split into single parts, so it's safe delete the message-body
|
|
|
|
b_ody.resize(0);
|
|
|
|
}
|
|
|
|
else { //sh*t, the parsing failed so we have to treat the message as "text/plain" instead
|
|
|
|
ct->setMimeType("text/plain");
|
|
|
|
ct->setCharset("US-ASCII");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (ct->mimeType()=="invalid/invalid") { //non-mime body => check for uuencoded content
|
|
|
|
Parser::UUEncoded uup(b_ody, rawHeader("Subject"));
|
|
|
|
|
|
|
|
if(uup.parse()) { // yep, it is uuencoded
|
|
|
|
|
|
|
|
if(uup.isPartial()) { // this seems to be only a part of the message so we treat it as "message/partial"
|
|
|
|
ct->setMimeType("message/partial");
|
|
|
|
//ct->setId(uniqueString()); not needed yet
|
|
|
|
ct->setPartialParams(uup.partialCount(), uup.partialNumber());
|
|
|
|
contentTransferEncoding()->setCte(Headers::CE7Bit);
|
|
|
|
}
|
|
|
|
else { //it's a complete message => treat as "multipart/mixed"
|
|
|
|
//the whole content is now split into single parts, so it's safe to delete the message-body
|
|
|
|
b_ody.resize(0);
|
|
|
|
|
|
|
|
//binary parts
|
|
|
|
for (unsigned int i=0;i<uup.binaryParts().count();i++) {
|
|
|
|
c=new Content();
|
|
|
|
//generate content with mime-compliant headers
|
|
|
|
tmp="Content-Type: ";
|
|
|
|
tmp += uup.mimeTypes().at(i);
|
|
|
|
tmp += "; name=\"";
|
|
|
|
tmp += uup.filenames().at(i);
|
|
|
|
tmp += "\"\nContent-Transfer-Encoding: x-uuencode\nContent-Disposition: attachment; filename=\"";
|
|
|
|
tmp += uup.filenames().at(i);
|
|
|
|
tmp += "\"\n\n";
|
|
|
|
tmp += uup.binaryParts().at(i);
|
|
|
|
c->setContent(tmp);
|
|
|
|
addContent(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(c_ontents && c_ontents->first()) { //readd the plain text before the uuencoded part
|
|
|
|
c_ontents->first()->setContent("Content-Type: text/plain\nContent-Transfer-Encoding: 7Bit\n\n"+uup.textPart());
|
|
|
|
c_ontents->first()->contentType()->setMimeType("text/plain");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Parser::YENCEncoded yenc(b_ody);
|
|
|
|
|
|
|
|
if ( yenc.parse()) {
|
|
|
|
/* If it is partial, just assume there is exactly one decoded part,
|
|
|
|
* and make this that part */
|
|
|
|
if (yenc.isPartial()) {
|
|
|
|
ct->setMimeType("message/partial");
|
|
|
|
//ct->setId(uniqueString()); not needed yet
|
|
|
|
ct->setPartialParams(yenc.partialCount(), yenc.partialNumber());
|
|
|
|
contentTransferEncoding()->setCte(Headers::CEbinary);
|
|
|
|
}
|
|
|
|
else { //it's a complete message => treat as "multipart/mixed"
|
|
|
|
//the whole content is now split into single parts, so it's safe to delete the message-body
|
|
|
|
b_ody.resize(0);
|
|
|
|
|
|
|
|
//binary parts
|
|
|
|
for (unsigned int i=0;i<yenc.binaryParts().count();i++) {
|
|
|
|
c=new Content();
|
|
|
|
//generate content with mime-compliant headers
|
|
|
|
tmp="Content-Type: ";
|
|
|
|
tmp += yenc.mimeTypes().at(i);
|
|
|
|
tmp += "; name=\"";
|
|
|
|
tmp += yenc.filenames().at(i);
|
|
|
|
tmp += "\"\nContent-Transfer-Encoding: binary\nContent-Disposition: attachment; filename=\"";
|
|
|
|
tmp += yenc.filenames().at(i);
|
|
|
|
tmp += "\"\n\n";
|
|
|
|
c->setContent(tmp);
|
|
|
|
|
|
|
|
// the bodies of yenc message parts are binary data, not null-terminated strings:
|
|
|
|
TQByteArray body = yenc.binaryParts()[i];
|
|
|
|
TQCString body_string(body.size());
|
|
|
|
memcpy(body_string.data(), body.data(), body.size());
|
|
|
|
c->setBody(body_string);
|
|
|
|
|
|
|
|
addContent(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(c_ontents && c_ontents->first()) { //readd the plain text before the uuencoded part
|
|
|
|
c_ontents->first()->setContent("Content-Type: text/plain\nContent-Transfer-Encoding: 7Bit\n\n"+yenc.textPart());
|
|
|
|
c_ontents->first()->contentType()->setMimeType("text/plain");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else { //no, this doesn't look like uuencoded stuff => we treat it as "text/plain"
|
|
|
|
ct->setMimeType("text/plain");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//tqDebug("void Content::parse() : finished");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Content::assemble()
|
|
|
|
{
|
|
|
|
TQCString newHead="";
|
|
|
|
|
|
|
|
//Content-Type
|
|
|
|
newHead+=contentType()->as7BitString()+"\n";
|
|
|
|
|
|
|
|
//Content-Transfer-Encoding
|
|
|
|
newHead+=contentTransferEncoding()->as7BitString()+"\n";
|
|
|
|
|
|
|
|
//Content-Description
|
|
|
|
Headers::Base *h=contentDescription(false);
|
|
|
|
if(h)
|
|
|
|
newHead+=h->as7BitString()+"\n";
|
|
|
|
|
|
|
|
//Content-Disposition
|
|
|
|
h=contentDisposition(false);
|
|
|
|
if(h)
|
|
|
|
newHead+=h->as7BitString()+"\n";
|
|
|
|
|
|
|
|
h_ead=newHead;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Content::clear()
|
|
|
|
{
|
|
|
|
delete h_eaders;
|
|
|
|
h_eaders=0;
|
|
|
|
delete c_ontents;
|
|
|
|
c_ontents=0;
|
|
|
|
h_ead.resize(0);
|
|
|
|
b_ody.resize(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TQCString Content::encodedContent(bool useCrLf)
|
|
|
|
{
|
|
|
|
TQCString e;
|
|
|
|
|
|
|
|
// hack to convert articles with uuencoded or yencoded binaries into
|
|
|
|
// proper mime-compliant articles
|
|
|
|
if(c_ontents && !c_ontents->isEmpty()) {
|
|
|
|
bool convertNonMimeBinaries=false;
|
|
|
|
|
|
|
|
// reencode non-mime binaries...
|
|
|
|
for(Content *c=c_ontents->first(); c; c=c_ontents->next()) {
|
|
|
|
if ((c->contentTransferEncoding(true)->cte()==Headers::CEuuenc) ||
|
|
|
|
(c->contentTransferEncoding(true)->cte()==Headers::CEbinary)) {
|
|
|
|
convertNonMimeBinaries=true;
|
|
|
|
c->b_ody = KCodecs::base64Encode(c->decodedContent(), true);
|
|
|
|
c->b_ody.append("\n");
|
|
|
|
c->contentTransferEncoding(true)->setCte(Headers::CEbase64);
|
|
|
|
c->contentTransferEncoding(true)->setDecoded(false);
|
|
|
|
c->removeHeader("Content-Description");
|
|
|
|
c->assemble();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// add proper mime headers...
|
|
|
|
if (convertNonMimeBinaries) {
|
|
|
|
h_ead.replace(TQRegExp("MIME-Version: .*\\n"),"");
|
|
|
|
h_ead.replace(TQRegExp("Content-Type: .*\\n"),"");
|
|
|
|
h_ead.replace(TQRegExp("Content-Transfer-Encoding: .*\\n"),"");
|
|
|
|
h_ead+="MIME-Version: 1.0\n";
|
|
|
|
h_ead+=contentType(true)->as7BitString()+"\n";
|
|
|
|
h_ead+=contentTransferEncoding(true)->as7BitString()+"\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//head
|
|
|
|
e=h_ead.copy();
|
|
|
|
e+="\n";
|
|
|
|
|
|
|
|
//body
|
|
|
|
if(!b_ody.isEmpty()) { //this message contains only one part
|
|
|
|
Headers::CTEncoding *enc=contentTransferEncoding();
|
|
|
|
|
|
|
|
if(enc->needToEncode()) {
|
|
|
|
if(enc->cte()==Headers::CEquPr) {
|
|
|
|
TQByteArray temp(b_ody.length());
|
|
|
|
memcpy(temp.data(), b_ody.data(), b_ody.length());
|
|
|
|
e+=KCodecs::quotedPrintableEncode(temp, false);
|
|
|
|
} else {
|
|
|
|
e+=KCodecs::base64Encode(b_ody, true);
|
|
|
|
e+="\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
e+=b_ody;
|
|
|
|
}
|
|
|
|
else if(c_ontents && !c_ontents->isEmpty()) { //this is a multipart message
|
|
|
|
Headers::ContentType *ct=contentType();
|
|
|
|
TQCString boundary="\n--"+ct->boundary();
|
|
|
|
|
|
|
|
//add all (encoded) contents separated by boundaries
|
|
|
|
for(Content *c=c_ontents->first(); c; c=c_ontents->next()) {
|
|
|
|
e+=boundary+"\n";
|
|
|
|
e+=c->encodedContent(false); // don't convert LFs here, we do that later!!!!!
|
|
|
|
}
|
|
|
|
//finally append the closing boundary
|
|
|
|
e+=boundary+"--\n";
|
|
|
|
};
|
|
|
|
|
|
|
|
if(useCrLf)
|
|
|
|
return LFtoCRLF(e);
|
|
|
|
else
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TQByteArray Content::decodedContent()
|
|
|
|
{
|
|
|
|
TQByteArray temp, ret;
|
|
|
|
Headers::CTEncoding *ec=contentTransferEncoding();
|
|
|
|
bool removeTrailingNewline=false;
|
|
|
|
int size=ec->cte()==Headers::CEbinary ? b_ody.size() : b_ody.length();
|
|
|
|
|
|
|
|
if (size==0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
temp.resize(size);
|
|
|
|
memcpy(temp.data(), b_ody.data(), size);
|
|
|
|
|
|
|
|
if(ec->decoded()) {
|
|
|
|
ret = temp;
|
|
|
|
removeTrailingNewline=true;
|
|
|
|
} else {
|
|
|
|
switch(ec->cte()) {
|
|
|
|
case Headers::CEbase64 :
|
|
|
|
KCodecs::base64Decode(temp, ret);
|
|
|
|
break;
|
|
|
|
case Headers::CEquPr :
|
|
|
|
ret = KCodecs::quotedPrintableDecode(b_ody);
|
|
|
|
ret.resize(ret.size()-1); // remove null-char
|
|
|
|
removeTrailingNewline=true;
|
|
|
|
break;
|
|
|
|
case Headers::CEuuenc :
|
|
|
|
KCodecs::uudecode(temp, ret);
|
|
|
|
break;
|
|
|
|
case Headers::CEbinary :
|
|
|
|
ret = temp;
|
|
|
|
removeTrailingNewline=false;
|
|
|
|
break;
|
|
|
|
default :
|
|
|
|
ret = temp;
|
|
|
|
removeTrailingNewline=true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (removeTrailingNewline && (ret.size()>0) && (ret[ret.size()-1] == '\n'))
|
|
|
|
ret.resize(ret.size()-1);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Content::decodedText(TQString &s, bool trimText,
|
|
|
|
bool removeTrailingNewlines)
|
|
|
|
{
|
|
|
|
if(!decodeText()) //this is not a text content !!
|
|
|
|
return;
|
|
|
|
|
|
|
|
bool ok=true;
|
|
|
|
TQTextCodec *codec=TDEGlobal::charsets()->codecForName(contentType()->charset(),ok);
|
|
|
|
|
|
|
|
s=codec->toUnicode(b_ody.data(), b_ody.length());
|
|
|
|
|
|
|
|
if (trimText && removeTrailingNewlines) {
|
|
|
|
int i;
|
|
|
|
for (i=s.length()-1; i>=0; i--)
|
|
|
|
if (!s[i].isSpace())
|
|
|
|
break;
|
|
|
|
s.truncate(i+1);
|
|
|
|
} else {
|
|
|
|
if (s.right(1)=="\n")
|
|
|
|
s.truncate(s.length()-1); // remove trailing new-line
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Content::decodedText(TQStringList &l, bool trimText,
|
|
|
|
bool removeTrailingNewlines)
|
|
|
|
{
|
|
|
|
if(!decodeText()) //this is not a text content !!
|
|
|
|
return;
|
|
|
|
|
|
|
|
TQString unicode;
|
|
|
|
bool ok=true;
|
|
|
|
|
|
|
|
TQTextCodec *codec=TDEGlobal::charsets()->codecForName(contentType()->charset(),ok);
|
|
|
|
|
|
|
|
unicode=codec->toUnicode(b_ody.data(), b_ody.length());
|
|
|
|
|
|
|
|
if (trimText && removeTrailingNewlines) {
|
|
|
|
int i;
|
|
|
|
for (i=unicode.length()-1; i>=0; i--)
|
|
|
|
if (!unicode[i].isSpace())
|
|
|
|
break;
|
|
|
|
unicode.truncate(i+1);
|
|
|
|
} else {
|
|
|
|
if (unicode.right(1)=="\n")
|
|
|
|
unicode.truncate(unicode.length()-1); // remove trailing new-line
|
|
|
|
}
|
|
|
|
|
|
|
|
l=TQStringList::split('\n', unicode, true); //split the string at linebreaks
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Content::fromUnicodeString(const TQString &s)
|
|
|
|
{
|
|
|
|
bool ok=true;
|
|
|
|
TQTextCodec *codec=TDEGlobal::charsets()->codecForName(contentType()->charset(),ok);
|
|
|
|
|
|
|
|
if(!ok) { // no suitable codec found => try local settings and hope the best ;-)
|
|
|
|
codec=TDEGlobal::locale()->codecForEncoding();
|
|
|
|
TQCString chset=TDEGlobal::locale()->encoding();
|
|
|
|
contentType()->setCharset(chset);
|
|
|
|
}
|
|
|
|
|
|
|
|
b_ody=codec->fromUnicode(s);
|
|
|
|
contentTransferEncoding()->setDecoded(true); //text is always decoded
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Content* Content::textContent()
|
|
|
|
{
|
|
|
|
Content *ret=0;
|
|
|
|
|
|
|
|
//return the first content with mimetype=text/*
|
|
|
|
if(contentType()->isText())
|
|
|
|
ret=this;
|
|
|
|
else if(c_ontents)
|
|
|
|
for(Content *c=c_ontents->first(); c; c=c_ontents->next())
|
|
|
|
if( (ret=c->textContent())!=0 )
|
|
|
|
break;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Content::attachments(Content::List *dst, bool incAlternatives)
|
|
|
|
{
|
|
|
|
dst->setAutoDelete(false); //don't delete the contents
|
|
|
|
|
|
|
|
if(!c_ontents)
|
|
|
|
dst->append(this);
|
|
|
|
else {
|
|
|
|
for(Content *c=c_ontents->first(); c; c=c_ontents->next()) {
|
|
|
|
if( !incAlternatives && c->contentType()->category()==Headers::CCalternativePart)
|
|
|
|
continue;
|
|
|
|
else
|
|
|
|
c->attachments(dst, incAlternatives);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(type()!=ATmimeContent) { // this is the toplevel article
|
|
|
|
Content *text=textContent();
|
|
|
|
if(text)
|
|
|
|
dst->removeRef(text);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Content::addContent(Content *c, bool prepend)
|
|
|
|
{
|
|
|
|
if(!c_ontents) { // this message is not multipart yet
|
|
|
|
c_ontents=new List();
|
|
|
|
c_ontents->setAutoDelete(true);
|
|
|
|
|
|
|
|
// first we convert the body to a content
|
|
|
|
Content *main=new Content();
|
|
|
|
|
|
|
|
//the Mime-Headers are needed, so we move them to the new content
|
|
|
|
if(h_eaders) {
|
|
|
|
|
|
|
|
main->h_eaders=new Headers::Base::List();
|
|
|
|
main->h_eaders->setAutoDelete(true);
|
|
|
|
|
|
|
|
Headers::Base::List srcHdrs=(*h_eaders);
|
|
|
|
srcHdrs.setAutoDelete(false);
|
|
|
|
int idx=0;
|
|
|
|
for(Headers::Base *h=srcHdrs.first(); h; h=srcHdrs.next()) {
|
|
|
|
if(h->isMimeHeader()) {
|
|
|
|
//remove from this content
|
|
|
|
idx=h_eaders->findRef(h);
|
|
|
|
h_eaders->take(idx);
|
|
|
|
//append to new content
|
|
|
|
main->h_eaders->append(h);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//"main" is now part of a multipart/mixed message
|
|
|
|
main->contentType()->setCategory(Headers::CCmixedPart);
|
|
|
|
|
|
|
|
//the head of "main" is empty, so we assemble it
|
|
|
|
main->assemble();
|
|
|
|
|
|
|
|
//now we can copy the body and append the new content;
|
|
|
|
main->b_ody=b_ody.copy();
|
|
|
|
c_ontents->append(main);
|
|
|
|
b_ody.resize(0); //not longer needed
|
|
|
|
|
|
|
|
|
|
|
|
//finally we have to convert this article to "multipart/mixed"
|
|
|
|
Headers::ContentType *ct=contentType();
|
|
|
|
ct->setMimeType("multipart/mixed");
|
|
|
|
ct->setBoundary(multiPartBoundary());
|
|
|
|
ct->setCategory(Headers::CCcontainer);
|
|
|
|
contentTransferEncoding()->clear(); // 7Bit, decoded
|
|
|
|
|
|
|
|
}
|
|
|
|
//here we actually add the content
|
|
|
|
if(prepend)
|
|
|
|
c_ontents->insert(0, c);
|
|
|
|
else
|
|
|
|
c_ontents->append(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Content::removeContent(Content *c, bool del)
|
|
|
|
{
|
|
|
|
if(!c_ontents) // what the ..
|
|
|
|
return;
|
|
|
|
|
|
|
|
int idx=0;
|
|
|
|
if(del)
|
|
|
|
c_ontents->removeRef(c);
|
|
|
|
else {
|
|
|
|
idx=c_ontents->findRef(c);
|
|
|
|
c_ontents->take(idx);
|
|
|
|
}
|
|
|
|
|
|
|
|
//only one content left => turn this message in a single-part
|
|
|
|
if(c_ontents->count()==1) {
|
|
|
|
Content *main=c_ontents->first();
|
|
|
|
|
|
|
|
//first we have to move the mime-headers
|
|
|
|
if(main->h_eaders) {
|
|
|
|
if(!h_eaders) {
|
|
|
|
h_eaders=new Headers::Base::List();
|
|
|
|
h_eaders->setAutoDelete(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
Headers::Base::List mainHdrs=(*(main->h_eaders));
|
|
|
|
mainHdrs.setAutoDelete(false);
|
|
|
|
|
|
|
|
for(Headers::Base *h=mainHdrs.first(); h; h=mainHdrs.next()) {
|
|
|
|
if(h->isMimeHeader()) {
|
|
|
|
removeHeader(h->type()); //remove the old header first
|
|
|
|
h_eaders->append(h); //now append the new one
|
|
|
|
idx=main->h_eaders->findRef(h);
|
|
|
|
main->h_eaders->take(idx); //remove from the old content
|
|
|
|
kdDebug(5003) << "Content::removeContent(Content *c, bool del) : mime-header moved: "
|
|
|
|
<< h->as7BitString() << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//now we can copy the body
|
|
|
|
b_ody=main->b_ody.copy();
|
|
|
|
|
|
|
|
//finally we can delete the content list
|
|
|
|
delete c_ontents;
|
|
|
|
c_ontents=0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Content::changeEncoding(Headers::contentEncoding e)
|
|
|
|
{
|
|
|
|
Headers::CTEncoding *enc=contentTransferEncoding();
|
|
|
|
if(enc->cte()==e) //nothing to do
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(decodeText())
|
|
|
|
enc->setCte(e); // text is not encoded until it's sent or saved so we just set the new encoding
|
|
|
|
else { // this content contains non textual data, that has to be re-encoded
|
|
|
|
|
|
|
|
if(e!=Headers::CEbase64) {
|
|
|
|
//kdWarning(5003) << "Content::changeEncoding() : non textual data and encoding != base64 - this should not happen\n => forcing base64" << endl;
|
|
|
|
e=Headers::CEbase64;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(enc->cte()!=e) { // ok, we reencode the content using base64
|
|
|
|
b_ody = KCodecs::base64Encode(decodedContent(), true);
|
|
|
|
b_ody.append("\n");
|
|
|
|
enc->setCte(e); //set encoding
|
|
|
|
enc->setDecoded(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Content::toStream(TQTextStream &ts, bool scrambleFromLines)
|
|
|
|
{
|
|
|
|
TQCString ret=encodedContent(false);
|
|
|
|
|
|
|
|
if (scrambleFromLines)
|
|
|
|
ret.replace(TQRegExp("\\n\\nFrom "), "\n\n>From ");
|
|
|
|
|
|
|
|
ts << ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Headers::Generic* Content::getNextHeader(TQCString &head)
|
|
|
|
{
|
|
|
|
int pos1=-1, pos2=0, len=head.length()-1;
|
|
|
|
bool folded(false);
|
|
|
|
Headers::Generic *header=0;
|
|
|
|
|
|
|
|
pos1 = head.find(": ");
|
|
|
|
|
|
|
|
if (pos1>-1) { //there is another header
|
|
|
|
pos2=pos1+=2; //skip the name
|
|
|
|
|
|
|
|
if (head[pos2]!='\n') { // check if the header is not empty
|
|
|
|
while(1) {
|
|
|
|
pos2=head.find("\n", pos2+1);
|
|
|
|
if(pos2==-1 || pos2==len || ( head[pos2+1]!=' ' && head[pos2+1]!='\t') ) //break if we reach the end of the string, honor folded lines
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
folded = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(pos2<0) pos2=len+1; //take the rest of the string
|
|
|
|
|
|
|
|
if (!folded)
|
|
|
|
header = new Headers::Generic(head.left(pos1-2), this, head.mid(pos1, pos2-pos1));
|
|
|
|
else
|
|
|
|
header = new Headers::Generic(head.left(pos1-2), this, head.mid(pos1, pos2-pos1).replace(TQRegExp("\\s*\\n\\s*")," "));
|
|
|
|
|
|
|
|
head.remove(0,pos2+1);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
head = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
return header;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Headers::Base* Content::getHeaderByType(const char *type)
|
|
|
|
{
|
|
|
|
if(!type)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
Headers::Base *h=0;
|
|
|
|
//first we check if the requested header is already cached
|
|
|
|
if(h_eaders)
|
|
|
|
for(h=h_eaders->first(); h; h=h_eaders->next())
|
|
|
|
if(h->is(type)) return h; //found
|
|
|
|
|
|
|
|
//now we look for it in the article head
|
|
|
|
TQCString raw=rawHeader(type);
|
|
|
|
if(!raw.isEmpty()) { //ok, we found it
|
|
|
|
//choose a suitable header class
|
|
|
|
if(strcasecmp("Message-Id", type)==0)
|
|
|
|
h=new Headers::MessageID(this, raw);
|
|
|
|
else if(strcasecmp("Subject", type)==0)
|
|
|
|
h=new Headers::Subject(this, raw);
|
|
|
|
else if(strcasecmp("Date", type)==0)
|
|
|
|
h=new Headers::Date(this, raw);
|
|
|
|
else if(strcasecmp("From", type)==0)
|
|
|
|
h=new Headers::From(this, raw);
|
|
|
|
else if(strcasecmp("Organization", type)==0)
|
|
|
|
h=new Headers::Organization(this, raw);
|
|
|
|
else if(strcasecmp("Reply-To", type)==0)
|
|
|
|
h=new Headers::ReplyTo(this, raw);
|
|
|
|
else if(strcasecmp("Mail-Copies-To", type)==0)
|
|
|
|
h=new Headers::MailCopiesTo(this, raw);
|
|
|
|
else if(strcasecmp("To", type)==0)
|
|
|
|
h=new Headers::To(this, raw);
|
|
|
|
else if(strcasecmp("CC", type)==0)
|
|
|
|
h=new Headers::CC(this, raw);
|
|
|
|
else if(strcasecmp("BCC", type)==0)
|
|
|
|
h=new Headers::BCC(this, raw);
|
|
|
|
else if(strcasecmp("Newsgroups", type)==0)
|
|
|
|
h=new Headers::Newsgroups(this, raw);
|
|
|
|
else if(strcasecmp("Followup-To", type)==0)
|
|
|
|
h=new Headers::FollowUpTo(this, raw);
|
|
|
|
else if(strcasecmp("References", type)==0)
|
|
|
|
h=new Headers::References(this, raw);
|
|
|
|
else if(strcasecmp("Lines", type)==0)
|
|
|
|
h=new Headers::Lines(this, raw);
|
|
|
|
else if(strcasecmp("Content-Type", type)==0)
|
|
|
|
h=new Headers::ContentType(this, raw);
|
|
|
|
else if(strcasecmp("Content-Transfer-Encoding", type)==0)
|
|
|
|
h=new Headers::CTEncoding(this, raw);
|
|
|
|
else if(strcasecmp("Content-Disposition", type)==0)
|
|
|
|
h=new Headers::CDisposition(this, raw);
|
|
|
|
else if(strcasecmp("Content-Description", type)==0)
|
|
|
|
h=new Headers::CDescription(this, raw);
|
|
|
|
else
|
|
|
|
h=new Headers::Generic(type, this, raw);
|
|
|
|
|
|
|
|
if(!h_eaders) {
|
|
|
|
h_eaders=new Headers::Base::List();
|
|
|
|
h_eaders->setAutoDelete(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
h_eaders->append(h); //add to cache
|
|
|
|
return h;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return 0; //header not found
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Content::setHeader(Headers::Base *h)
|
|
|
|
{
|
|
|
|
if(!h) return;
|
|
|
|
removeHeader(h->type());
|
|
|
|
if(!h_eaders) {
|
|
|
|
h_eaders=new Headers::Base::List();
|
|
|
|
h_eaders->setAutoDelete(true);
|
|
|
|
}
|
|
|
|
h_eaders->append(h);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Content::removeHeader(const char *type)
|
|
|
|
{
|
|
|
|
if(h_eaders)
|
|
|
|
for(Headers::Base *h=h_eaders->first(); h; h=h_eaders->next())
|
|
|
|
if(h->is(type))
|
|
|
|
return h_eaders->remove();
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int Content::size()
|
|
|
|
{
|
|
|
|
int ret=b_ody.length();
|
|
|
|
|
|
|
|
if(contentTransferEncoding()->cte()==Headers::CEbase64)
|
|
|
|
return (ret*3/4); //base64 => 6 bit per byte
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int Content::storageSize()
|
|
|
|
{
|
|
|
|
int s=h_ead.size();
|
|
|
|
|
|
|
|
if(!c_ontents)
|
|
|
|
s+=b_ody.size();
|
|
|
|
else {
|
|
|
|
for(Content *c=c_ontents->first(); c; c=c_ontents->next())
|
|
|
|
s+=c->storageSize();
|
|
|
|
}
|
|
|
|
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int Content::lineCount()
|
|
|
|
{
|
|
|
|
int ret=0;
|
|
|
|
if(type()==ATmimeContent)
|
|
|
|
ret+=h_ead.contains('\n');
|
|
|
|
ret+=b_ody.contains('\n');
|
|
|
|
|
|
|
|
if(c_ontents && !c_ontents->isEmpty())
|
|
|
|
for(Content *c=c_ontents->first(); c; c=c_ontents->next())
|
|
|
|
ret+=c->lineCount();
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TQCString Content::rawHeader(const char *name)
|
|
|
|
{
|
|
|
|
return extractHeader(h_ead, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Content::decodeText()
|
|
|
|
{
|
|
|
|
Headers::CTEncoding *enc=contentTransferEncoding();
|
|
|
|
|
|
|
|
if(!contentType()->isText())
|
|
|
|
return false; //non textual data cannot be decoded here => use decodedContent() instead
|
|
|
|
if(enc->decoded())
|
|
|
|
return true; //nothing to do
|
|
|
|
|
|
|
|
switch(enc->cte()) {
|
|
|
|
case Headers::CEbase64 :
|
|
|
|
b_ody=KCodecs::base64Decode(b_ody);
|
|
|
|
b_ody.append("\n");
|
|
|
|
break;
|
|
|
|
case Headers::CEquPr :
|
|
|
|
b_ody=KCodecs::quotedPrintableDecode(b_ody);
|
|
|
|
break;
|
|
|
|
case Headers::CEuuenc :
|
|
|
|
b_ody=KCodecs::uudecode(b_ody);
|
|
|
|
b_ody.append("\n");
|
|
|
|
break;
|
|
|
|
case Headers::CEbinary :
|
|
|
|
b_ody=TQCString(b_ody.data(), b_ody.size()+1);
|
|
|
|
b_ody.append("\n");
|
|
|
|
default :
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
enc->setDecoded(true);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Content::setDefaultCharset(const TQCString &cs)
|
|
|
|
{
|
|
|
|
d_efaultCS = KMime::cachedCharset(cs);
|
|
|
|
|
|
|
|
if(c_ontents && !c_ontents->isEmpty())
|
|
|
|
for(Content *c=c_ontents->first(); c; c=c_ontents->next())
|
|
|
|
c->setDefaultCharset(cs);
|
|
|
|
|
|
|
|
// reparse the part and its sub-parts in order
|
|
|
|
// to clear cached header values
|
|
|
|
parse();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Content::setForceDefaultCS(bool b)
|
|
|
|
{
|
|
|
|
f_orceDefaultCS=b;
|
|
|
|
|
|
|
|
if(c_ontents && !c_ontents->isEmpty())
|
|
|
|
for(Content *c=c_ontents->first(); c; c=c_ontents->next())
|
|
|
|
c->setForceDefaultCS(b);
|
|
|
|
|
|
|
|
// reparse the part and its sub-parts in order
|
|
|
|
// to clear cached header values
|
|
|
|
parse();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace KMime
|