khtml Library API Documentation

kmultipart.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2002 David Faure <david@mandrakesoft.com>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00017    Boston, MA 02111-1307, USA.
00018 */
00019 
00020 #include "kmultipart.h"
00021 
00022 #include <qvbox.h>
00023 #include <kinstance.h>
00024 #include <kmimetype.h>
00025 #include <klocale.h>
00026 #include <kio/job.h>
00027 #include <qfile.h>
00028 #include <ktempfile.h>
00029 #include <kmessagebox.h>
00030 #include <kparts/componentfactory.h>
00031 #include <kparts/genericfactory.h>
00032 #include <khtml_part.h>
00033 #include <unistd.h>
00034 #include <kxmlguifactory.h>
00035 
00036 typedef KParts::GenericFactory<KMultiPart> KMultiPartFactory; // factory for the part
00037 K_EXPORT_COMPONENT_FACTORY( libkmultipart /*library name*/, KMultiPartFactory )
00038 
00039 //#define DEBUG_PARSING
00040 
00041 class KLineParser
00042 {
00043 public:
00044     KLineParser() {
00045         m_lineComplete = false;
00046     }
00047     void addChar( char c, bool storeNewline ) {
00048         if ( !storeNewline && c == '\r' )
00049             return;
00050         Q_ASSERT( !m_lineComplete );
00051         if ( storeNewline || c != '\n' ) {
00052             int sz = m_currentLine.size();
00053             m_currentLine.resize( sz+1, QGArray::SpeedOptim );
00054             m_currentLine[sz] = c;
00055         }
00056         if ( c == '\n' )
00057             m_lineComplete = true;
00058     }
00059     bool isLineComplete() const {
00060         return m_lineComplete;
00061     }
00062     QByteArray currentLine() const {
00063         return m_currentLine;
00064     }
00065     void clearLine() {
00066         Q_ASSERT( m_lineComplete );
00067         reset();
00068     }
00069     void reset() {
00070         m_currentLine.resize( 0, QGArray::SpeedOptim );
00071         m_lineComplete = false;
00072     }
00073 private:
00074     QByteArray m_currentLine;
00075     bool m_lineComplete; // true when ending with '\n'
00076 };
00077 
00078 /* testcase:
00079    Content-type: multipart/mixed;boundary=ThisRandomString
00080 
00081 --ThisRandomString
00082 Content-type: text/plain
00083 
00084 Data for the first object.
00085 
00086 --ThisRandomString
00087 Content-type: text/plain
00088 
00089 Data for the second and last object.
00090 
00091 --ThisRandomString--
00092 */
00093 
00094 
00095 KMultiPart::KMultiPart( QWidget *parentWidget, const char *widgetName,
00096                         QObject *parent, const char *name, const QStringList& )
00097     : KParts::ReadOnlyPart( parent, name )
00098 {
00099     m_filter = 0L;
00100 
00101     setInstance( KMultiPartFactory::instance() );
00102 
00103     QVBox *box = new QVBox( parentWidget, widgetName );
00104     setWidget( box );
00105 
00106     m_extension = new KParts::BrowserExtension( this );
00107 
00108     // We probably need to use m_extension to get the urlArgs in openURL...
00109 
00110     m_part = 0L;
00111     m_job = 0L;
00112     m_lineParser = new KLineParser;
00113     m_tempFile = 0L;
00114 }
00115 
00116 KMultiPart::~KMultiPart()
00117 {
00118     // important: delete the nested part before the part or qobject destructor runs.
00119     // we now delete the nested part which deletes the part's widget which makes
00120     // _OUR_ m_widget 0 which in turn avoids our part destructor to delete the
00121     // widget ;-)
00122     // ### additional note: it _can_ be that the part has been deleted before:
00123     // when we're in a html frameset and the view dies first, then it will also
00124     // kill the htmlpart
00125     if ( m_part )
00126         delete static_cast<KParts::ReadOnlyPart *>( m_part );
00127     delete m_job;
00128     delete m_lineParser;
00129     delete m_tempFile;
00130     delete m_filter;
00131     m_filter = 0L;
00132 }
00133 
00134 
00135 void KMultiPart::startHeader()
00136 {
00137     m_bParsingHeader = true; // we expect a header to come first
00138     m_bGotAnyHeader = false;
00139     m_gzip = false;
00140     // just to be sure for now
00141     delete m_filter;
00142     m_filter = 0L;
00143 }
00144 
00145 
00146 bool KMultiPart::openURL( const KURL &url )
00147 {
00148     m_url = url;
00149     m_lineParser->reset();
00150     startHeader();
00151 
00152     KParts::URLArgs args = m_extension->urlArgs();
00153     //m_mimeType = args.serviceType;
00154 
00155     // Hmm, args.reload is set to true when reloading, but this doesn't seem to be enough...
00156     // I get "HOLD: Reusing held slave for <url>", and the old data
00157 
00158     m_job = KIO::get( url, args.reload, false );
00159 
00160     emit started( m_job );
00161 
00162     connect( m_job, SIGNAL( result( KIO::Job * ) ),
00163              this, SLOT( slotJobFinished( KIO::Job * ) ) );
00164     connect( m_job, SIGNAL( data( KIO::Job *, const QByteArray & ) ),
00165              this, SLOT( slotData( KIO::Job *, const QByteArray & ) ) );
00166 
00167     return true;
00168 }
00169 
00170 // Yes, libkdenetwork's has such a parser already (MultiPart),
00171 // but it works on the complete string, expecting the whole data to be available....
00172 // The version here is asynchronous.
00173 void KMultiPart::slotData( KIO::Job *job, const QByteArray &data )
00174 {
00175     if (m_boundary.isNull())
00176     {
00177        QString tmp = job->queryMetaData("media-boundary");
00178        kdDebug() << "Got Boundary from kio-http '" << tmp << "'" << endl;
00179        if ( !tmp.isEmpty() ) {
00180            m_boundary = QCString("--")+tmp.latin1();
00181            m_boundaryLength = m_boundary.length();
00182        }
00183     }
00184     // Append to m_currentLine until eol
00185     for ( uint i = 0; i < data.size() ; ++i )
00186     {
00187         // Store char. Skip if '\n' and currently parsing a header.
00188         m_lineParser->addChar( data[i], !m_bParsingHeader );
00189         if ( m_lineParser->isLineComplete() )
00190         {
00191             QByteArray lineData = m_lineParser->currentLine();
00192 #ifdef DEBUG_PARSING
00193             kdDebug() << "lineData.size()=" << lineData.size() << endl;
00194 #endif
00195             QCString line( lineData.data(), lineData.size()+1 ); // deep copy
00196             // 0-terminate the data, but only for the line-based tests below
00197             // We want to keep the raw data in case it ends up in sendData()
00198             int sz = line.size();
00199             if ( sz > 0 )
00200                 line[sz-1] = '\0';
00201 #ifdef DEBUG_PARSING
00202             kdDebug() << "[" << m_bParsingHeader << "] line='" << line << "'" << endl;
00203 #endif
00204             if ( m_bParsingHeader )
00205             {
00206                 if ( !line.isEmpty() )
00207                     m_bGotAnyHeader = true;
00208                 if ( m_boundary.isNull() )
00209                 {
00210                     if ( !line.isEmpty() ) {
00211 #ifdef DEBUG_PARSING
00212                         kdDebug() << "Boundary is " << line << endl;
00213 #endif
00214                         m_boundary = line;
00215                         m_boundaryLength = m_boundary.length();
00216                     }
00217                 }
00218                 else if ( !qstrnicmp( line.data(), "Content-Encoding:", 17 ) )
00219                 {
00220                     QString encoding = QString::fromLatin1(line.data()+17).stripWhiteSpace().lower();
00221                     if (encoding == "gzip" || encoding == "x-gzip") {
00222                         m_gzip = true;
00223                     } else {
00224                         kdDebug() << "FIXME: unhandled encoding type in KMultiPart: " << encoding << endl;
00225                     }
00226                 }
00227                 // parse Content-Type
00228                 else if ( !qstrnicmp( line.data(), "Content-Type:", 13 ) )
00229                 {
00230                     Q_ASSERT( m_nextMimeType.isNull() );
00231                     m_nextMimeType = QString::fromLatin1( line.data() + 14 ).stripWhiteSpace();
00232                     kdDebug() << "m_nextMimeType=" << m_nextMimeType << endl;
00233                 }
00234                 // Empty line, end of headers (if we had any header line before)
00235                 else if ( line.isEmpty() && m_bGotAnyHeader )
00236                 {
00237                     m_bParsingHeader = false;
00238 #ifdef DEBUG_PARSING
00239                     kdDebug() << "end of headers" << endl;
00240 #endif
00241                     startOfData();
00242                 }
00243                 // First header (when we know it from kio_http)
00244                 else if ( line == m_boundary )
00245                     ; // nothing to do
00246                 else if ( !line.isEmpty() ) // this happens with e.g. Set-Cookie:
00247                     kdDebug() << "Ignoring header " << line << endl;
00248             } else {
00249                 if ( !qstrncmp( line, m_boundary, m_boundaryLength ) )
00250                 {
00251 #ifdef DEBUG_PARSING
00252                     kdDebug() << "boundary found!" << endl;
00253                     kdDebug() << "after it is " << line.data() + m_boundaryLength << endl;
00254 #endif
00255                     // Was it the very last boundary ?
00256                     if ( !qstrncmp( line.data() + m_boundaryLength, "--", 2 ) )
00257                     {
00258 #ifdef DEBUG_PARSING
00259                         kdDebug() << "Completed!" << endl;
00260 #endif
00261                         endOfData();
00262                         emit completed();
00263                     } else
00264                     {
00265                         char nextChar = *(line.data() + m_boundaryLength);
00266 #ifdef DEBUG_PARSING
00267                         kdDebug() << "KMultiPart::slotData nextChar='" << nextChar << "'" << endl;
00268 #endif
00269                         if ( nextChar == '\n' || nextChar == '\r' ) {
00270                             endOfData();
00271                             startHeader();
00272                         }
00273                         else {
00274                             // otherwise, false hit, it has trailing stuff
00275                             sendData( lineData );
00276                         }
00277                     }
00278                 } else {
00279                     // send to part
00280                     sendData( lineData );
00281                 }
00282             }
00283             m_lineParser->clearLine();
00284         }
00285     }
00286 }
00287 
00288 void KMultiPart::setPart( const QString& mimeType )
00289 {
00290     KXMLGUIFactory *guiFactory = factory();
00291     if ( guiFactory ) // seems to be 0 when restoring from SM
00292         guiFactory->removeClient( this );
00293     kdDebug() << "KMultiPart::setPart " << mimeType << endl;
00294     delete m_part;
00295     // Try to find an appropriate viewer component
00296     m_part = KParts::ComponentFactory::createPartInstanceFromQuery<KParts::ReadOnlyPart>
00297              ( m_mimeType, QString::null, widget(), 0L, this, 0L );
00298     if ( !m_part ) {
00299         // TODO launch external app
00300         KMessageBox::error( widget(), i18n("No handler found for %1!").arg(m_mimeType) );
00301         return;
00302     }
00303     // By making the part a child XMLGUIClient of ours, we get its GUI merged in.
00304     insertChildClient( m_part );
00305     m_part->widget()->show();
00306 
00307     connect( m_part, SIGNAL( completed() ),
00308              this, SLOT( slotPartCompleted() ) );
00309 
00310     KParts::BrowserExtension* childExtension = KParts::BrowserExtension::childObject( m_part );
00311 
00312     if ( childExtension )
00313     {
00314 
00315         // Forward signals from the part's browser extension
00316         // this is very related (but not exactly like) KHTMLPart::processObjectRequest
00317 
00318         connect( childExtension, SIGNAL( openURLNotify() ),
00319                  m_extension, SIGNAL( openURLNotify() ) );
00320 
00321         connect( childExtension, SIGNAL( openURLRequestDelayed( const KURL &, const KParts::URLArgs & ) ),
00322                  m_extension, SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ) );
00323 
00324         connect( childExtension, SIGNAL( createNewWindow( const KURL &, const KParts::URLArgs & ) ),
00325                  m_extension, SIGNAL( createNewWindow( const KURL &, const KParts::URLArgs & ) ) );
00326         connect( childExtension, SIGNAL( createNewWindow( const KURL &, const KParts::URLArgs &, const KParts::WindowArgs &, KParts::ReadOnlyPart *& ) ),
00327                  m_extension, SIGNAL( createNewWindow( const KURL &, const KParts::URLArgs & , const KParts::WindowArgs &, KParts::ReadOnlyPart *&) ) );
00328 
00329         // Keep in sync with khtml_part.cpp
00330         connect( childExtension, SIGNAL( popupMenu( const QPoint &, const KFileItemList & ) ),
00331                  m_extension, SIGNAL( popupMenu( const QPoint &, const KFileItemList & ) ) );
00332         connect( childExtension, SIGNAL( popupMenu( KXMLGUIClient *, const QPoint &, const KFileItemList & ) ),
00333                  m_extension, SIGNAL( popupMenu( KXMLGUIClient *, const QPoint &, const KFileItemList & ) ) );
00334         connect( childExtension, SIGNAL( popupMenu( KXMLGUIClient *, const QPoint &, const KFileItemList &, const KParts::URLArgs &, KParts::BrowserExtension::PopupFlags ) ),
00335                  m_extension, SIGNAL( popupMenu( KXMLGUIClient *, const QPoint &, const KFileItemList &, const KParts::URLArgs &, KParts::BrowserExtension::PopupFlags ) ) );
00336         connect( childExtension, SIGNAL( popupMenu( const QPoint &, const KURL &, const QString &, mode_t ) ),
00337                  m_extension, SIGNAL( popupMenu( const QPoint &, const KURL &, const QString &, mode_t ) ) );
00338         connect( childExtension, SIGNAL( popupMenu( KXMLGUIClient *, const QPoint &, const KURL &, const QString &, mode_t ) ),
00339                  m_extension, SIGNAL( popupMenu( KXMLGUIClient *, const QPoint &, const KURL &, const QString &, mode_t ) ) );
00340         connect( childExtension, SIGNAL( popupMenu( KXMLGUIClient *, const QPoint &, const KURL &, const KParts::URLArgs &, KParts::BrowserExtension::PopupFlags, mode_t ) ),
00341                  m_extension, SIGNAL( popupMenu( KXMLGUIClient *, const QPoint &, const KURL &, const KParts::URLArgs &, KParts::BrowserExtension::PopupFlags, mode_t ) ) );
00342 
00343 
00344         connect( childExtension, SIGNAL( infoMessage( const QString & ) ),
00345                  m_extension, SIGNAL( infoMessage( const QString & ) ) );
00346 
00347         childExtension->setBrowserInterface( m_extension->browserInterface() );
00348 
00349         connect( childExtension, SIGNAL( enableAction( const char *, bool ) ),
00350                  m_extension, SIGNAL( enableAction( const char *, bool ) ) );
00351         connect( childExtension, SIGNAL( setLocationBarURL( const QString& ) ),
00352                  m_extension, SIGNAL( setLocationBarURL( const QString& ) ) );
00353         connect( childExtension, SIGNAL( setIconURL( const KURL& ) ),
00354                  m_extension, SIGNAL( setIconURL( const KURL& ) ) );
00355         connect( childExtension, SIGNAL( loadingProgress( int ) ),
00356                  m_extension, SIGNAL( loadingProgress( int ) ) );
00357         connect( childExtension, SIGNAL( speedProgress( int ) ),
00358                  m_extension, SIGNAL( speedProgress( int ) ) );
00359         connect( childExtension, SIGNAL( selectionInfo( const KFileItemList& ) ),
00360                  m_extension, SIGNAL( selectionInfo( const KFileItemList& ) ) );
00361         connect( childExtension, SIGNAL( selectionInfo( const QString& ) ),
00362                  m_extension, SIGNAL( selectionInfo( const QString& ) ) );
00363         connect( childExtension, SIGNAL( selectionInfo( const KURL::List& ) ),
00364                  m_extension, SIGNAL( selectionInfo( const KURL::List& ) ) );
00365         connect( childExtension, SIGNAL( mouseOverInfo( const KFileItem* ) ),
00366                  m_extension, SIGNAL( mouseOverInfo( const KFileItem* ) ) );
00367         connect( childExtension, SIGNAL( moveTopLevelWidget( int, int ) ),
00368                  m_extension, SIGNAL( moveTopLevelWidget( int, int ) ) );
00369         connect( childExtension, SIGNAL( resizeTopLevelWidget( int, int ) ),
00370                  m_extension, SIGNAL( resizeTopLevelWidget( int, int ) ) );
00371     }
00372 
00373     m_isHTMLPart = ( mimeType == "text/html" );
00374     // Load the part's plugins too.
00375     // ###### This is a hack. The bug is that KHTMLPart doesn't load its plugins
00376     // if className != "Browser/View".
00377     loadPlugins( this, m_part, m_part->instance() );
00378     // Get the part's GUI to appear
00379     if ( guiFactory )
00380         guiFactory->addClient( this );
00381 }
00382 
00383 void KMultiPart::startOfData()
00384 {
00385     kdDebug() << "KMultiPart::startOfData" << endl;
00386     Q_ASSERT( !m_nextMimeType.isNull() );
00387     if( m_nextMimeType.isNull() )
00388         return;
00389 
00390     if ( m_gzip )
00391     {
00392         m_filter = new HTTPFilterGZip;
00393         connect( m_filter, SIGNAL( output( const QByteArray& ) ), this, SLOT( reallySendData( const QByteArray& ) ) );
00394     }
00395 
00396     if ( m_mimeType != m_nextMimeType )
00397     {
00398         // Need to switch parts (or create the initial one)
00399         m_mimeType = m_nextMimeType;
00400         setPart( m_mimeType );
00401     }
00402     Q_ASSERT( m_part );
00403     // Pass URLArgs (e.g. reload)
00404     KParts::BrowserExtension* childExtension = KParts::BrowserExtension::childObject( m_part );
00405     if ( childExtension )
00406         childExtension->setURLArgs( m_extension->urlArgs() );
00407 
00408     m_nextMimeType = QString::null;
00409     delete m_tempFile;
00410     m_tempFile = 0L;
00411     if ( m_isHTMLPart )
00412     {
00413         KHTMLPart* htmlPart = static_cast<KHTMLPart *>( static_cast<KParts::ReadOnlyPart *>( m_part ) );
00414         htmlPart->begin( url() );
00415     }
00416     else
00417         m_tempFile = new KTempFile;
00418 }
00419 
00420 void KMultiPart::sendData( const QByteArray& line )
00421 {
00422     if ( m_filter )
00423     {
00424         m_filter->slotInput( line );
00425     }
00426     else
00427     {
00428         reallySendData( line );
00429     }
00430 }
00431 
00432 void KMultiPart::reallySendData( const QByteArray& line )
00433 {
00434     if ( m_isHTMLPart )
00435     {
00436         KHTMLPart* htmlPart = static_cast<KHTMLPart *>( static_cast<KParts::ReadOnlyPart *>( m_part ) );
00437         htmlPart->write( line.data(), line.size() );
00438     }
00439     else if ( m_tempFile )
00440     {
00441         m_tempFile->file()->writeBlock( line.data(), line.size() );
00442     }
00443 }
00444 
00445 void KMultiPart::endOfData()
00446 {
00447     Q_ASSERT( m_part );
00448     if ( m_isHTMLPart )
00449     {
00450         KHTMLPart* htmlPart = static_cast<KHTMLPart *>( static_cast<KParts::ReadOnlyPart *>( m_part ) );
00451         htmlPart->end();
00452     } else if ( m_tempFile )
00453     {
00454         m_tempFile->close();
00455         kdDebug() << "KMultiPart::endOfData opening " << m_tempFile->name() << endl;
00456         KURL url;
00457         url.setPath( m_tempFile->name() );
00458         (void) m_part->openURL( url );
00459         delete m_tempFile;
00460         m_tempFile = 0L;
00461     }
00462 }
00463 
00464 void KMultiPart::slotPartCompleted()
00465 {
00466     if ( !m_isHTMLPart )
00467     {
00468         Q_ASSERT( m_part );
00469         // Delete temp file used by the part
00470         Q_ASSERT( m_part->url().isLocalFile() );
00471     kdDebug() << "slotPartCompleted deleting " << m_part->url().path() << endl;
00472         (void) unlink( QFile::encodeName( m_part->url().path() ) );
00473         // Do not emit completed from here.
00474     }
00475 }
00476 
00477 bool KMultiPart::closeURL()
00478 {
00479     if ( m_part )
00480         return m_part->closeURL();
00481     return true;
00482 }
00483 
00484 void KMultiPart::guiActivateEvent( KParts::GUIActivateEvent * )
00485 {
00486     // Not public!
00487     //if ( m_part )
00488     //    m_part->guiActivateEvent( e );
00489 }
00490 
00491 void KMultiPart::slotJobFinished( KIO::Job *job )
00492 {
00493     if ( job->error() )
00494     {
00495         // TODO use khtml's error:// scheme
00496         job->showErrorDialog();
00497         emit canceled( job->errorString() );
00498     }
00499     else
00500     {
00501         /*if ( m_khtml->view()->contentsY() == 0 )
00502         {
00503             KParts::URLArgs args = m_ext->urlArgs();
00504             m_khtml->view()->setContentsPos( args.xOffset, args.yOffset );
00505         }*/
00506 
00507         emit completed();
00508 
00509         //QTimer::singleShot( 0, this, SLOT( updateWindowCaption() ) );
00510     }
00511     m_job = 0L;
00512 }
00513 
00514 KAboutData* KMultiPart::createAboutData()
00515 {
00516     KAboutData* aboutData = new KAboutData( "kmultipart", I18N_NOOP("KMultiPart"),
00517                                              "0.1",
00518                                             I18N_NOOP( "Embeddable component for multipart/mixed" ),
00519                                              KAboutData::License_GPL,
00520                                              "(c) 2001, David Faure <david@mandrakesoft.com>");
00521     return aboutData;
00522 }
00523 
00524 #if 0
00525 KMultiPartBrowserExtension::KMultiPartBrowserExtension( KMultiPart *parent, const char *name )
00526     : KParts::BrowserExtension( parent, name )
00527 {
00528     m_imgPart = parent;
00529 }
00530 
00531 int KMultiPartBrowserExtension::xOffset()
00532 {
00533     return m_imgPart->doc()->view()->contentsX();
00534 }
00535 
00536 int KMultiPartBrowserExtension::yOffset()
00537 {
00538     return m_imgPart->doc()->view()->contentsY();
00539 }
00540 
00541 void KMultiPartBrowserExtension::print()
00542 {
00543     static_cast<KHTMLPartBrowserExtension *>( m_imgPart->doc()->browserExtension() )->print();
00544 }
00545 
00546 void KMultiPartBrowserExtension::reparseConfiguration()
00547 {
00548     static_cast<KHTMLPartBrowserExtension *>( m_imgPart->doc()->browserExtension() )->reparseConfiguration();
00549     m_imgPart->doc()->setAutoloadImages( true );
00550 }
00551 #endif
00552 
00553 #include "kmultipart.moc"
KDE Logo
This file is part of the documentation for khtml Library Version 3.2.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Wed Feb 4 12:37:22 2004 by doxygen 1.2.18 written by Dimitri van Heesch, © 1997-2003