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