kio_help.cpp

00001 #include <config.h>
00002 
00003 #ifdef HAVE_SYS_TYPES_H
00004 # include <sys/types.h>
00005 #endif
00006 #ifdef HAVE_SYS_STAT_H
00007 # include <sys/stat.h>
00008 #endif
00009 
00010 #include <errno.h>
00011 #include <fcntl.h>
00012 #ifdef HAVE_STDIO_H
00013 # include <stdio.h>
00014 #endif
00015 #ifdef HAVE_STDLIB_H
00016 # include <stdlib.h>
00017 #endif
00018 
00019 #include <qvaluelist.h>
00020 #include <qfileinfo.h>
00021 #include <qfile.h>
00022 #include <qtextstream.h>
00023 #include <qregexp.h>
00024 #include <qtextcodec.h>
00025 
00026 #include <kdebug.h>
00027 #include <kurl.h>
00028 #include <kglobal.h>
00029 #include <klocale.h>
00030 #include <kstandarddirs.h>
00031 #include <kinstance.h>
00032 
00033 #include "kio_help.h"
00034 #include <libxslt/xsltutils.h>
00035 #include <libxslt/transform.h>
00036 #include "xslt.h"
00037 
00038 using namespace KIO;
00039 
00040 QString HelpProtocol::langLookup(QString fname)
00041 {
00042     QStringList search;
00043 
00044     // assemble the local search paths
00045     const QStringList localDoc = KGlobal::dirs()->resourceDirs("html");
00046 
00047     kdDebug( 7119 ) << "Looking up help for: " << fname << endl;
00048 
00049     QString path;
00050     int slash = fname.findRev ('/');
00051     if (slash == -1 || slash == 0) {
00052       path = fname;
00053       fname = "";
00054     } else {
00055       path = fname.left (slash);
00056       fname = fname.right (fname.length() - slash);
00057     }
00058 
00059     QStringList langs = KGlobal::locale()->languageList();
00060     QStringList::ConstIterator lang;
00061     for (lang = langs.begin(); lang != langs.end(); ++lang)
00062         if ((*lang).left(2) == "en")
00063         search.append(QString("/usr/share/gnome/help/%1/C%2").arg(path).arg(fname));
00064         else
00065         search.append(QString("/usr/share/gnome/help/%1/%2%3").arg(path).arg(*lang).arg(fname));
00066 
00067     langs.append( "en" );
00068     langs.remove( "C" );
00069 
00070     // this is kind of compat hack as we install our docs in en/ but the
00071     // default language is en_US
00072     for (QStringList::Iterator it = langs.begin(); it != langs.end(); ++it)
00073         if ( *it == "en_US" )
00074             *it = "en";
00075 
00076     // look up the different languages
00077     int ldCount = localDoc.count();
00078     for (int id=0; id < ldCount; id++)
00079     {
00080         QStringList::ConstIterator lang;
00081         for (lang = langs.begin(); lang != langs.end(); ++lang)
00082             search.append(QString("%1%2/%3").arg(localDoc[id]).arg(*lang).arg(path + fname));
00083     }
00084 
00085     // try to locate the file
00086     QStringList::Iterator it;
00087     for (it = search.begin(); it != search.end(); ++it)
00088     {
00089         kdDebug( 7119 ) << "Looking for help in: " << *it << endl;
00090 
00091         QFileInfo info(*it);
00092         if (info.exists() && info.isFile() && info.isReadable())
00093             return *it;
00094 
00095         if ( ( *it ).right( 5 ) == ".html" )
00096         {
00097             QString file = (*it).left((*it).findRev('/')) + "/index.docbook";
00098             kdDebug( 7119 ) << "Looking for help in: " << file << endl;
00099             info.setFile(file);
00100             if (info.exists() && info.isFile() && info.isReadable())
00101                 return *it;
00102         }
00103 
00104         if ( ( *it ).right( 5 ) == ".html" )
00105         {
00106             QString file = (*it).left((*it).findRev('/')) + "/" + path + ".xml";
00107             kdDebug( 7119 ) << "Looking for help in: " << file << endl;
00108             info.setFile(file);
00109             if (info.exists() && info.isFile() && info.isReadable())
00110                 return *it;
00111         }
00112     }
00113 
00114 
00115     return QString::null;
00116 }
00117 
00118 
00119 QString HelpProtocol::lookupFile(const QString &fname,
00120                                  const QString &query, bool &redirect)
00121 {
00122     redirect = false;
00123 
00124     QString path, result;
00125 
00126     path = fname;
00127 
00128     result = langLookup(path);
00129     if (result.isEmpty())
00130     {
00131         result = langLookup(path + "/index.html");
00132         if (!result.isEmpty())
00133     {
00134             KURL red( "help:/" );
00135             red.setPath( path + "/index.html" );
00136             red.setQuery( query );
00137             redirection(red);
00138             kdDebug( 7119 ) << "redirect to " << red.url() << endl;
00139             redirect = true;
00140     }
00141         else
00142     {
00143         unicodeError( i18n("There is no documentation available for %1." ).arg(path) );
00144         finished();
00145             return QString::null;
00146     }
00147     } else
00148         kdDebug( 7119 ) << "result " << result << endl;
00149 
00150     return result;
00151 }
00152 
00153 
00154 void HelpProtocol::unicodeError( const QString &t )
00155 {
00156    data(fromUnicode( QString(
00157         "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=%1\"></head>\n"
00158         "%2</html>" ).arg( QTextCodec::codecForLocale()->mimeName() ).arg( t ) ) );
00159 }
00160 
00161 HelpProtocol *slave = 0;
00162 
00163 HelpProtocol::HelpProtocol( bool ghelp, const QCString &pool, const QCString &app )
00164   : SlaveBase( ghelp ? "ghelp" : "help", pool, app ), mGhelp( ghelp )
00165 {
00166     slave = this;
00167 }
00168 
00169 void HelpProtocol::get( const KURL& url )
00170 {
00171     kdDebug( 7119 ) << "get: path=" << url.path()
00172               << " query=" << url.query() << endl;
00173 
00174     bool redirect;
00175     QString doc;
00176     doc = url.path();
00177 
00178     if ( !mGhelp ) {
00179         if (doc.at(0) != '/')
00180             doc = doc.prepend('/');
00181 
00182         if (doc.at(doc.length() - 1) == '/')
00183             doc += "index.html";
00184     }
00185 
00186     infoMessage(i18n("Looking up correct file"));
00187 
00188     if ( !mGhelp ) {
00189       doc = lookupFile(doc, url.query(), redirect);
00190 
00191       if (redirect)
00192       {
00193           finished();
00194           return;
00195       }
00196     }
00197 
00198     if (doc.isEmpty())
00199     {
00200         error( KIO::ERR_DOES_NOT_EXIST, url.url() );
00201         return;
00202     }
00203 
00204     mimeType("text/html");
00205     KURL target;
00206     target.setPath(doc);
00207     if (url.hasHTMLRef())
00208         target.setHTMLRef(url.htmlRef());
00209 
00210     kdDebug( 7119 ) << "target " << target.url() << endl;
00211 
00212     QString file = target.path();
00213     
00214     if ( mGhelp ) {
00215       if ( file.right( 4 ) != ".xml" ) {
00216          get_file( target );
00217          return;
00218       }
00219     } else {
00220         QString docbook_file = file.left(file.findRev('/')) + "/index.docbook";
00221         int last_slash = file.findRev('/');
00222         if (last_slash != -1 && last_slash != 0) {
00223           int slash2 = file.findRev('/', last_slash -1);
00224           if (slash2 != -1 && slash2 != 0) {
00225          int slash3 = file.findRev('/', slash2 - 1);
00226          if (slash3 != -1) {
00227            QString xml_file = file.left(file.findRev('/')) + "/" + file.mid(slash3 + 1, slash2 - (slash3 + 1)) + ".xml";
00228            kdDebug( 7119 ) << "xml_file " << xml_file << endl;
00229            QFileInfo fi(xml_file);
00230            if (fi.exists())
00231              docbook_file = xml_file;
00232          }
00233           }
00234         }
00235         if (!KStandardDirs::exists(file)) {
00236             file = docbook_file;
00237         } else {
00238             QFileInfo fi(file);
00239             if (fi.isDir()) {
00240                 file = docbook_file;
00241             } else {
00242                 if ( file.right( 5 ) != ".html" || !compareTimeStamps( file, docbook_file ) ) {
00243                     get_file( target );
00244                     return;
00245                 } else
00246                     file = docbook_file;
00247             }
00248         }
00249     }
00250 
00251     infoMessage(i18n("Preparing document"));
00252 
00253     if ( mGhelp ) {
00254         QString xsl = "customization/kde-nochunk.xsl";
00255         mParsed = transform(file, locate("dtd", xsl));
00256 
00257         kdDebug( 7119 ) << "parsed " << mParsed.length() << endl;
00258 
00259         if (mParsed.isEmpty()) {
00260             unicodeError( i18n( "The requested help file could not be parsed:<br>%1" ).arg( file ) );
00261         } else {
00262             int pos1 = mParsed.find( "charset=" );
00263             if ( pos1 > 0 ) {
00264               int pos2 = mParsed.find( '"', pos1 );
00265               if ( pos2 > 0 ) {
00266                 mParsed.replace( pos1, pos2 - pos1, "charset=UTF-8" );
00267               }
00268             }
00269             data( mParsed.utf8() );
00270         }
00271     } else {
00272 
00273         kdDebug( 7119 ) << "look for cache for " << file << endl;
00274 
00275         mParsed = lookForCache( file );
00276 
00277         kdDebug( 7119 ) << "cached parsed " << mParsed.length() << endl;
00278 
00279         if ( mParsed.isEmpty() ) {
00280             mParsed = transform(file, locate("dtd", "customization/kde-chunk.xsl"));
00281             if ( !mParsed.isEmpty() ) {
00282                 infoMessage( i18n( "Saving to cache" ) );
00283         QString cache;
00284         if (file.endsWith(".xml"))
00285             cache = file.left( file.length() - strlen ("xml") );
00286         else
00287             cache = file.left( file.length() - strlen ("docbook") );
00288                 saveToCache( mParsed, locateLocal( "cache",
00289                                                         "kio_help" + cache +
00290                                                         "cache.bz2" ) );
00291             }
00292         } else infoMessage( i18n( "Using cached version" ) );
00293 
00294         kdDebug( 7119 ) << "parsed " << mParsed.length() << endl;
00295 
00296         if (mParsed.isEmpty()) {
00297             unicodeError( i18n( "The requested help file could not be parsed:<br>%1" ).arg( file ) );
00298         } else {
00299             QString query = url.query(), anchor;
00300 
00301             // if we have a query, look if it contains an anchor
00302             if (!query.isEmpty())
00303                 if (query.left(8) == "?anchor=") {
00304                     anchor = query.mid(8).lower();
00305 
00306                 KURL redirURL(url);
00307 
00308                 redirURL.setQuery(QString::null);
00309                 redirURL.setHTMLRef(anchor);
00310                 redirection(redirURL);
00311                 finished();
00312                 return;
00313             }
00314             if (anchor.isEmpty() && url.hasHTMLRef())
00315             anchor = url.htmlRef();
00316 
00317             kdDebug( 7119 ) << "anchor: " << anchor << endl;
00318 
00319             if ( !anchor.isEmpty() )
00320             {
00321                 int index = 0;
00322                 while ( true ) {
00323                     index = mParsed.find( QRegExp( "<a name=" ), index);
00324                     if ( index == -1 ) {
00325                         kdDebug( 7119 ) << "no anchor\n";
00326                         break; // use whatever is the target, most likely index.html
00327                     }
00328 
00329                     if ( mParsed.mid( index, 11 + anchor.length() ).lower() ==
00330                          QString( "<a name=\"%1\">" ).arg( anchor ) )
00331                     {
00332                         index = mParsed.findRev( "<FILENAME filename=", index ) +
00333                                  strlen( "<FILENAME filename=\"" );
00334                         QString filename=mParsed.mid( index, 2000 );
00335                         filename = filename.left( filename.find( '\"' ) );
00336                         QString path = target.path();
00337                         path = path.left( path.findRev( '/' ) + 1) + filename;
00338                         kdDebug( 7119 ) << "anchor found in " << path <<endl;
00339                         target.setPath( path );
00340                         break;
00341                     }
00342                     index++;
00343                 }
00344             }
00345             emitFile( target );
00346         }
00347     }
00348 
00349     finished();
00350 }
00351 
00352 void HelpProtocol::emitFile( const KURL& url )
00353 {
00354     infoMessage(i18n("Looking up section"));
00355 
00356     QString filename = url.path().mid(url.path().findRev('/') + 1);
00357 
00358     int index = mParsed.find(QString("<FILENAME filename=\"%1\"").arg(filename));
00359     if (index == -1) {
00360         if ( filename == "index.html" ) {
00361             data( fromUnicode( mParsed ) );
00362             return;
00363         }
00364 
00365         unicodeError( i18n("Could not find filename %1 in %2.").arg(filename).arg( url.url() ) );
00366         return;
00367     }
00368 
00369     QString filedata = splitOut(mParsed, index);
00370     replaceCharsetHeader( filedata );
00371 
00372     data( fromUnicode( filedata ) );
00373     data( QByteArray() );
00374 }
00375 
00376 void HelpProtocol::mimetype( const KURL &)
00377 {
00378     mimeType("text/html");
00379     finished();
00380 }
00381 
00382 // Copied from kio_file to avoid redirects
00383 
00384 #define MAX_IPC_SIZE (1024*32)
00385 
00386 void HelpProtocol::get_file( const KURL& url )
00387 {
00388     kdDebug( 7119 ) << "get_file " << url.url() << endl;
00389 
00390     QCString _path( QFile::encodeName(url.path()));
00391     struct stat buff;
00392     if ( ::stat( _path.data(), &buff ) == -1 ) {
00393         if ( errno == EACCES )
00394            error( KIO::ERR_ACCESS_DENIED, url.path() );
00395         else
00396            error( KIO::ERR_DOES_NOT_EXIST, url.path() );
00397     return;
00398     }
00399 
00400     if ( S_ISDIR( buff.st_mode ) ) {
00401     error( KIO::ERR_IS_DIRECTORY, url.path() );
00402     return;
00403     }
00404     if ( S_ISFIFO( buff.st_mode ) || S_ISSOCK ( buff.st_mode ) ) {
00405     error( KIO::ERR_CANNOT_OPEN_FOR_READING, url.path() );
00406     return;
00407     }
00408 
00409     int fd = open( _path.data(), O_RDONLY);
00410     if ( fd < 0 ) {
00411     error( KIO::ERR_CANNOT_OPEN_FOR_READING, url.path() );
00412     return;
00413     }
00414 
00415     totalSize( buff.st_size );
00416     int processed_size = 0;
00417 
00418     char buffer[ MAX_IPC_SIZE ];
00419     QByteArray array;
00420 
00421     while( 1 )
00422     {
00423        int n = ::read( fd, buffer, MAX_IPC_SIZE );
00424        if (n == -1)
00425        {
00426           if (errno == EINTR)
00427               continue;
00428           error( KIO::ERR_COULD_NOT_READ, url.path());
00429           close(fd);
00430           return;
00431        }
00432        if (n == 0)
00433           break; // Finished
00434 
00435        array.setRawData(buffer, n);
00436        data( array );
00437        array.resetRawData(buffer, n);
00438 
00439        processed_size += n;
00440        processedSize( processed_size );
00441     }
00442 
00443     data( QByteArray() );
00444 
00445     close( fd );
00446 
00447     processedSize( buff.st_size );
00448 
00449     finished();
00450 }
KDE Home | KDE Accessibility Home | Description of Access Keys