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