Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / runtime / kioslave / man / kio_man.cpp
blobdeea95dd71b41b3884fbac9a0da190a0afc1ef14
1 /* This file is part of the KDE libraries
2 Copyright (c) 2000 Matthias Hoelzer-Kluepfel <mhk@caldera.de>
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <sys/stat.h>
22 #include <string.h>
23 #include <dirent.h>
25 #include <QByteArray>
26 #include <QDir>
27 #include <QFile>
28 #include <QTextStream>
29 #include <QDataStream>
30 #include <Qt3Support/Q3PtrList>
31 #include <QMap>
32 #include <QRegExp>
33 #include <QTextCodec>
35 #include <kdebug.h>
36 #include <kcomponentdata.h>
37 #include <kglobal.h>
38 #include <kstandarddirs.h>
39 #include <k3process.h>
40 #include <klocale.h>
41 #include <kmimetype.h>
43 #include "kio_man.h"
44 #include "kio_man.moc"
45 #include "man2html.h"
46 #include <assert.h>
47 #include <kfilterbase.h>
48 #include <kfilterdev.h>
50 using namespace KIO;
52 MANProtocol *MANProtocol::_self = 0;
54 #define SGML2ROFF_DIRS "/usr/lib/sgml"
57 * Drop trailing ".section[.gz]" from name
59 static
60 void stripExtension( QString *name )
62 int pos = name->length();
64 if ( name->indexOf(".gz", -3) != -1 )
65 pos -= 3;
66 else if ( name->indexOf(".z", -2, Qt::CaseInsensitive) != -1 )
67 pos -= 2;
68 else if ( name->indexOf(".bz2", -4) != -1 )
69 pos -= 4;
70 else if ( name->indexOf(".bz", -3) != -1 )
71 pos -= 3;
73 if ( pos > 0 )
74 pos = name->lastIndexOf('.', pos-1);
76 if ( pos > 0 )
77 name->truncate( pos );
80 static
81 bool parseUrl(const QString& _url, QString &title, QString &section)
83 section.clear();
85 QString url = _url;
86 if (url.at(0) == '/') {
87 if (KStandardDirs::exists(url)) {
88 title = url;
89 return true;
90 } else
92 // If the directory does not exist, then it is perhaps a normal man page
93 kDebug(7107) << url << " does not exist";
97 while (url.at(0) == '/')
98 url.remove(0,1);
100 title = url;
102 int pos = url.indexOf('(');
103 if (pos < 0)
104 return true;
106 title = title.left(pos);
108 section = url.mid(pos+1);
109 section = section.left(section.length()-1);
111 return true;
115 MANProtocol::MANProtocol(const QByteArray &pool_socket, const QByteArray &app_socket)
116 : QObject(), SlaveBase("man", pool_socket, app_socket)
118 assert(!_self);
119 _self = this;
120 const QString common_dir = KGlobal::dirs()->findResourceDir( "html", "en/common/kde-default.css" );
121 const QString strPath=QString( "file:%1/en/common" ).arg( common_dir );
122 m_htmlPath=strPath.toLocal8Bit(); // ### TODO encode for HTML
123 m_cssPath=strPath.toLocal8Bit(); // ### TODO encode for CSS
124 section_names << "1" << "2" << "3" << "3n" << "3p" << "4" << "5" << "6" << "7"
125 << "8" << "9" << "l" << "n";
126 m_manCSSFile = KStandardDirs::locate( "data", "kio_man/kio_man.css" );
129 MANProtocol *MANProtocol::self() { return _self; }
131 MANProtocol::~MANProtocol()
133 _self = 0;
136 void MANProtocol::parseWhatIs( QMap<QString, QString> &i, QTextStream &t, const QString &mark )
138 QRegExp re( mark );
139 QString l;
140 while ( !t.atEnd() )
142 l = t.readLine();
143 int pos = re.indexIn( l );
144 if (pos != -1)
146 QString names = l.left(pos);
147 QString descr = l.mid(pos + re.matchedLength());
148 while ((pos = names.indexOf(",")) != -1)
150 i[names.left(pos++)] = descr;
151 while (names[pos] == ' ')
152 pos++;
153 names = names.mid(pos);
155 i[names] = descr;
160 bool MANProtocol::addWhatIs(QMap<QString, QString> &i, const QString &name, const QString &mark)
162 QFile f(name);
163 if (!f.open(QIODevice::ReadOnly))
164 return false;
165 QTextStream t(&f);
166 parseWhatIs( i, t, mark );
167 return true;
170 QMap<QString, QString> MANProtocol::buildIndexMap(const QString &section)
172 QMap<QString, QString> i;
173 QStringList man_dirs = manDirectories();
174 // Supplementary places for whatis databases
175 man_dirs += m_mandbpath;
176 if (!man_dirs.contains("/var/cache/man"))
177 man_dirs << "/var/cache/man";
178 if (!man_dirs.contains("/var/catman"))
179 man_dirs << "/var/catman";
181 QStringList names;
182 names << "whatis.db" << "whatis";
183 QString mark = "\\s+\\(" + section + "[a-z]*\\)\\s+-\\s+";
185 for ( QStringList::ConstIterator it_dir = man_dirs.begin();
186 it_dir != man_dirs.end();
187 ++it_dir )
189 if ( QFile::exists( *it_dir ) ) {
190 QStringList::ConstIterator it_name;
191 for ( it_name = names.begin();
192 it_name != names.end();
193 it_name++ )
195 if (addWhatIs(i, (*it_dir) + '/' + (*it_name), mark))
196 break;
198 if ( it_name == names.end() ) {
199 K3Process proc;
200 proc << "whatis" << "-M" << (*it_dir) << "-w" << "*";
201 myStdStream.clear();
202 connect( &proc, SIGNAL( receivedStdout(K3Process *, char *, int ) ),
203 SLOT( slotGetStdOutput( K3Process *, char *, int ) ) );
204 proc.start( K3Process::Block, K3Process::Stdout );
205 QTextStream t( &myStdStream, QIODevice::ReadOnly );
206 parseWhatIs( i, t, mark );
210 return i;
213 QStringList MANProtocol::manDirectories()
215 checkManPaths();
217 // Build a list of man directories including translations
219 QStringList man_dirs;
221 for ( QStringList::ConstIterator it_dir = m_manpath.begin();
222 it_dir != m_manpath.end();
223 it_dir++ )
225 // Translated pages in "<mandir>/<lang>" if the directory
226 // exists
227 QStringList languages = KGlobal::locale()->languageList();
229 for (QStringList::ConstIterator it_lang = languages.begin();
230 it_lang != languages.end();
231 it_lang++ )
233 if ( !(*it_lang).isEmpty() && (*it_lang) != QString("C") ) {
234 QString dir = (*it_dir) + '/' + (*it_lang);
236 struct stat sbuf;
238 if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0
239 && S_ISDIR( sbuf.st_mode ) )
241 const QString p = QDir(dir).canonicalPath();
242 if (!man_dirs.contains(p)) man_dirs += p;
247 // Untranslated pages in "<mandir>"
248 const QString p = QDir(*it_dir).canonicalPath();
249 if (!man_dirs.contains(p)) man_dirs += p;
251 return man_dirs;
254 QStringList MANProtocol::findPages(const QString &_section,
255 const QString &title,
256 bool full_path)
258 QString section = _section;
260 QStringList list;
262 // kDebug() << "findPages '" << section << "' '" << title << "'\n";
263 if ( (!title.isEmpty()) && (title.at(0) == '/') ) {
264 list.append(title);
265 return list;
268 const QString star( "*" );
271 // Find man sections in this directory
273 QStringList sect_list;
274 if ( section.isEmpty() )
275 section = star;
277 if ( section != star )
280 // Section given as argument
282 sect_list += section;
283 while ( (!section.isEmpty()) && (section.at(section.length() - 1).isLetter()) ) {
284 section.truncate(section.length() - 1);
285 sect_list += section;
287 } else {
288 sect_list += section;
291 QStringList man_dirs = manDirectories();
294 // Find man pages in the sections listed above
296 for ( int i=0;i<sect_list.count(); i++)
298 QString it_s=sect_list.at(i);
299 QString it_real = it_s.toLower();
301 // Find pages
303 //kDebug(7107)<<"Before inner loop";
304 for ( QStringList::const_iterator it_dir = man_dirs.begin();
305 it_dir != man_dirs.end();
306 it_dir++ )
308 QString man_dir = (*it_dir);
310 // Sections = all sub directories "man*" and "sman*"
312 DIR *dp = ::opendir( QFile::encodeName( man_dir ) );
314 if ( !dp )
315 continue;
317 struct dirent *ep;
319 const QString man = QString("man");
320 const QString sman = QString("sman");
322 while ( (ep = ::readdir( dp )) != 0L ) {
324 const QString file = QFile::decodeName( ep->d_name );
325 QString sect = QString();
327 if ( file.startsWith( man ) )
328 sect = file.mid(3);
329 else if (file.startsWith(sman))
330 sect = file.mid(4);
332 if (sect.toLower()==it_real) it_real = sect;
334 // Only add sect if not already contained, avoid duplicates
335 if (!sect_list.contains(sect) && _section.isEmpty()) {
336 //kDebug() << "another section " << sect;
337 sect_list += sect;
341 //kDebug(7107) <<" after while loop";
342 ::closedir( dp );
343 #if 0
344 kDebug(7107)<<"================-";
345 kDebug(7107)<<"star=="<<star;
346 kDebug(7107)<<"it_s=="<<it_s;
347 kDebug(7107)<<"================+";
348 #endif
349 if ( it_s != star ) { // in that case we only look around for sections
350 //kDebug(7107)<<"Within if ( it_s != star )";
351 const QString dir = man_dir + QString("/man") + (it_real) + '/';
352 const QString sdir = man_dir + QString("/sman") + (it_real) + '/';
354 //kDebug(7107)<<"calling findManPagesInSection";
355 findManPagesInSection(dir, title, full_path, list);
356 findManPagesInSection(sdir, title, full_path, list);
358 kDebug(7107)<<"After if";
362 //kDebug(7107) << "finished " << list << " " << sect_list;
364 return list;
367 void MANProtocol::findManPagesInSection(const QString &dir, const QString &title, bool full_path, QStringList &list)
369 kDebug() << "findManPagesInSection " << dir << " " << title;
370 bool title_given = !title.isEmpty();
372 DIR *dp = ::opendir( QFile::encodeName( dir ) );
374 if ( !dp )
375 return;
377 struct dirent *ep;
379 while ( (ep = ::readdir( dp )) != 0L ) {
380 if ( ep->d_name[0] != '.' ) {
382 QString name = QFile::decodeName( ep->d_name );
384 // check title if we're looking for a specific page
385 if ( title_given ) {
386 if ( !name.startsWith( title ) ) {
387 continue;
389 else {
390 // beginning matches, do a more thorough check...
391 QString tmp_name = name;
392 stripExtension( &tmp_name );
393 if ( tmp_name != title )
394 continue;
398 if ( full_path )
399 name.prepend( dir );
401 list += name ;
404 ::closedir( dp );
407 void MANProtocol::output(const char *insert)
409 if (insert)
411 m_outputBuffer.write(insert,strlen(insert));
413 if (!insert || m_outputBuffer.pos() >= 2048)
415 m_outputBuffer.close();
416 data(m_outputBuffer.buffer());
417 m_outputBuffer.setData(QByteArray());
418 m_outputBuffer.open(QIODevice::WriteOnly);
422 #ifndef SIMPLE_MAN2HTML
423 // called by man2html
424 extern char *read_man_page(const char *filename)
426 return MANProtocol::self()->readManPage(filename);
429 // called by man2html
430 extern void output_real(const char *insert)
432 MANProtocol::self()->output(insert);
434 #endif
436 static QString text2html(const QString& txt)
438 QString reply = txt;
440 reply = reply.replace('&', "&amp;");
441 reply = reply.replace('<', "&lt;");
442 reply = reply.replace('>', "&gt;");
443 reply = reply.replace('"', "&dquot;");
444 reply = reply.replace('\'', "&quot;");
445 return reply;
449 void MANProtocol::get(const KUrl& url )
451 kDebug(7107) << "GET " << url.url();
453 QString title, section;
455 if (!parseUrl(url.path(), title, section))
457 showMainIndex();
458 return;
461 // tell the mimetype
462 mimeType("text/html");
464 // see if an index was requested
465 if (url.query().isEmpty() && (title.isEmpty() || title == "/" || title == "."))
467 if (section == "index" || section.isEmpty())
468 showMainIndex();
469 else
470 showIndex(section);
471 return;
474 const QStringList foundPages=findPages(section, title);
475 bool pageFound=true;
477 if (foundPages.isEmpty())
479 outputError(i18n("No man page matching to %1 found.<br /><br />"
480 "Check that you have not mistyped the name of the page that you want.<br />"
481 "Be careful that you must take care about upper case and lower case characters!<br />"
482 "If everything looks correct, then perhaps you need to set a better search path "
483 "for man pages, be it by the environment variable MANPATH or a matching file "
484 "in the directory /etc .", text2html(title)));
485 pageFound=false;
487 else if (foundPages.count()>1)
489 pageFound=false;
490 //check for the case that there is foo.1 and foo.1.gz found:
491 // ### TODO make it more generic (other extensions)
492 if ((foundPages.count()==2) &&
493 (((foundPages[0]+".gz") == foundPages[1]) ||
494 (foundPages[0] == (foundPages[1]+".gz"))))
495 pageFound=true;
496 else
497 outputMatchingPages(foundPages);
499 //yes, we found exactly one man page
501 if (pageFound)
503 setResourcePath(m_htmlPath,m_cssPath);
504 m_outputBuffer.open(QIODevice::WriteOnly);
505 const QByteArray filename=QFile::encodeName(foundPages[0]);
506 char *buf = readManPage(filename);
508 if (!buf)
510 outputError(i18n("Open of %1 failed.", title));
511 finished();
512 return;
514 // will call output_real
515 scan_man_page(buf);
516 delete [] buf;
518 output(0); // flush
520 m_outputBuffer.close();
521 data(m_outputBuffer.buffer());
522 m_outputBuffer.setData(QByteArray());
523 // tell we are done
524 data(QByteArray());
526 finished();
529 void MANProtocol::slotGetStdOutput(K3Process* /* p */, char *s, int len)
531 myStdStream += QString::fromLocal8Bit(s, len);
534 char *MANProtocol::readManPage(const char *_filename)
536 QByteArray filename = _filename;
538 char *buf = NULL;
540 /* Determine type of man page file by checking its path. Determination by
541 * MIME type with KMimeType doesn't work reliablely. E.g., Solaris 7:
542 * /usr/man/sman7fs/pcfs.7fs -> text/x-csrc : WRONG
543 * If the path name constains the string sman, assume that it's SGML and
544 * convert it to roff format (used on Solaris). */
545 //QString file_mimetype = KMimeType::findByPath(QString(filename), 0, false)->name();
546 if (QString(filename).contains("sman", Qt::CaseInsensitive)) //file_mimetype == "text/html" || )
548 myStdStream =QString();
549 K3Process proc;
551 /* Determine path to sgml2roff, if not already done. */
552 getProgramPath();
553 proc << mySgml2RoffPath << filename;
555 connect(&proc, SIGNAL(receivedStdout (K3Process *, char *, int)),
556 this, SLOT(slotGetStdOutput(K3Process *, char *, int)));
557 proc.start(K3Process::Block, K3Process::All);
559 const QByteArray cstr=myStdStream.toLatin1();
560 const int len = cstr.size()-1;
561 buf = new char[len + 4];
562 memmove(buf + 1, cstr.data(), len);
563 buf[0]=buf[len]='\n'; // Start and end with a end of line
564 buf[len+1]=buf[len+2]='\0'; // Two additional NUL characters at end
566 else
568 if (QDir::isRelativePath(filename)) {
569 kDebug(7107) << "relative " << filename;
570 filename = QDir::cleanPath(lastdir + '/' + filename).toUtf8();
571 if (!KStandardDirs::exists(filename)) { // exists perhaps with suffix
572 lastdir = filename.left(filename.lastIndexOf('/'));
573 QDir mandir(lastdir);
574 mandir.setNameFilters(QStringList() << (filename.mid(filename.lastIndexOf('/') + 1) + ".*"));
575 filename = lastdir + '/' + QFile::encodeName(mandir.entryList().first());
577 kDebug(7107) << "resolved to " << filename;
579 lastdir = filename.left(filename.lastIndexOf('/'));
581 QIODevice *fd= KFilterDev::deviceForFile(filename);
583 if ( !fd || !fd->open(QIODevice::ReadOnly))
585 delete fd;
586 return 0;
588 QByteArray array(fd->readAll());
589 kDebug(7107) << "read " << array.size();
590 fd->close();
591 delete fd;
593 if (array.isEmpty())
594 return 0;
596 const int len = array.size();
597 buf = new char[len + 4];
598 memmove(buf + 1, array.data(), len);
599 buf[0]=buf[len]='\n'; // Start and end with a end of line
600 buf[len+1]=buf[len+2]='\0'; // Two NUL characters at end
602 return buf;
606 void MANProtocol::outputError(const QString& errmsg)
608 QByteArray array;
609 QTextStream os(&array, QIODevice::WriteOnly);
610 os.setCodec( "UTF-8" );
612 os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl;
613 os << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" << endl;
614 os << "<title>" << i18n("Man output") << "</title>\n" << endl;
615 if ( !m_manCSSFile.isEmpty() )
616 os << "<link href=\"file:///" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl;
617 os << "</head>" << endl;
618 os << "<body>" << i18n("<h1>KDE Man Viewer Error</h1>") << errmsg << "</body>" << endl;
619 os << "</html>" << endl;
621 data(array);
624 void MANProtocol::outputMatchingPages(const QStringList &matchingPages)
626 QByteArray array;
627 QTextStream os(&array, QIODevice::WriteOnly);
628 os.setCodec( "UTF-8" );
630 os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl;
631 os << "<html>\n<head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">"<<endl;
632 os << "<title>" << i18n("Man output") <<"</title>" << endl;
633 if ( !m_manCSSFile.isEmpty() )
634 os << "<link href=\"file:///" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl;
635 os << "</head>" <<endl;
636 os << "<body><h1>" << i18n("There is more than one matching man page.");
637 os << "</h1>\n<ul>\n";
639 int acckey=1;
640 for (QStringList::ConstIterator it = matchingPages.begin(); it != matchingPages.end(); ++it)
642 os<<"<li><a href='man:"<<(*it)<<"' accesskey='"<< acckey <<"'>"<< *it <<"</a><br>\n<br>\n";
643 acckey++;
645 os << "</ul>\n";
646 os << "<hr>\n";
647 os << "<p>" << i18n("Note: if you read a man page in your language,"
648 " be aware it can contain some mistakes or be obsolete."
649 " In case of doubt, you should have a look at the English version.") << "</p>";
651 os << "</body>\n</html>"<<endl;
653 data(array);
654 finished();
657 void MANProtocol::stat( const KUrl& url)
659 kDebug(7107) << "ENTERING STAT " << url.url();
661 QString title, section;
663 if (!parseUrl(url.path(), title, section))
665 error(KIO::ERR_MALFORMED_URL, url.url());
666 return;
669 kDebug(7107) << "URL " << url.url() << " parsed to title='" << title << "' section=" << section;
671 UDSEntry entry;
672 entry.insert(KIO::UDSEntry::UDS_NAME, title);
673 entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
675 QString newUrl = "man:"+title;
676 if (!section.isEmpty())
677 newUrl += QString("(%1)").arg(section);
678 entry.insert(KIO::UDSEntry::UDS_URL, newUrl);
680 entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("text/html"));
682 statEntry(entry);
684 finished();
688 extern "C"
691 int KDE_EXPORT kdemain( int argc, char **argv ) {
693 KComponentData componentData("kio_man");
695 kDebug(7107) << "STARTING " << getpid();
697 if (argc != 4)
699 fprintf(stderr, "Usage: kio_man protocol domain-socket1 domain-socket2\n");
700 exit(-1);
703 MANProtocol slave(argv[2], argv[3]);
704 slave.dispatchLoop();
706 kDebug(7107) << "Done";
708 return 0;
713 void MANProtocol::mimetype(const KUrl & /*url*/)
715 mimeType("text/html");
716 finished();
719 static QString sectionName(const QString& section)
721 if (section == "1")
722 return i18n("User Commands");
723 else if (section == "2")
724 return i18n("System Calls");
725 else if (section == "3")
726 return i18n("Subroutines");
727 else if (section == "3p")
728 return i18n("Perl Modules");
729 else if (section == "3n")
730 return i18n("Network Functions");
731 else if (section == "4")
732 return i18n("Devices");
733 else if (section == "5")
734 return i18n("File Formats");
735 else if (section == "6")
736 return i18n("Games");
737 else if (section == "7")
738 return i18n("Miscellaneous");
739 else if (section == "8")
740 return i18n("System Administration");
741 else if (section == "9")
742 return i18n("Kernel");
743 else if (section == "l")
744 return i18n("Local Documentation");
745 else if (section == "n")
746 return i18n("New");
748 return QString();
751 QStringList MANProtocol::buildSectionList(const QStringList& dirs) const
753 QStringList l;
755 for (QStringList::ConstIterator it = section_names.begin();
756 it != section_names.end(); ++it)
758 for (QStringList::ConstIterator dir = dirs.begin();
759 dir != dirs.end(); ++dir)
761 QDir d((*dir)+"/man"+(*it));
762 if (d.exists())
764 l << *it;
765 break;
769 return l;
772 void MANProtocol::showMainIndex()
774 QByteArray array;
775 QTextStream os(&array, QIODevice::WriteOnly);
776 os.setCodec( "UTF-8" );
778 // print header
779 os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl;
780 os << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" << endl;
781 os << "<title>" << i18n("UNIX Manual Index") << "</title>" << endl;
782 if (!m_manCSSFile.isEmpty())
783 os << "<link href=\"file:///" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl;
784 os << "</head>" << endl;
785 os << "<body><h1>" << i18n("UNIX Manual Index") << "</h1>" << endl;
787 // ### TODO: why still the environment variable
788 const QString sectList = getenv("MANSECT");
789 QStringList sections;
790 if (sectList.isEmpty())
791 sections = buildSectionList(manDirectories());
792 else
793 sections = sectList.split( ':');
795 os << "<table>" << endl;
797 QStringList::ConstIterator it;
798 for (it = sections.begin(); it != sections.end(); ++it)
799 os << "<tr><td><a href=\"man:(" << *it << ")\" accesskey=\"" <<
800 (((*it).length()==1)?(*it):(*it).right(1))<<"\">" << i18n("Section ")
801 << *it << "</a></td><td>&nbsp;</td><td> " << sectionName(*it) << "</td></tr>" << endl;
803 os << "</table>" << endl;
805 // print footer
806 os << "</body></html>" << endl;
808 data(array);
809 finished();
812 void MANProtocol::constructPath(QStringList& constr_path, QStringList constr_catmanpath)
814 QMap<QString, QString> manpath_map;
815 QMap<QString, QString> mandb_map;
817 // Add paths from /etc/man.conf
819 // Explicit manpaths may be given by lines starting with "MANPATH" or
820 // "MANDATORY_MANPATH" (depending on system ?).
821 // Mappings from $PATH to manpath are given by lines starting with
822 // "MANPATH_MAP"
824 QRegExp manpath_regex( "^MANPATH\\s" );
825 QRegExp mandatory_regex( "^MANDATORY_MANPATH\\s" );
826 QRegExp manpath_map_regex( "^MANPATH_MAP\\s" );
827 QRegExp mandb_map_regex( "^MANDB_MAP\\s" );
828 //QRegExp section_regex( "^SECTION\\s" );
829 QRegExp space_regex( "\\s+" ); // for parsing manpath map
831 QFile mc("/etc/man.conf"); // Caldera
832 if (!mc.exists())
833 mc.setFileName("/etc/manpath.config"); // SuSE, Debian
834 if (!mc.exists())
835 mc.setFileName("/etc/man.config"); // Mandrake
837 if (mc.open(QIODevice::ReadOnly))
839 QTextStream is(&mc);
840 is.setCodec( QTextCodec::codecForLocale () );
842 while (!is.atEnd())
844 const QString line = is.readLine();
845 if ( manpath_regex.indexIn(line) == 0 )
847 const QString path = line.mid(8).trimmed();
848 constr_path += path;
850 else if ( mandatory_regex.indexIn(line) == 0 )
852 const QString path = line.mid(18).trimmed();
853 constr_path += path;
855 else if ( manpath_map_regex.indexIn(line) == 0 )
857 // The entry is "MANPATH_MAP <path> <manpath>"
858 const QStringList mapping =
859 line.split( space_regex);
861 if ( mapping.count() == 3 )
863 const QString dir = QDir::cleanPath( mapping[1] );
864 const QString mandir = QDir::cleanPath( mapping[2] );
866 manpath_map[ dir ] = mandir;
869 else if ( mandb_map_regex.indexIn(line) == 0 )
871 // The entry is "MANDB_MAP <manpath> <catmanpath>"
872 const QStringList mapping =
873 line.split( space_regex);
875 if ( mapping.count() == 3 )
877 const QString mandir = QDir::cleanPath( mapping[1] );
878 const QString catmandir = QDir::cleanPath( mapping[2] );
880 mandb_map[ mandir ] = catmandir;
883 /* sections are not used
884 else if ( section_regex.find(line, 0) == 0 )
886 if ( !conf_section.isEmpty() )
887 conf_section += ':';
888 conf_section += line.mid(8).trimmed();
892 mc.close();
895 // Default paths
896 static const char *manpaths[] = {
897 "/usr/X11/man",
898 "/usr/X11R6/man",
899 "/usr/man",
900 "/usr/local/man",
901 "/usr/exp/man",
902 "/usr/openwin/man",
903 "/usr/dt/man",
904 "/opt/freetool/man",
905 "/opt/local/man",
906 "/usr/tex/man",
907 "/usr/www/man",
908 "/usr/lang/man",
909 "/usr/gnu/man",
910 "/usr/share/man",
911 "/usr/motif/man",
912 "/usr/titools/man",
913 "/usr/sunpc/man",
914 "/usr/ncd/man",
915 "/usr/newsprint/man",
916 NULL };
919 int i = 0;
920 while (manpaths[i]) {
921 if ( constr_path.indexOf( QString( manpaths[i] ) ) == -1 )
922 constr_path += QString( manpaths[i] );
923 i++;
926 // Directories in $PATH
927 // - if a manpath mapping exists, use that mapping
928 // - if a directory "<path>/man" or "<path>/../man" exists, add it
929 // to the man path (the actual existence check is done further down)
931 if ( ::getenv("PATH") ) {
932 const QStringList path =
933 QString::fromLocal8Bit( ::getenv("PATH") ).split( ":", QString::SkipEmptyParts );
935 for ( QStringList::const_iterator it = path.begin();
936 it != path.end();
937 ++it )
939 const QString dir = QDir::cleanPath( *it );
940 QString mandir = manpath_map[ dir ];
942 if ( !mandir.isEmpty() ) {
943 // a path mapping exists
944 if ( constr_path.indexOf( mandir ) == -1 )
945 constr_path += mandir;
947 else {
948 // no manpath mapping, use "<path>/man" and "<path>/../man"
950 mandir = dir + QString( "/man" );
951 if ( constr_path.indexOf( mandir ) == -1 )
952 constr_path += mandir;
954 int pos = dir.lastIndexOf( '/' );
955 if ( pos > 0 ) {
956 mandir = dir.left( pos ) + QString("/man");
957 if ( constr_path.indexOf( mandir ) == -1 )
958 constr_path += mandir;
961 QString catmandir = mandb_map[ mandir ];
962 if ( !mandir.isEmpty() )
964 if ( constr_catmanpath.indexOf( catmandir ) == -1 )
965 constr_catmanpath += catmandir;
967 else
969 // What is the default mapping?
970 catmandir = mandir;
971 catmandir.replace("/usr/share/","/var/cache/");
972 if ( constr_catmanpath.indexOf( catmandir ) == -1 )
973 constr_catmanpath += catmandir;
979 void MANProtocol::checkManPaths()
981 static bool inited = false;
983 if (inited)
984 return;
986 inited = true;
988 const QString manpath_env = QString::fromLocal8Bit( ::getenv("MANPATH") );
989 //QString mansect_env = QString::fromLocal8Bit( ::getenv("MANSECT") );
991 // Decide if $MANPATH is enough on its own or if it should be merged
992 // with the constructed path.
993 // A $MANPATH starting or ending with ":", or containing "::",
994 // should be merged with the constructed path.
996 bool construct_path = false;
998 if ( manpath_env.isEmpty()
999 || manpath_env[0] == ':'
1000 || manpath_env[manpath_env.length()-1] == ':'
1001 || manpath_env.contains( "::" ) )
1003 construct_path = true; // need to read config file
1006 // Constucted man path -- consists of paths from
1007 // /etc/man.conf
1008 // default dirs
1009 // $PATH
1010 QStringList constr_path;
1011 QStringList constr_catmanpath; // catmanpath
1013 QString conf_section;
1015 if ( construct_path )
1017 constructPath(constr_path, constr_catmanpath);
1020 m_mandbpath=constr_catmanpath;
1022 // Merge $MANPATH with the constructed path to form the
1023 // actual manpath.
1025 // The merging syntax with ":" and "::" in $MANPATH will be
1026 // satisfied if any empty string in path_list_env (there
1027 // should be 1 or 0) is replaced by the constructed path.
1029 const QStringList path_list_env = manpath_env.split( ':', QString::KeepEmptyParts);
1031 for ( QStringList::const_iterator it = path_list_env.begin();
1032 it != path_list_env.end();
1033 ++it )
1035 struct stat sbuf;
1037 QString dir = (*it);
1039 if ( !dir.isEmpty() ) {
1040 // Add dir to the man path if it exists
1041 if ( m_manpath.indexOf( dir ) == -1 ) {
1042 if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0
1043 && S_ISDIR( sbuf.st_mode ) )
1045 m_manpath += dir;
1049 else {
1050 // Insert constructed path ($MANPATH was empty, or
1051 // there was a ":" at an end or "::")
1053 for ( QStringList::Iterator it2 = constr_path.begin();
1054 it2 != constr_path.end();
1055 it2++ )
1057 dir = (*it2);
1059 if ( !dir.isEmpty() ) {
1060 if ( m_manpath.indexOf( dir ) == -1 ) {
1061 if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0
1062 && S_ISDIR( sbuf.st_mode ) )
1064 m_manpath += dir;
1072 /* sections are not used
1073 // Sections
1074 QStringList m_mansect = mansect_env.split( ':', QString::KeepEmptyParts);
1076 const char* default_sect[] =
1077 { "1", "2", "3", "4", "5", "6", "7", "8", "9", "n", 0L };
1079 for ( int i = 0; default_sect[i] != 0L; i++ )
1080 if ( m_mansect.indexOf( QString( default_sect[i] ) ) == -1 )
1081 m_mansect += QString( default_sect[i] );
1087 //#define _USE_OLD_CODE
1089 #ifdef _USE_OLD_CODE
1090 #ifdef __GNUC__
1091 #warning "using old code"
1092 #endif
1093 #else
1095 // Define this, if you want to compile with qsort from stdlib.h
1096 // else the Qt Heapsort will be used.
1097 // Note, qsort seems to be a bit faster (~10%) on a large man section
1098 // eg. man section 3
1099 #define _USE_QSORT
1101 // Setup my own structure, with char pointers.
1102 // from now on only pointers are copied, no strings
1104 // containing the whole path string,
1105 // the beginning of the man page name
1106 // and the length of the name
1107 struct man_index_t {
1108 char *manpath; // the full path including man file
1109 const char *manpage_begin; // pointer to the begin of the man file name in the path
1110 int manpage_len; // len of the man file name
1112 typedef man_index_t *man_index_ptr;
1114 #ifdef _USE_QSORT
1115 int compare_man_index(const void *s1, const void *s2)
1117 struct man_index_t *m1 = *(struct man_index_t **)s1;
1118 struct man_index_t *m2 = *(struct man_index_t **)s2;
1119 int i;
1120 // Compare the names of the pages
1121 // with the shorter length.
1122 // Man page names are not '\0' terminated, so
1123 // this is a bit tricky
1124 if ( m1->manpage_len > m2->manpage_len)
1126 i = qstrnicmp( m1->manpage_begin,
1127 m2->manpage_begin,
1128 m2->manpage_len);
1129 if (!i)
1130 return 1;
1131 return i;
1134 if ( m1->manpage_len < m2->manpage_len)
1136 i = qstrnicmp( m1->manpage_begin,
1137 m2->manpage_begin,
1138 m1->manpage_len);
1139 if (!i)
1140 return -1;
1141 return i;
1144 return qstrnicmp( m1->manpage_begin,
1145 m2->manpage_begin,
1146 m1->manpage_len);
1149 #else /* !_USE_QSORT */
1150 #ifdef __GNUC__
1151 #warning using heapsort
1152 #endif
1153 // Set up my own man page list,
1154 // with a special compare function to sort itself
1155 typedef QList<struct man_index_t*> QManIndexListBase;
1156 typedef QList<struct man_index_t*>::Iterator QManIndexListIterator;
1158 class QManIndexList : public QManIndexListBase
1160 public:
1161 private:
1162 int compareItems( Q3PtrCollection::Item s1, Q3PtrCollection::Item s2 )
1164 struct man_index_t *m1 = (struct man_index_t *)s1;
1165 struct man_index_t *m2 = (struct man_index_t *)s2;
1166 int i;
1167 // compare the names of the pages
1168 // with the shorter length
1169 if (m1->manpage_len > m2->manpage_len)
1171 i = qstrnicmp(m1->manpage_begin,
1172 m2->manpage_begin,
1173 m2->manpage_len);
1174 if (!i)
1175 return 1;
1176 return i;
1179 if (m1->manpage_len > m2->manpage_len)
1182 i = qstrnicmp(m1->manpage_begin,
1183 m2->manpage_begin,
1184 m1->manpage_len);
1185 if (!i)
1186 return -1;
1187 return i;
1190 return qstrnicmp(m1->manpage_begin,
1191 m2->manpage_begin,
1192 m1->manpage_len);
1196 #endif /* !_USE_QSORT */
1197 #endif /* !_USE_OLD_CODE */
1202 void MANProtocol::showIndex(const QString& section)
1204 QByteArray array;
1205 QTextStream os(&array, QIODevice::WriteOnly);
1206 os.setCodec( "UTF-8" );
1208 // print header
1209 os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl;
1210 os << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" << endl;
1211 os << "<title>" << i18n("UNIX Manual Index") << "</title>" << endl;
1212 if ( !m_manCSSFile.isEmpty() )
1213 os << "<link href=\"file:///" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl;
1214 os << "</head>" << endl;
1215 os << "<body><div class=\"secidxmain\">" << endl;
1216 os << "<h1>" << i18n( "Index for Section %1: %2", section, sectionName(section)) << "</h1>" << endl;
1218 // compose list of search paths -------------------------------------------------------------
1220 checkManPaths();
1221 infoMessage(i18n("Generating Index"));
1223 // search for the man pages
1224 QStringList pages = findPages( section, QString() );
1226 QMap<QString, QString> indexmap = buildIndexMap(section);
1228 // print out the list
1229 os << "<table>" << endl;
1231 #ifdef _USE_OLD_CODE
1232 pages.sort();
1234 QMap<QString, QString> pagemap;
1236 QStringList::ConstIterator page;
1237 for (page = pages.begin(); page != pages.end(); ++page)
1239 QString fileName = *page;
1241 stripExtension( &fileName );
1243 pos = fileName.lastIndexOf('/');
1244 if (pos > 0)
1245 fileName = fileName.mid(pos+1);
1247 if (!fileName.isEmpty())
1248 pagemap[fileName] = *page;
1252 for (QMap<QString,QString>::ConstIterator it = pagemap.begin();
1253 it != pagemap.end(); ++it)
1255 os << "<tr><td><a href=\"man:" << it.data() << "\">\n"
1256 << it.key() << "</a></td><td>&nbsp;</td><td> "
1257 << (indexmap.contains(it.key()) ? indexmap[it.key()] : "" )
1258 << "</td></tr>" << endl;
1261 #else /* ! _USE_OLD_CODE */
1263 #ifdef _USE_QSORT
1265 int listlen = pages.count();
1266 man_index_ptr *indexlist = new man_index_ptr[listlen];
1267 listlen = 0;
1269 #else /* !_USE_QSORT */
1271 QManIndexList manpages;
1272 manpages.setAutoDelete(true);
1274 #endif /* _USE_QSORT */
1276 QStringList::const_iterator page;
1277 for (page = pages.begin(); page != pages.end(); ++page)
1279 // I look for the beginning of the man page name
1280 // i.e. "bla/pagename.3.gz" by looking for the last "/"
1281 // Then look for the end of the name by searching backwards
1282 // for the last ".", not counting zip extensions.
1283 // If the len of the name is >0,
1284 // store it in the list structure, to be sorted later
1286 char *manpage_end;
1287 struct man_index_t *manindex = new man_index_t;
1288 manindex->manpath = strdup((*page).toUtf8());
1290 manindex->manpage_begin = strrchr(manindex->manpath, '/');
1291 if (manindex->manpage_begin)
1293 manindex->manpage_begin++;
1294 assert(manindex->manpage_begin >= manindex->manpath);
1296 else
1298 manindex->manpage_begin = manindex->manpath;
1299 assert(manindex->manpage_begin >= manindex->manpath);
1302 // Skip extension ".section[.gz]"
1304 char *begin = (char*)(manindex->manpage_begin);
1305 int len = strlen( begin );
1306 char *end = begin+(len-1);
1308 if ( len >= 3 && strcmp( end-2, ".gz" ) == 0 )
1309 end -= 3;
1310 else if ( len >= 2 && strcmp( end-1, ".Z" ) == 0 )
1311 end -= 2;
1312 else if ( len >= 2 && strcmp( end-1, ".z" ) == 0 )
1313 end -= 2;
1314 else if ( len >= 4 && strcmp( end-3, ".bz2" ) == 0 )
1315 end -= 4;
1317 while ( end >= begin && *end != '.' )
1318 end--;
1320 if ( end < begin )
1321 manpage_end = 0;
1322 else
1323 manpage_end = end;
1325 if (NULL == manpage_end)
1327 // no '.' ending ???
1328 // set the pointer past the end of the filename
1329 manindex->manpage_len = (*page).length();
1330 manindex->manpage_len -= (manindex->manpage_begin - manindex->manpath);
1331 assert(manindex->manpage_len >= 0);
1333 else
1335 manindex->manpage_len = (manpage_end - manindex->manpage_begin);
1336 assert(manindex->manpage_len >= 0);
1339 if (0 < manindex->manpage_len)
1342 #ifdef _USE_QSORT
1344 indexlist[listlen] = manindex;
1345 listlen++;
1347 #else /* !_USE_QSORT */
1349 manpages.append(manindex);
1351 #endif /* _USE_QSORT */
1357 // Now do the sorting on the page names
1358 // and the printout afterwards
1359 // While printing avoid duplicate man page names
1362 struct man_index_t dummy_index = {0l,0l,0};
1363 struct man_index_t *last_index = &dummy_index;
1365 #ifdef _USE_QSORT
1367 // sort and print
1368 qsort(indexlist, listlen, sizeof(struct man_index_t *), compare_man_index);
1370 QChar firstchar, tmp;
1371 QString indexLine="<div class=\"secidxshort\">\n";
1372 if (indexlist[0]->manpage_len>0)
1374 firstchar=QChar((indexlist[0]->manpage_begin)[0]).toLower();
1376 const QString appendixstr = QString(
1377 " [<a href=\"#%1\" accesskey=\"%2\">%3</a>]\n"
1378 ).arg(firstchar).arg(firstchar).arg(firstchar);
1379 indexLine.append(appendixstr);
1381 os << "<tr><td class=\"secidxnextletter\"" << " colspan=\"3\">\n <a name=\""
1382 << firstchar << "\">" << firstchar <<"</a>\n</td></tr>" << endl;
1384 for (int i=0; i<listlen; i++)
1386 struct man_index_t *manindex = indexlist[i];
1388 // qstrncmp():
1389 // "last_man" has already a \0 string ending, but
1390 // "manindex->manpage_begin" has not,
1391 // so do compare at most "manindex->manpage_len" of the strings.
1392 if (last_index->manpage_len == manindex->manpage_len &&
1393 !qstrncmp(last_index->manpage_begin,
1394 manindex->manpage_begin,
1395 manindex->manpage_len)
1398 continue;
1401 tmp=QChar((manindex->manpage_begin)[0]).toLower();
1402 if (firstchar != tmp)
1404 firstchar = tmp;
1405 os << "<tr><td class=\"secidxnextletter\"" << " colspan=\"3\">\n <a name=\""
1406 << firstchar << "\">" << firstchar << "</a>\n</td></tr>" << endl;
1408 const QString appendixstr = QString(
1409 " [<a href=\"#%1\" accesskey=\"%2\">%3</a>]\n"
1410 ).arg(firstchar).arg(firstchar).arg(firstchar);
1411 indexLine.append(appendixstr);
1413 os << "<tr><td><a href=\"man:"
1414 << manindex->manpath << "\">\n";
1416 ((char *)manindex->manpage_begin)[manindex->manpage_len] = '\0';
1417 os << manindex->manpage_begin
1418 << "</a></td><td>&nbsp;</td><td> "
1419 << (indexmap.contains(manindex->manpage_begin) ? indexmap[manindex->manpage_begin] : "" )
1420 << "</td></tr>" << endl;
1421 last_index = manindex;
1423 indexLine.append("</div>");
1425 for (int i=0; i<listlen; i++) {
1426 ::free(indexlist[i]->manpath); // allocated by strdup
1427 delete indexlist[i];
1430 delete [] indexlist;
1432 #else /* !_USE_QSORT */
1434 manpages.sort(); // using
1436 for (QManIndexListIterator mit(manpages);
1437 mit.current();
1438 ++mit )
1440 struct man_index_t *manindex = mit.current();
1442 // qstrncmp():
1443 // "last_man" has already a \0 string ending, but
1444 // "manindex->manpage_begin" has not,
1445 // so do compare at most "manindex->manpage_len" of the strings.
1446 if (last_index->manpage_len == manindex->manpage_len &&
1447 !qstrncmp(last_index->manpage_begin,
1448 manindex->manpage_begin,
1449 manindex->manpage_len)
1452 continue;
1455 os << "<tr><td><a href=\"man:"
1456 << manindex->manpath << "\">\n";
1458 manindex->manpage_begin[manindex->manpage_len] = '\0';
1459 os << manindex->manpage_begin
1460 << "</a></td><td>&nbsp;</td><td> "
1461 << (indexmap.contains(manindex->manpage_begin) ? indexmap[manindex->manpage_begin] : "" )
1462 << "</td></tr>" << endl;
1463 last_index = manindex;
1465 #endif /* _USE_QSORT */
1466 #endif /* _USE_OLD_CODE */
1468 os << "</table></div>" << endl;
1470 os << indexLine << endl;
1472 // print footer
1473 os << "</body></html>" << endl;
1475 infoMessage(QString());
1476 mimeType("text/html");
1477 data(array);
1478 finished();
1481 void MANProtocol::listDir(const KUrl &url)
1483 kDebug( 7107 ) << "ENTER listDir: " << url.prettyUrl();
1485 QString title;
1486 QString section;
1488 if ( !parseUrl(url.path(), title, section) ) {
1489 error( KIO::ERR_MALFORMED_URL, url.url() );
1490 return;
1493 QStringList list = findPages( section, QString(), false );
1495 UDSEntryList uds_entry_list;
1497 QStringList::Iterator it = list.begin();
1498 const QStringList::Iterator end = list.end();
1500 for ( ; it != end; ++it ) {
1501 stripExtension( &(*it) );
1503 UDSEntry uds_entry;
1504 uds_entry.insert( KIO::UDSEntry::UDS_NAME, *it );
1505 uds_entry_list.append( uds_entry );
1508 listEntries( uds_entry_list );
1509 finished();
1512 void MANProtocol::getProgramPath()
1514 if (!mySgml2RoffPath.isEmpty())
1515 return;
1517 mySgml2RoffPath = KGlobal::dirs()->findExe("sgml2roff");
1518 if (!mySgml2RoffPath.isEmpty())
1519 return;
1521 /* sgml2roff isn't found in PATH. Check some possible locations where it may be found. */
1522 mySgml2RoffPath = KGlobal::dirs()->findExe("sgml2roff", QString(SGML2ROFF_DIRS));
1523 if (!mySgml2RoffPath.isEmpty())
1524 return;
1526 /* Cannot find sgml2roff program: */
1527 outputError(i18n("Could not find the sgml2roff program on your system. Please install it, if necessary, and extend the search path by adjusting the environment variable PATH before starting KDE."));
1528 finished();
1529 exit();