• Skip to content
  • Skip to link menu
KDE 4.3 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • Sitemap
  • Contact Us
 

KMIME Library

kmime_content.cpp

Go to the documentation of this file.
00001 /*
00002     kmime_content.cpp
00003 
00004     KMime, the KDE internet mail/usenet news message library.
00005     Copyright (c) 2001 the KMime authors.
00006     See file AUTHORS for details
00007     Copyright (c) 2006 Volker Krause <vkrause@kde.org>
00008 
00009     This library is free software; you can redistribute it and/or
00010     modify it under the terms of the GNU Library General Public
00011     License as published by the Free Software Foundation; either
00012     version 2 of the License, or (at your option) any later version.
00013 
00014     This library is distributed in the hope that it will be useful,
00015     but WITHOUT ANY WARRANTY; without even the implied warranty of
00016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00017     Library General Public License for more details.
00018 
00019     You should have received a copy of the GNU Library General Public License
00020     along with this library; see the file COPYING.LIB.  If not, write to
00021     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00022     Boston, MA 02110-1301, USA.
00023 */
00036 #include "kmime_content.h"
00037 #include "kmime_content_p.h"
00038 #include "kmime_parsers.h"
00039 #include "kmime_util_p.h"
00040 
00041 #include <kcharsets.h>
00042 #include <kcodecs.h>
00043 #include <kglobal.h>
00044 #include <klocale.h>
00045 #include <kdebug.h>
00046 
00047 #include <QtCore/QTextCodec>
00048 #include <QtCore/QTextStream>
00049 #include <QtCore/QByteArray>
00050 
00051 using namespace KMime;
00052 
00053 namespace KMime {
00054 
00055 Content::Content()
00056   : d_ptr( new ContentPrivate( this ) )
00057 {
00058 }
00059 
00060 Content::Content( Content *parent )
00061   : d_ptr( new ContentPrivate( this ) )
00062 {
00063   d_ptr->parent = parent;
00064 }
00065 
00066 Content::Content( const QByteArray &h, const QByteArray &b )
00067   : d_ptr( new ContentPrivate( this ) )
00068 {
00069   d_ptr->head = h;
00070   d_ptr->body = b;
00071 }
00072 
00073 Content::Content( const QByteArray &h, const QByteArray &b, Content *parent )
00074   : d_ptr( new ContentPrivate( this ) )
00075 {
00076   d_ptr->head = h;
00077   d_ptr->body = b;
00078   d_ptr->parent = parent;
00079 }
00080 
00081 Content::Content( ContentPrivate *d ) :
00082     d_ptr( d )
00083 {
00084 }
00085 
00086 Content::~Content()
00087 {
00088   qDeleteAll( h_eaders );
00089   h_eaders.clear();
00090   delete d_ptr;
00091 }
00092 
00093 bool Content::hasContent() const
00094 {
00095   return !d_ptr->head.isEmpty() || !d_ptr->body.isEmpty() || !d_ptr->contents.isEmpty();
00096 }
00097 
00098 void Content::setContent( const QList<QByteArray> &l )
00099 {
00100   Q_D(Content);
00101   //qDebug("Content::setContent( const QList<QByteArray> &l ) : start");
00102   d->head.clear();
00103   d->body.clear();
00104 
00105   //usage of textstreams is much faster than simply appending the strings
00106   QTextStream hts( &( d->head ), QIODevice::WriteOnly );
00107   QTextStream bts( &( d->body ), QIODevice::WriteOnly );
00108   hts.setCodec( "ISO 8859-1" );
00109   bts.setCodec( "ISO 8859-1" );
00110 
00111   bool isHead = true;
00112   foreach ( const QByteArray& line, l ) {
00113     if ( isHead && line.isEmpty() ) {
00114       isHead = false;
00115       continue;
00116     }
00117     if ( isHead ) {
00118       hts << line << "\n";
00119     } else {
00120       bts << line << "\n";
00121     }
00122   }
00123 
00124   //qDebug("Content::setContent( const QList<QByteArray> & l ) : finished");
00125 }
00126 
00127 void Content::setContent( const QByteArray &s )
00128 {
00129   Q_D(Content);
00130   d->head.clear();
00131   d->body.clear();
00132 
00133   // empty header
00134   if ( s.startsWith( '\n' ) ) {
00135     d->body = s.right( s.length() - 1 );
00136     return;
00137   }
00138 
00139   int pos = s.indexOf( "\n\n", 0 );
00140   if ( pos > -1 ) {
00141     d->head = s.left( ++pos );  //header *must* end with "\n" !!
00142     d->body = s.mid( pos + 1, s.length() - pos - 1 );
00143   } else {
00144     d->head = s;
00145   }
00146 }
00147 
00148 QByteArray Content::head() const
00149 {
00150   return d_ptr->head;
00151 }
00152 
00153 void Content::setHead( const QByteArray &head )
00154 {
00155   d_ptr->head = head;
00156   if ( !head.endsWith( '\n' ) )
00157     d_ptr->head += '\n';
00158 }
00159 
00160 QByteArray Content::body() const
00161 {
00162   return d_ptr->body;
00163 }
00164 
00165 void Content::setBody( const QByteArray &body )
00166 {
00167   d_ptr->body = body;
00168 }
00169 
00170 //parse the message, split multiple parts
00171 void Content::parse()
00172 {
00173   Q_D(Content);
00174   //qDebug("void Content::parse() : start");
00175   qDeleteAll( h_eaders );
00176   h_eaders.clear();
00177 
00178   // check this part has already been partioned into subparts.
00179   // if this is the case, we will not try to reparse the body
00180   // of this part.
00181   if ( d->body.size() == 0 && !d->contents.isEmpty() ) {
00182     // reparse all sub parts
00183     foreach ( Content *c, d->contents ) {
00184       c->parse();
00185     }
00186     return;
00187   }
00188 
00189   qDeleteAll( d->contents );
00190   d->contents.clear();
00191 
00192   Headers::ContentType *ct = contentType();
00193   QByteArray tmp;
00194   Content *c;
00195   Headers::contentCategory cat;
00196 
00197   // just "text" as mimetype is suspicious, perhaps this article was
00198   // generated by broken software, better check for uuencoded binaries
00199   // No mimetype content can also contains such encoded data (unless it is just
00200   // a text/plain message --that the test isText() after this block catches--)
00201   if ( ct->mimeType() == "text" || ct->isEmpty() ) {
00202     //non-mime body => check for uuencoded content
00203     Parser::UUEncoded uup( d->body, rawHeader( "Subject" ) );
00204 
00205     if ( uup.parse() ) { // yep, it is uuencoded
00206 
00207       if ( uup.isPartial() ) {
00208         //this seems to be only a part of the message so we treat
00209         //it as "message/partial"
00210         ct->setMimeType( "message/partial" );
00211         //ct->setId( uniqueString() ); not needed yet
00212         ct->setPartialParams( uup.partialCount(), uup.partialNumber() );
00213         contentTransferEncoding()->setEncoding( Headers::CE7Bit );
00214       } else { //it's a complete message => treat as "multipart/mixed"
00215         //the whole content is now split into single parts, so it's safe
00216         //to delete the message-body
00217         d->body.clear();
00218 
00219         //binary parts
00220         for ( int i = 0; i < uup.binaryParts().count(); ++i ) {
00221           c = new Content( this );
00222           //generate content with mime-compliant headers
00223           tmp = "Content-Type: ";
00224           tmp += uup.mimeTypes().at( i );
00225           tmp += "; name=\"";
00226           tmp += uup.filenames().at( i );
00227           tmp += "\"\nContent-Transfer-Encoding: x-uuencode\nContent-Disposition: attachment; filename=\"";
00228           tmp += uup.filenames().at( i );
00229           tmp += "\"\n\n";
00230           tmp += uup.binaryParts().at( i );
00231           c->setContent( tmp );
00232           addContent( c );
00233         }
00234 
00235         if ( !d->contents.isEmpty() && d->contents.first() ) {
00236           //readd the plain text before the uuencoded part
00237           d->contents.first()->setContent(
00238             "Content-Type: text/plain\nContent-Transfer-Encoding: 7Bit\n\n" +
00239             uup.textPart() );
00240           d->contents.first()->contentType()->setMimeType( "text/plain" );
00241         }
00242       }
00243     } else {
00244       Parser::YENCEncoded yenc( d->body );
00245 
00246       if ( yenc.parse() ) {
00247         /* If it is partial, just assume there is exactly one decoded part,
00248           * and make this that part */
00249         if ( yenc.isPartial() ) {
00250           ct->setMimeType( "message/partial" );
00251           //ct->setId( uniqueString() ); not needed yet
00252           ct->setPartialParams( yenc.partialCount(), yenc.partialNumber() );
00253           contentTransferEncoding()->setEncoding( Headers::CEbinary );
00254         } else { //it's a complete message => treat as "multipart/mixed"
00255           //the whole content is now split into single parts, so it's safe
00256           //to delete the message-body
00257           d->body.clear();
00258 
00259           //binary parts
00260           for ( int i=0; i<yenc.binaryParts().count(); i++ ) {
00261             c = new Content( this );
00262             //generate content with mime-compliant headers
00263             tmp = "Content-Type: ";
00264             tmp += yenc.mimeTypes().at( i );
00265             tmp += "; name=\"";
00266             tmp += yenc.filenames().at( i );
00267             tmp += "\"\nContent-Transfer-Encoding: binary\nContent-Disposition: attachment; filename=\"";
00268             tmp += yenc.filenames().at( i );
00269             tmp += "\"\n\n";
00270             c->setContent( tmp );
00271 
00272             // the bodies of yenc message parts are binary data, not null-terminated strings:
00273             c->setBody( yenc.binaryParts()[i] );
00274 
00275             addContent( c );
00276           }
00277 
00278           if ( !d->contents.isEmpty() && d->contents.first() ) {
00279             //readd the plain text before the uuencoded part
00280             d->contents.first()->setContent(
00281               "Content-Type: text/plain\nContent-Transfer-Encoding: 7Bit\n\n" +
00282               yenc.textPart() );
00283             d->contents.first()->contentType()->setMimeType( "text/plain" );
00284           }
00285         }
00286       }
00287     }
00288   }
00289 
00290 
00291   if ( ct->isText() ) { // default is text/plain
00292     return; //nothing to do
00293   }
00294 
00295 
00296   if ( ct->isMultipart() ) {   //this is a multipart message
00297     tmp = ct->boundary(); //get boundary-parameter
00298 
00299     if ( !tmp.isEmpty() ) {
00300       Parser::MultiPart mpp( d->body, tmp );
00301       if ( mpp.parse() ) { //at least one part found
00302 
00303         if ( ct->isSubtype( "alternative" ) ) { //examine category for the sub-parts
00304           cat = Headers::CCalternativePart;
00305         } else {
00306           cat = Headers::CCmixedPart;  //default to "mixed"
00307         }
00308 
00309         QList<QByteArray> parts = mpp.parts();
00310         QList<QByteArray>::Iterator it;
00311         for ( it=parts.begin(); it != parts.end(); ++it ) {
00312           //create a new Content for every part
00313           c = new Content( this );
00314           c->setContent( *it );
00315           c->parse();
00316           c->contentType()->setCategory( cat ); //set category of the sub-part
00317           d->contents.append( c );
00318           //qDebug("part:\n%s\n\n%s", c->h_ead.data(), c->b_ody.left(100).data());
00319         }
00320 
00321         //the whole content is now split into single parts, so it's safe delete the message-body
00322         d->body.clear();
00323       } else { //sh*t, the parsing failed so we have to treat the message as "text/plain" instead
00324         ct->setMimeType( "text/plain" );
00325         ct->setCharset( "US-ASCII" );
00326       }
00327     }
00328   }
00329 }
00330 
00331 void Content::assemble()
00332 {
00333   Q_D(Content);
00334   QByteArray newHead = assembleHeaders();
00335   foreach ( Headers::Base *h, h_eaders ) {
00336     if ( h->isXHeader() ) {
00337       newHead += h->as7BitString() + '\n';
00338       KMime::removeHeader( d->head, h->type() );
00339     }
00340   }
00341   newHead += d->head; // keep unparsed headers
00342   d->head = newHead;
00343 
00344   foreach ( Content *c, contents() ) {
00345     c->assemble();
00346   }
00347 }
00348 
00349 QByteArray Content::assembleHeaders()
00350 {
00351   Q_D(Content);
00352   QByteArray newHead;
00353 
00354   //Content-Type
00355   Headers::Base *h = contentType( false );
00356   if ( h && !h->isEmpty() ) {
00357     newHead += contentType()->as7BitString() + '\n';
00358     KMime::removeHeader( d->head, h->type() );
00359   }
00360 
00361   //Content-Transfer-Encoding
00362   h = contentTransferEncoding( false );
00363   if ( h && !h->isEmpty() ) {
00364     newHead += contentTransferEncoding()->as7BitString() + '\n';
00365     KMime::removeHeader( d->head, h->type() );
00366   }
00367 
00368   //Content-Description
00369   h = contentDescription( false );
00370   if ( h ) {
00371     newHead += h->as7BitString() + '\n';
00372     KMime::removeHeader( d->head, h->type() );
00373   }
00374 
00375   //Content-Disposition
00376   h = contentDisposition( false );
00377   if ( h ) {
00378     newHead += h->as7BitString() + '\n';
00379     KMime::removeHeader( d->head, h->type() );
00380   }
00381 
00382   return newHead;
00383 }
00384 
00385 void Content::clear()
00386 {
00387   Q_D(Content);
00388   qDeleteAll( h_eaders );
00389   h_eaders.clear();
00390   qDeleteAll( d->contents );
00391   d->contents.clear();
00392   d->head.clear();
00393   d->body.clear();
00394 }
00395 
00396 QByteArray Content::encodedContent( bool useCrLf )
00397 {
00398   Q_D(Content);
00399   QByteArray e;
00400 
00401   // hack to convert articles with uuencoded or yencoded binaries into
00402   // proper mime-compliant articles
00403   if ( !d->contents.isEmpty() ) {
00404     bool convertNonMimeBinaries=false;
00405 
00406     // reencode non-mime binaries...
00407     foreach ( Content *c, d->contents ) {
00408       if ( ( c->contentTransferEncoding( true )->encoding() == Headers::CEuuenc ) ||
00409            ( c->contentTransferEncoding( true )->encoding() == Headers::CEbinary ) ) {
00410         convertNonMimeBinaries = true;
00411         c->setBody( KCodecs::base64Encode( c->decodedContent(), true ) + '\n' );
00412         c->contentTransferEncoding( true )->setEncoding(Headers::CEbase64);
00413         c->contentTransferEncoding( true )->setDecoded( false );
00414         c->removeHeader("Content-Description");
00415         c->assemble();
00416       }
00417     }
00418 
00419     // add proper mime headers...
00420     if ( convertNonMimeBinaries ) {
00421       int beg = 0, end = 0;
00422       beg = d->head.indexOf( "MIME-Version: " );
00423       if ( beg >= 0 ) {
00424         end = d->head.indexOf( '\n', beg );
00425       }
00426       if ( beg >= 0 && end > beg ) {
00427         d->head.remove( beg, end - beg );
00428       }
00429       beg = d->head.indexOf( "Content-Type: " );
00430       if ( beg >= 0 ) {
00431         end = d->head.indexOf( '\n', beg );
00432       }
00433       if ( beg >= 0 && end > beg ) {
00434         d->head.remove( beg, end - beg );
00435       }
00436       beg = d->head.indexOf( "Content-Transfer-Encoding: " );
00437       if ( beg >= 0 ) {
00438         end = d->head.indexOf( '\n', beg );
00439       }
00440       if ( beg >= 0 && end > beg ) {
00441         d->head.remove( beg, end - beg );
00442       }
00443 
00444       d->head += "MIME-Version: 1.0\n";
00445       d->head += contentType( true )->as7BitString() + '\n';
00446       d->head += contentTransferEncoding( true )->as7BitString() + '\n';
00447     }
00448   }
00449 
00450   //head
00451   e = d->head;
00452   e += '\n';
00453 
00454   //body
00455   if ( !d->body.isEmpty() ) { //this message contains only one part
00456     Headers::ContentTransferEncoding *enc=contentTransferEncoding();
00457 
00458     if (enc->needToEncode()) {
00459       if ( enc->encoding() == Headers::CEquPr ) {
00460         e += KCodecs::quotedPrintableEncode( d->body, false );
00461       } else {
00462         e += KCodecs::base64Encode( d->body, true );
00463         e += '\n';
00464       }
00465     } else {
00466       e += d->body;
00467     }
00468   }
00469   else if ( !d->contents.isEmpty() ) { //this is a multipart message
00470     Headers::ContentType *ct=contentType();
00471     QByteArray boundary = "\n--" + ct->boundary();
00472 
00473     //add all (encoded) contents separated by boundaries
00474     foreach ( Content *c, d->contents ) {
00475       e+=boundary + '\n';
00476       e += c->encodedContent( false );  // don't convert LFs here, we do that later!!!!!
00477     }
00478     //finally append the closing boundary
00479     e += boundary+"--\n";
00480   };
00481 
00482   if ( useCrLf ) {
00483     return LFtoCRLF( e );
00484   } else {
00485     return e;
00486   }
00487 }
00488 
00489 QByteArray Content::decodedContent()
00490 {
00491   QByteArray temp, ret;
00492   Headers::ContentTransferEncoding *ec=contentTransferEncoding();
00493   bool removeTrailingNewline=false;
00494   int size = d_ptr->body.length();
00495 
00496   if ( size == 0 ) {
00497     return ret;
00498   }
00499 
00500   temp.resize( size );
00501   memcpy( temp.data(), d_ptr->body.data(), size );
00502 
00503   if ( ec->decoded() ) {
00504     ret = temp;
00505     removeTrailingNewline = true;
00506   } else {
00507     switch( ec->encoding() ) {
00508     case Headers::CEbase64 :
00509       KCodecs::base64Decode( temp, ret );
00510       break;
00511     case Headers::CEquPr :
00512       ret = KCodecs::quotedPrintableDecode( d_ptr->body );
00513       ret.resize( ret.size() - 1 );  // remove null-char
00514       removeTrailingNewline = true;
00515       break;
00516     case Headers::CEuuenc :
00517       KCodecs::uudecode( temp, ret );
00518       break;
00519     case Headers::CEbinary :
00520       ret = temp;
00521       removeTrailingNewline = false;
00522       break;
00523     default :
00524       ret = temp;
00525       removeTrailingNewline = true;
00526     }
00527   }
00528 
00529   if ( removeTrailingNewline && ( ret.size() > 0 ) && ( ret[ret.size()-1] == '\n') ) {
00530     ret.resize( ret.size() - 1 );
00531   }
00532 
00533   return ret;
00534 }
00535 
00536 QString Content::decodedText( bool trimText, bool removeTrailingNewlines )
00537 {
00538   if ( !decodeText() ) { //this is not a text content !!
00539     return QString();
00540   }
00541 
00542   bool ok = true;
00543   QTextCodec *codec =
00544     KGlobal::charsets()->codecForName( contentType()->charset(), ok );
00545 
00546   QString s = codec->toUnicode( d_ptr->body.data(), d_ptr->body.length() );
00547 
00548   if ( trimText && removeTrailingNewlines ) {
00549     int i;
00550     for ( i = s.length() - 1; i >= 0; --i ) {
00551       if ( !s[i].isSpace() ) {
00552         break;
00553       }
00554     }
00555     s.truncate( i + 1 );
00556   } else {
00557     if ( s.right( 1 ) == "\n" ) {
00558       s.truncate( s.length() - 1 ); // remove trailing new-line
00559     }
00560   }
00561 
00562   return s;
00563 }
00564 
00565 void Content::fromUnicodeString( const QString &s )
00566 {
00567   bool ok = true;
00568   QTextCodec *codec =
00569     KGlobal::charsets()->codecForName( contentType()->charset(), ok );
00570 
00571   if ( !ok ) { // no suitable codec found => try local settings and hope the best ;-)
00572     codec = KGlobal::locale()->codecForEncoding();
00573     QByteArray chset = KGlobal::locale()->encoding();
00574     contentType()->setCharset( chset );
00575   }
00576 
00577   d_ptr->body = codec->fromUnicode( s );
00578   contentTransferEncoding()->setDecoded( true ); //text is always decoded
00579 }
00580 
00581 Content *Content::textContent()
00582 {
00583   Content *ret=0;
00584 
00585   //return the first content with mimetype=text/*
00586   if ( contentType()->isText() ) {
00587     ret = this;
00588   } else {
00589     foreach ( Content *c, d_ptr->contents ) {
00590       if ( ( ret = c->textContent() ) != 0 ) {
00591         break;
00592       }
00593     }
00594   }
00595   return ret;
00596 }
00597 
00598 Content::List Content::attachments( bool incAlternatives )
00599 {
00600   List attachments;
00601   if ( d_ptr->contents.isEmpty() ) {
00602     attachments.append( this );
00603   } else {
00604     foreach ( Content *c, d_ptr->contents ) {
00605       if ( !incAlternatives &&
00606            c->contentType()->category() == Headers::CCalternativePart ) {
00607         continue;
00608       } else {
00609         attachments += c->attachments( incAlternatives );
00610       }
00611     }
00612   }
00613 
00614   if ( isTopLevel() ) {
00615     Content *text = textContent();
00616     if ( text ) {
00617       attachments.removeAll( text );
00618     }
00619   }
00620   return attachments;
00621 }
00622 
00623 Content::List Content::contents() const
00624 {
00625   return d_ptr->contents;
00626 }
00627 
00628 void Content::addContent( Content *c, bool prepend )
00629 {
00630   Q_D(Content);
00631   if ( d->contents.isEmpty() && !contentType()->isMultipart() ) {
00632     // this message is not multipart yet
00633 
00634     // first we convert the body to a content
00635     Content *main = new Content( this );
00636 
00637     //the Mime-Headers are needed, so we move them to the new content
00638     for ( Headers::Base::List::iterator it = h_eaders.begin();
00639           it != h_eaders.end(); ) {
00640       if ( (*it)->isMimeHeader() ) {
00641         // append to new content
00642         main->h_eaders.append( *it );
00643         // and remove from this content
00644         it = h_eaders.erase( it );
00645       } else {
00646         ++it;
00647       }
00648     }
00649 
00650     //"main" is now part of a multipart/mixed message
00651     main->contentType()->setCategory(Headers::CCmixedPart);
00652 
00653     //the head of "main" is empty, so we assemble it
00654     main->assemble();
00655 
00656     //now we can copy the body and append the new content;
00657     main->setBody( d->body );
00658     d->contents.append( main );
00659     d->body.clear(); //no longer needed
00660 
00661     //finally we have to convert this article to "multipart/mixed"
00662     Headers::ContentType *ct=contentType();
00663     ct->setMimeType( "multipart/mixed" );
00664     ct->setBoundary( multiPartBoundary() );
00665     ct->setCategory( Headers::CCcontainer );
00666     contentTransferEncoding()->clear();  // 7Bit, decoded
00667 
00668   }
00669 
00670   //here we actually add the content
00671   if ( prepend ) {
00672     d->contents.insert( 0, c );
00673   } else {
00674     d->contents.append( c );
00675   }
00676 
00677   if ( c->parent() != this )
00678     c->setParent(this);
00679 }
00680 
00681 void Content::removeContent( Content *c, bool del )
00682 {
00683   Q_D(Content);
00684   if ( d->contents.isEmpty() ) { // what the ..
00685     return;
00686   }
00687 
00688   d->contents.removeAll( c );
00689   if ( del ) {
00690     delete c;
00691   } else {
00692     c->setParent( 0 );
00693   }
00694 
00695   //only one content left => turn this message in a single-part
00696   if ( d->contents.count() == 1 ) {
00697     Content *main = d->contents.first();
00698 
00699     //first we have to move the mime-headers
00700     for ( Headers::Base::List::iterator it = main->h_eaders.begin();
00701           it != main->h_eaders.end(); ) {
00702       if ( (*it)->isMimeHeader() ) {
00703         kDebug(5320) << "Content::removeContent(Content *c, bool del) : mime-header moved:"
00704                      << (*it)->as7BitString();
00705         // first remove the old header
00706         removeHeader( (*it)->type() );
00707         // then append to new content
00708         h_eaders.append( *it );
00709         // and finally remove from this content
00710         it = main->h_eaders.erase( it );
00711       } else {
00712         ++it;
00713       }
00714     }
00715 
00716     //now we can copy the body
00717     d->body = main->body();
00718 
00719     //finally we can delete the content list
00720     qDeleteAll( d->contents );
00721     d->contents.clear();
00722   }
00723 }
00724 
00725 void Content::changeEncoding( Headers::contentEncoding e )
00726 {
00727   Headers::ContentTransferEncoding *enc = contentTransferEncoding();
00728   if ( enc->encoding() == e ) { //nothing to do
00729     return;
00730   }
00731 
00732   if ( decodeText() ) {
00733     enc->setEncoding( e ); // text is not encoded until it's sent or saved
00734                            // so we just set the new encoding
00735   } else {
00736     // this content contains non textual data, that has to be re-encoded
00737     if ( e != Headers::CEbase64 ) {
00738       //kWarning(5003) << "Content::changeEncoding() : non textual data"
00739       //               << "and encoding != base64 - this should not happen =>"
00740       //               << "forcing base64";
00741       e = Headers::CEbase64;
00742     }
00743 
00744     if ( enc->encoding() != e ) { // ok, we reencode the content using base64
00745       d_ptr->body = KCodecs::base64Encode( decodedContent(), true );
00746       d_ptr->body.append( "\n" );
00747       enc->setEncoding( e ); //set encoding
00748       enc->setDecoded( false );
00749     }
00750   }
00751 }
00752 
00753 void Content::toStream( QTextStream &ts, bool scrambleFromLines )
00754 {
00755   QByteArray ret = encodedContent( false );
00756 
00757   if ( scrambleFromLines ) {
00758     // FIXME Why are only From lines with a preceding empty line considered?
00759     //       And, of course, all lines starting with >*From have to be escaped
00760     //       because otherwise the transformation is not revertable.
00761     ret.replace( "\n\nFrom ", "\n\n>From ");
00762   }
00763   ts << ret;
00764 }
00765 
00766 Headers::Generic *Content::getNextHeader( QByteArray &head )
00767 {
00768     return nextHeader( head );
00769 }
00770 
00771 Headers::Generic *Content::nextHeader( QByteArray &head )
00772 {
00773   int pos1=-1, pos2=0, len=head.length()-1;
00774   bool folded( false );
00775   Headers::Generic *header=0;
00776 
00777   pos1 = head.indexOf( ": " );
00778 
00779   if ( pos1 > -1 ) {    //there is another header
00780     pos2 = pos1 += 2; //skip the name
00781 
00782     if ( head[pos2] != '\n' ) {  // check if the header is not empty
00783       while ( 1 ) {
00784         pos2 = head.indexOf( '\n', pos2 + 1 );
00785         if ( pos2 == -1 || pos2 == len ||
00786              ( head[pos2+1] != ' ' && head[pos2+1] != '\t' ) ) {
00787           //break if we reach the end of the string, honor folded lines
00788           break;
00789         } else {
00790           folded = true;
00791         }
00792       }
00793     }
00794 
00795     if ( pos2 < 0 ) {
00796       pos2 = len + 1; //take the rest of the string
00797     }
00798 
00799     if ( !folded ) {
00800       header = new Headers::Generic(head.left(pos1-2), this, head.mid(pos1, pos2-pos1));
00801     } else {
00802       QByteArray hdrValue = head.mid( pos1, pos2 - pos1 );
00803       header = new Headers::Generic( head.left( pos1 - 2 ), this, unfoldHeader( hdrValue ) );
00804     }
00805 
00806     head.remove( 0, pos2 + 1 );
00807   } else {
00808     head = "";
00809   }
00810 
00811   return header;
00812 }
00813 
00814 Headers::Base *Content::getHeaderByType( const char *type )
00815 {
00816   return headerByType( type );
00817 }
00818 
00819 Headers::Base *Content::headerByType( const char *type )
00820 {
00821   if ( !type ) {
00822     return 0;
00823   }
00824 
00825   //first we check if the requested header is already cached
00826   foreach ( Headers::Base *h, h_eaders ) {
00827     if ( h->is( type ) ) {
00828       return h; //found
00829     }
00830   }
00831 
00832   //now we look for it in the article head
00833   Headers::Base *h = 0;
00834   QByteArray raw=rawHeader( type );
00835   if ( !raw.isEmpty() ) { //ok, we found it
00836     //choose a suitable header class
00837     if ( strcasecmp( "Message-Id", type ) == 0 ) {
00838       h = new Headers::MessageID( this, raw );
00839     } else if ( strcasecmp( "Subject", type ) == 0 ) {
00840       h = new Headers::Subject( this, raw );
00841     } else if ( strcasecmp( "Date", type ) == 0 ) {
00842       h = new Headers::Date( this, raw );
00843     } else if ( strcasecmp( "From", type ) == 0 ) {
00844       h = new Headers::From( this, raw );
00845     } else if ( strcasecmp( "Organization", type ) == 0 ) {
00846       h = new Headers::Organization( this, raw );
00847     } else if ( strcasecmp( "Reply-To", type ) == 0 ) {
00848       h = new Headers::ReplyTo( this, raw );
00849     } else if ( strcasecmp( "Mail-Copies-To", type ) == 0 ) {
00850       h = new Headers::MailCopiesTo( this, raw );
00851     } else if ( strcasecmp( "To", type ) == 0 ) {
00852       h = new Headers::To( this, raw );
00853     } else if ( strcasecmp( "CC", type ) == 0 ) {
00854       h = new Headers::Cc( this, raw );
00855     } else if ( strcasecmp( "BCC", type ) == 0 ) {
00856       h = new Headers::Bcc( this, raw );
00857     } else if ( strcasecmp( "Newsgroups", type ) == 0 ) {
00858       h = new Headers::Newsgroups( this, raw );
00859     } else if ( strcasecmp( "Followup-To", type ) == 0 ) {
00860       h = new Headers::FollowUpTo( this, raw );
00861     } else if ( strcasecmp( "References", type ) == 0 ) {
00862       h = new Headers::References( this, raw );
00863     } else if ( strcasecmp( "Lines", type ) == 0 ) {
00864       h = new Headers::Lines( this, raw );
00865     } else if ( strcasecmp( "Content-Type", type ) == 0 ) {
00866       h = new Headers::ContentType( this, raw );
00867     } else if ( strcasecmp( "Content-Transfer-Encoding", type ) == 0 ) {
00868       h = new Headers::ContentTransferEncoding( this, raw );
00869     } else if ( strcasecmp( "Content-Disposition", type ) == 0 ) {
00870       h = new Headers::ContentDisposition( this, raw );
00871     } else if ( strcasecmp( "Content-Description", type ) == 0 ) {
00872       h = new Headers::ContentDescription( this, raw );
00873     } else if ( strcasecmp( "Content-Location", type ) == 0 ) {
00874       h = new Headers::ContentLocation( this, raw );
00875     } else if ( strcasecmp( "Sender", type ) == 0 ) {
00876       h = new Headers::Sender( this, raw );
00877     } else {
00878       h = new Headers::Generic( type, this, raw );
00879     }
00880     h_eaders.append( h );  //add to cache
00881     return h;
00882   } else {
00883     return 0; //header not found
00884   }
00885 }
00886 
00887 QList<Headers::Base*> Content::headersByType( const char *type )
00888 {
00889   QList<Headers::Base*> result;
00890 
00891   if ( !type ) {
00892     return result;
00893   }
00894 
00895   QList<QByteArray> raw=rawHeaders( type );
00896   foreach( QByteArray header, raw )
00897       result.append( new Headers::Generic( type, this, header ) );
00898   return result;
00899 }
00900 
00901 void Content::setHeader( Headers::Base *h )
00902 {
00903   if ( !h ) {
00904     return;
00905   }
00906   removeHeader( h->type() );
00907   h_eaders.append( h );
00908 }
00909 
00910 bool Content::removeHeader( const char *type )
00911 {
00912   for ( Headers::Base::List::iterator it = h_eaders.begin();
00913         it != h_eaders.end(); ++it )
00914     if ( (*it)->is(type) ) {
00915       delete (*it);
00916       h_eaders.erase( it );
00917       return true;
00918     }
00919 
00920   return false;
00921 }
00922 
00923 bool Content::hasHeader( const char *type )
00924 {
00925   return headerByType( type ) != 0;
00926 }
00927 
00928 Headers::ContentType *Content::contentType( bool create )
00929 {
00930   Headers::ContentType *p=0;
00931   return headerInstance( p, create );
00932 }
00933 
00934 Headers::ContentTransferEncoding *Content::contentTransferEncoding( bool create )
00935 {
00936   Headers::ContentTransferEncoding *p=0;
00937   return headerInstance( p, create );
00938 }
00939 
00940 Headers::ContentDisposition *Content::contentDisposition( bool create )
00941 {
00942   Headers::ContentDisposition *p=0;
00943   return headerInstance( p, create );
00944 }
00945 
00946 Headers::ContentDescription *Content::contentDescription( bool create )
00947 {
00948   Headers::ContentDescription *p=0;
00949   return headerInstance( p, create );
00950 }
00951 
00952 Headers::ContentLocation *Content::contentLocation( bool create )
00953 {
00954   Headers::ContentLocation *p=0;
00955   return headerInstance( p, create );
00956 }
00957 
00958 int Content::size()
00959 {
00960   int ret = d_ptr->body.length();
00961 
00962   if ( contentTransferEncoding()->encoding() == Headers::CEbase64 ) {
00963     return ret * 3 / 4; //base64 => 6 bit per byte
00964   }
00965 
00966   return ret;
00967 }
00968 
00969 int Content::storageSize() const
00970 {
00971   const Q_D(Content);
00972   int s = d->head.size();
00973 
00974   if ( d->contents.isEmpty() ) {
00975     s += d->body.size();
00976   } else {
00977     foreach ( Content *c, d->contents ) {
00978       s += c->storageSize();
00979     }
00980   }
00981 
00982   return s;
00983 }
00984 
00985 int Content::lineCount() const
00986 {
00987   const Q_D(Content);
00988   int ret = 0;
00989   if ( !isTopLevel() ) {
00990     ret += d->head.count( '\n' );
00991   }
00992   ret += d->body.count( '\n' );
00993 
00994   foreach ( Content *c, d->contents ) {
00995     ret += c->lineCount();
00996   }
00997 
00998   return ret;
00999 }
01000 
01001 QByteArray Content::rawHeader( const char *name ) const
01002 {
01003   return KMime::extractHeader( d_ptr->head, name );
01004 }
01005 
01006 QList<QByteArray> Content::rawHeaders( const char *name ) const
01007 {
01008   return KMime::extractHeaders( d_ptr->head, name );
01009 }
01010 
01011 bool Content::decodeText()
01012 {
01013   Q_D(Content);
01014   Headers::ContentTransferEncoding *enc = contentTransferEncoding();
01015 
01016   if ( !contentType()->isText() ) {
01017     return false; //non textual data cannot be decoded here => use decodedContent() instead
01018   }
01019   if ( enc->decoded() ) {
01020     return true; //nothing to do
01021   }
01022 
01023   switch( enc->encoding() )
01024   {
01025   case Headers::CEbase64 :
01026     d->body = KCodecs::base64Decode( d->body );
01027     d->body.append( "\n" );
01028     break;
01029   case Headers::CEquPr :
01030     d->body = KCodecs::quotedPrintableDecode( d->body );
01031     break;
01032   case Headers::CEuuenc :
01033     d->body = KCodecs::uudecode( d->body );
01034     d->body.append( "\n" );
01035     break;
01036   case Headers::CEbinary :
01037     // nothing to decode
01038     d->body.append( "\n" );
01039   default :
01040     break;
01041   }
01042 
01043   enc->setDecoded( true );
01044   return true;
01045 }
01046 
01047 QByteArray Content::defaultCharset() const
01048 {
01049   return d_ptr->defaultCS;
01050 }
01051 
01052 void Content::setDefaultCharset( const QByteArray &cs )
01053 {
01054   d_ptr->defaultCS = KMime::cachedCharset( cs );
01055 
01056   foreach ( Content *c, d_ptr->contents ) {
01057     c->setDefaultCharset( cs );
01058   }
01059 
01060   // reparse the part and its sub-parts in order
01061   // to clear cached header values
01062   parse();
01063 }
01064 
01065 bool Content::forceDefaultCharset() const
01066 {
01067   return d_ptr->forceDefaultCS;
01068 }
01069 
01070 void Content::setForceDefaultCharset( bool b )
01071 {
01072   d_ptr->forceDefaultCS = b;
01073 
01074   foreach ( Content *c, d_ptr->contents ) {
01075     c->setForceDefaultCharset( b );
01076   }
01077 
01078   // reparse the part and its sub-parts in order
01079   // to clear cached header values
01080   parse();
01081 }
01082 
01083 Content * KMime::Content::content( const ContentIndex &index ) const
01084 {
01085   if ( !index.isValid() ) {
01086     return const_cast<KMime::Content*>( this );
01087   }
01088   ContentIndex idx = index;
01089   unsigned int i = idx.pop() - 1; // one-based -> zero-based index
01090   if ( i < (unsigned int)d_ptr->contents.size() ) {
01091     return d_ptr->contents[i]->content( idx );
01092   } else {
01093     return 0;
01094   }
01095 }
01096 
01097 ContentIndex KMime::Content::indexForContent( Content * content ) const
01098 {
01099   int i = d_ptr->contents.indexOf( content );
01100   if ( i >= 0 ) {
01101     ContentIndex ci;
01102     ci.push( i + 1 ); // zero-based -> one-based index
01103     return ci;
01104   }
01105   // not found, we need to search recursively
01106   for ( int i = 0; i < d_ptr->contents.size(); ++i ) {
01107     ContentIndex ci = d_ptr->contents[i]->indexForContent( content );
01108     if ( ci.isValid() ) {
01109       // found it
01110       ci.push( i + 1 ); // zero-based -> one-based index
01111       return ci;
01112     }
01113   }
01114   return ContentIndex(); // not found
01115 }
01116 
01117 bool Content::isTopLevel() const
01118 {
01119   return false;
01120 }
01121 
01122 void Content::setParent( Content* parent )
01123 {
01124   //make sure the Content is only in the contents list of one parent object
01125   Content *oldParent = d_ptr->parent;
01126   if ( oldParent && oldParent->contents().contains( this ) ) {
01127     oldParent->removeContent( this );
01128   }
01129 
01130   d_ptr->parent = parent;
01131   if ( parent && !parent->contents().contains( this ) ) {
01132     parent->addContent( this );
01133   }
01134 }
01135 
01136 Content* Content::parent() const
01137 {
01138   return d_ptr->parent;
01139 }
01140 
01141 Content* Content::topLevel() const
01142 {
01143   Content *top = const_cast<Content*>(this);
01144   Content *c = parent();
01145   while ( c ) {
01146     top = c;
01147     c = c->parent();
01148   }
01149 
01150   return top;
01151 }
01152 
01153 ContentIndex Content::index() const
01154 {
01155   Content* top = topLevel();
01156   if ( top ) {
01157     return top->indexForContent( const_cast<Content*>(this) );
01158   }
01159 
01160   return indexForContent( const_cast<Content*>(this)  );
01161 }
01162 
01163 
01164 
01165 } // namespace KMime

KMIME Library

Skip menu "KMIME Library"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  • kabc
  • kblog
  • kcal
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  • kldap
  • kmime
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.5.8
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal