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
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
00071
00072 for (QStringList::Iterator it = langs.begin(); it != langs.end(); ++it)
00073 if ( *it == "en_US" )
00074 *it = "en";
00075
00076
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
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
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;
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
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;
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 }