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