add more spacing
[personal-kdebase.git] / runtime / kioslave / man / kio_man.cpp
blob42cd844ca408c38502ba6b671f89f15032bb5613
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.
20 #include "kio_man.h"
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <sys/stat.h>
25 #include <string.h>
26 #include <dirent.h>
28 #include <QByteArray>
29 #include <QDir>
30 #include <QFile>
31 #include <QTextStream>
32 #include <QDataStream>
33 #include <QMap>
34 #include <QRegExp>
35 #include <QTextCodec>
37 #include <kdebug.h>
38 #include <kcomponentdata.h>
39 #include <kglobal.h>
40 #include <kstandarddirs.h>
41 #include <KProcess>
42 #include <klocale.h>
43 #include <kmimetype.h>
45 #include "kio_man.moc"
46 #include "man2html.h"
47 #include <assert.h>
48 #include <kfilterbase.h>
49 #include <kfilterdev.h>
51 using namespace KIO;
53 MANProtocol *MANProtocol::_self = 0;
55 #define SGML2ROFF_DIRS "/usr/lib/sgml"
58 * Drop trailing ".section[.gz]" from name
60 static
61 void stripExtension( QString *name )
63 int pos = name->length();
65 if ( name->indexOf(".gz", -3) != -1 )
66 pos -= 3;
67 else if ( name->indexOf(".z", -2, Qt::CaseInsensitive) != -1 )
68 pos -= 2;
69 else if ( name->indexOf(".bz2", -4) != -1 )
70 pos -= 4;
71 else if ( name->indexOf(".bz", -3) != -1 )
72 pos -= 3;
74 if ( pos > 0 )
75 pos = name->lastIndexOf('.', pos-1);
77 if ( pos > 0 )
78 name->truncate( pos );
81 static
82 bool parseUrl(const QString& _url, QString &title, QString &section)
84 section.clear();
86 QString url = _url;
87 if (url.isEmpty() || url.at(0) == '/') {
88 if (url.isEmpty() || KStandardDirs::exists(url)) {
89 // man:/usr/share/man/man1/ls.1.gz is a valid file
90 title = url;
91 return true;
92 } else
94 // If the directory does not exist, then it is perhaps a normal man page
95 kDebug(7107) << url << " does not exist";
99 while (!url.isEmpty() && url.at(0) == '/')
100 url.remove(0,1);
102 title = url;
104 int pos = url.indexOf('(');
105 if (pos < 0)
106 return true; // man:ls -> title=ls
108 title = title.left(pos);
110 section = url.mid(pos+1);
112 pos = section.indexOf(')');
113 if (pos >= 0) {
114 if (pos < section.length() - 2 && title.isEmpty()) {
115 title = section.mid(pos + 2);
117 section = section.left(pos);
120 // man:ls(2) -> title="ls", section="2"
122 return true;
126 MANProtocol::MANProtocol(const QByteArray &pool_socket, const QByteArray &app_socket)
127 : QObject(), SlaveBase("man", pool_socket, app_socket)
129 assert(!_self);
130 _self = this;
131 const QString common_dir = KGlobal::dirs()->findResourceDir( "html", "en/common/kde-default.css" );
132 const QString strPath=QString( "file:%1/en/common" ).arg( common_dir );
133 m_cssPath=strPath.toLocal8Bit(); // ### TODO encode for CSS
134 section_names << "1" << "2" << "3" << "3n" << "3p" << "4" << "5" << "6" << "7"
135 << "8" << "9" << "l" << "n";
137 QString cssPath(KStandardDirs::locate( "data", "kio_man/kio_man.css" ));
138 KUrl cssUrl(KUrl::fromPath(cssPath));
139 m_manCSSFile = cssUrl.url().toUtf8();
142 MANProtocol *MANProtocol::self() { return _self; }
144 MANProtocol::~MANProtocol()
146 _self = 0;
149 void MANProtocol::parseWhatIs( QMap<QString, QString> &i, QTextStream &t, const QString &mark )
151 QRegExp re( mark );
152 QString l;
153 while ( !t.atEnd() )
155 l = t.readLine();
156 int pos = re.indexIn( l );
157 if (pos != -1)
159 QString names = l.left(pos);
160 QString descr = l.mid(pos + re.matchedLength());
161 while ((pos = names.indexOf(",")) != -1)
163 i[names.left(pos++)] = descr;
164 while (names[pos] == ' ')
165 pos++;
166 names = names.mid(pos);
168 i[names] = descr;
173 bool MANProtocol::addWhatIs(QMap<QString, QString> &i, const QString &name, const QString &mark)
175 QFile f(name);
176 if (!f.open(QIODevice::ReadOnly))
177 return false;
178 QTextStream t(&f);
179 parseWhatIs( i, t, mark );
180 return true;
183 QMap<QString, QString> MANProtocol::buildIndexMap(const QString &section)
185 QMap<QString, QString> i;
186 QStringList man_dirs = manDirectories();
187 // Supplementary places for whatis databases
188 man_dirs += m_mandbpath;
189 if (!man_dirs.contains("/var/cache/man"))
190 man_dirs << "/var/cache/man";
191 if (!man_dirs.contains("/var/catman"))
192 man_dirs << "/var/catman";
194 QStringList names;
195 names << "whatis.db" << "whatis";
196 QString mark = "\\s+\\(" + section + "[a-z]*\\)\\s+-\\s+";
198 for ( QStringList::ConstIterator it_dir = man_dirs.constBegin();
199 it_dir != man_dirs.constEnd();
200 ++it_dir )
202 if ( QFile::exists( *it_dir ) ) {
203 QStringList::ConstIterator it_name;
204 for ( it_name = names.constBegin();
205 it_name != names.constEnd();
206 it_name++ )
208 if (addWhatIs(i, (*it_dir) + '/' + (*it_name), mark))
209 break;
211 if ( it_name == names.constEnd() ) {
212 KProcess proc;
213 proc << "whatis" << "-M" << (*it_dir) << "-w" << "*";
214 proc.setOutputChannelMode( KProcess::OnlyStdoutChannel );
215 proc.execute();
216 QTextStream t( proc.readAllStandardOutput(), QIODevice::ReadOnly );
217 parseWhatIs( i, t, mark );
221 return i;
224 QStringList MANProtocol::manDirectories()
226 checkManPaths();
228 // Build a list of man directories including translations
230 QStringList man_dirs;
232 for ( QStringList::ConstIterator it_dir = m_manpath.constBegin();
233 it_dir != m_manpath.constEnd();
234 it_dir++ )
236 // Translated pages in "<mandir>/<lang>" if the directory
237 // exists
238 QStringList languages = KGlobal::locale()->languageList();
240 for (QStringList::ConstIterator it_lang = languages.constBegin();
241 it_lang != languages.constEnd();
242 it_lang++ )
244 if ( !(*it_lang).isEmpty() && (*it_lang) != QString("C") ) {
245 QString dir = (*it_dir) + '/' + (*it_lang);
247 struct stat sbuf;
249 if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0
250 && S_ISDIR( sbuf.st_mode ) )
252 const QString p = QDir(dir).canonicalPath();
253 if (!man_dirs.contains(p)) man_dirs += p;
258 // Untranslated pages in "<mandir>"
259 const QString p = QDir(*it_dir).canonicalPath();
260 if (!man_dirs.contains(p)) man_dirs += p;
262 return man_dirs;
265 QStringList MANProtocol::findPages(const QString &_section,
266 const QString &title,
267 bool full_path)
269 QString section = _section;
271 QStringList list;
273 // kDebug() << "findPages '" << section << "' '" << title << "'\n";
274 if ( (!title.isEmpty()) && (title.at(0) == '/') ) {
275 list.append(title);
276 return list;
279 const QString star( "*" );
282 // Find man sections in this directory
284 QStringList sect_list;
285 if ( section.isEmpty() )
286 section = star;
288 if ( section != star )
291 // Section given as argument
293 sect_list += section;
294 while ( (!section.isEmpty()) && (section.at(section.length() - 1).isLetter()) ) {
295 section.truncate(section.length() - 1);
296 sect_list += section;
298 } else {
299 sect_list += section;
302 QStringList man_dirs = manDirectories();
305 // Find man pages in the sections listed above
307 for ( int i=0;i<sect_list.count(); i++)
309 QString it_s=sect_list.at(i);
310 QString it_real = it_s.toLower();
312 // Find pages
314 //kDebug(7107)<<"Before inner loop";
315 for ( QStringList::const_iterator it_dir = man_dirs.constBegin();
316 it_dir != man_dirs.constEnd();
317 it_dir++ )
319 QString man_dir = (*it_dir);
321 // Sections = all sub directories "man*" and "sman*"
323 DIR *dp = ::opendir( QFile::encodeName( man_dir ) );
325 if ( !dp )
326 continue;
328 struct dirent *ep;
330 const QString man = QString("man");
331 const QString sman = QString("sman");
333 while ( (ep = ::readdir( dp )) != 0L ) {
335 const QString file = QFile::decodeName( ep->d_name );
336 QString sect = QString();
338 if ( file.startsWith( man ) )
339 sect = file.mid(3);
340 else if (file.startsWith(sman))
341 sect = file.mid(4);
343 if (sect.toLower()==it_real) it_real = sect;
345 // Only add sect if not already contained, avoid duplicates
346 if (!sect_list.contains(sect) && _section.isEmpty()) {
347 //kDebug() << "another section " << sect;
348 sect_list += sect;
352 //kDebug(7107) <<" after while loop";
353 ::closedir( dp );
354 #if 0
355 kDebug(7107)<<"================-";
356 kDebug(7107)<<"star=="<<star;
357 kDebug(7107)<<"it_s=="<<it_s;
358 kDebug(7107)<<"================+";
359 #endif
360 if ( it_s != star ) { // in that case we only look around for sections
361 //kDebug(7107)<<"Within if ( it_s != star )";
362 const QString dir = man_dir + QString("/man") + (it_real) + '/';
363 const QString sdir = man_dir + QString("/sman") + (it_real) + '/';
365 //kDebug(7107)<<"calling findManPagesInSection";
366 findManPagesInSection(dir, title, full_path, list);
367 findManPagesInSection(sdir, title, full_path, list);
369 kDebug(7107)<<"After if";
373 //kDebug(7107) << "finished " << list << " " << sect_list;
375 return list;
378 void MANProtocol::findManPagesInSection(const QString &dir, const QString &title, bool full_path, QStringList &list)
380 kDebug() << "findManPagesInSection " << dir << " " << title;
381 bool title_given = !title.isEmpty();
383 DIR *dp = ::opendir( QFile::encodeName( dir ) );
385 if ( !dp )
386 return;
388 struct dirent *ep;
390 while ( (ep = ::readdir( dp )) != 0L ) {
391 if ( ep->d_name[0] != '.' ) {
393 QString name = QFile::decodeName( ep->d_name );
395 // check title if we're looking for a specific page
396 if ( title_given ) {
397 if ( !name.startsWith( title ) ) {
398 continue;
400 else {
401 // beginning matches, do a more thorough check...
402 QString tmp_name = name;
403 stripExtension( &tmp_name );
404 if ( tmp_name != title )
405 continue;
409 if ( full_path )
410 name.prepend( dir );
412 list += name ;
415 ::closedir( dp );
418 void MANProtocol::output(const char *insert)
420 if (insert)
422 m_outputBuffer.write(insert,strlen(insert));
424 if (!insert || m_outputBuffer.pos() >= 2048)
426 m_outputBuffer.close();
427 data(m_outputBuffer.buffer());
428 m_outputBuffer.setData(QByteArray());
429 m_outputBuffer.open(QIODevice::WriteOnly);
433 #ifndef SIMPLE_MAN2HTML
434 // called by man2html
435 extern char *read_man_page(const char *filename)
437 return MANProtocol::self()->readManPage(filename);
440 // called by man2html
441 extern void output_real(const char *insert)
443 MANProtocol::self()->output(insert);
445 #endif
447 static QString text2html(const QString& txt)
449 QString reply = txt;
451 reply = reply.replace('&', "&amp;");
452 reply = reply.replace('<', "&lt;");
453 reply = reply.replace('>', "&gt;");
454 reply = reply.replace('"', "&dquot;");
455 reply = reply.replace('\'', "&quot;");
456 return reply;
460 void MANProtocol::get(const KUrl& url )
462 kDebug(7107) << "GET " << url.url();
464 QString title, section;
466 if (!parseUrl(url.path(), title, section))
468 showMainIndex();
469 return;
472 // tell the mimetype
473 mimeType("text/html");
475 // see if an index was requested
476 if (url.query().isEmpty() && (title.isEmpty() || title == "/" || title == "."))
478 if (section == "index" || section.isEmpty())
479 showMainIndex();
480 else
481 showIndex(section);
482 return;
485 const QStringList foundPages=findPages(section, title);
486 bool pageFound=true;
488 if (foundPages.isEmpty())
490 outputError(i18n("No man page matching to %1 found.<br /><br />"
491 "Check that you have not mistyped the name of the page that you want.<br />"
492 "Check that you have typed the name using the correct upper and lower case characters.<br />"
493 "If everything looks correct, then you may need to improve the search path "
494 "for man pages; either using the environment variable MANPATH or using a matching file "
495 "in the /etc directory.", text2html(title)));
496 pageFound=false;
498 else if (foundPages.count()>1)
500 pageFound=false;
501 //check for the case that there is foo.1 and foo.1.gz found:
502 // ### TODO make it more generic (other extensions)
503 if ((foundPages.count()==2) &&
504 (((foundPages[0]+".gz") == foundPages[1]) ||
505 (foundPages[0] == (foundPages[1]+".gz"))))
506 pageFound=true;
507 else
508 outputMatchingPages(foundPages);
510 //yes, we found exactly one man page
512 if (pageFound)
514 setResourcePath(m_cssPath);
515 setCssFile(m_manCSSFile);
516 m_outputBuffer.open(QIODevice::WriteOnly);
517 const QByteArray filename=QFile::encodeName(foundPages[0]);
518 char *buf = readManPage(filename);
520 if (!buf)
522 outputError(i18n("Open of %1 failed.", title));
523 finished();
524 return;
526 // will call output_real
527 scan_man_page(buf);
528 delete [] buf;
530 output(0); // flush
532 m_outputBuffer.close();
533 data(m_outputBuffer.buffer());
534 m_outputBuffer.setData(QByteArray());
535 // tell we are done
536 data(QByteArray());
538 finished();
541 char *MANProtocol::readManPage(const char *_filename)
543 QByteArray filename = _filename;
545 char *buf = NULL;
547 /* Determine type of man page file by checking its path. Determination by
548 * MIME type with KMimeType doesn't work reliablely. E.g., Solaris 7:
549 * /usr/man/sman7fs/pcfs.7fs -> text/x-csrc : WRONG
550 * If the path name constains the string sman, assume that it's SGML and
551 * convert it to roff format (used on Solaris). */
552 //QString file_mimetype = KMimeType::findByPath(QString(filename), 0, false)->name();
553 if (QString(filename).contains("sman", Qt::CaseInsensitive)) //file_mimetype == "text/html" || )
555 KProcess proc;
556 // Determine path to sgml2roff, if not already done.
557 getProgramPath();
558 proc << mySgml2RoffPath << filename;
559 proc.setOutputChannelMode( KProcess::OnlyStdoutChannel );
560 proc.execute();
561 const QByteArray cstr = proc.readAllStandardOutput();
562 const int len = cstr.size()-1;
563 buf = new char[len + 4];
564 memmove(buf + 1, cstr.data(), len);
565 buf[0]=buf[len]='\n'; // Start and end with a end of line
566 buf[len+1]=buf[len+2]='\0'; // Two additional NUL characters at end
568 else
570 if (QDir::isRelativePath(filename)) {
571 kDebug(7107) << "relative " << filename;
572 filename = QDir::cleanPath(lastdir + '/' + filename).toUtf8();
573 if (!KStandardDirs::exists(filename)) { // exists perhaps with suffix
574 lastdir = filename.left(filename.lastIndexOf('/'));
575 QDir mandir(lastdir);
576 mandir.setNameFilters(QStringList() << (filename.mid(filename.lastIndexOf('/') + 1) + ".*"));
577 filename = lastdir + '/' + QFile::encodeName(mandir.entryList().first());
579 kDebug(7107) << "resolved to " << filename;
581 lastdir = filename.left(filename.lastIndexOf('/'));
583 QIODevice *fd= KFilterDev::deviceForFile(filename);
585 if ( !fd || !fd->open(QIODevice::ReadOnly))
587 delete fd;
588 return 0;
590 QByteArray array(fd->readAll());
591 kDebug(7107) << "read " << array.size();
592 fd->close();
593 delete fd;
595 if (array.isEmpty())
596 return 0;
598 const int len = array.size();
599 buf = new char[len + 4];
600 memmove(buf + 1, array.data(), len);
601 buf[0]=buf[len]='\n'; // Start and end with a end of line
602 buf[len+1]=buf[len+2]='\0'; // Two NUL characters at end
604 return buf;
608 void MANProtocol::outputError(const QString& errmsg)
610 QByteArray array;
611 QTextStream os(&array, QIODevice::WriteOnly);
612 os.setCodec( "UTF-8" );
614 os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl;
615 os << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" << endl;
616 os << "<title>" << i18n("Man output") << "</title>\n" << endl;
617 if ( !m_manCSSFile.isEmpty() )
618 os << "<link href=\"" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl;
619 os << "</head>" << endl;
620 os << "<body>" << i18n("<h1>KDE Man Viewer Error</h1>") << errmsg << "</body>" << endl;
621 os << "</html>" << endl;
623 data(array);
626 void MANProtocol::outputMatchingPages(const QStringList &matchingPages)
628 QByteArray array;
629 QTextStream os(&array, QIODevice::WriteOnly);
630 os.setCodec( "UTF-8" );
632 os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl;
633 os << "<html>\n<head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">"<<endl;
634 os << "<title>" << i18n("Man output") <<"</title>" << endl;
635 if ( !m_manCSSFile.isEmpty() )
636 os << "<link href=\"" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl;
637 os << "</head>" <<endl;
638 os << "<body><h1>" << i18n("There is more than one matching man page.");
639 os << "</h1>\n<ul>\n";
641 int acckey=1;
642 for (QStringList::ConstIterator it = matchingPages.begin(); it != matchingPages.end(); ++it)
644 os<<"<li><a href='man:"<<(*it)<<"' accesskey='"<< acckey <<"'>"<< *it <<"</a><br>\n<br>\n";
645 acckey++;
647 os << "</ul>\n";
648 os << "<hr>\n";
649 os << "<p>" << i18n("Note: if you read a man page in your language,"
650 " be aware it can contain some mistakes or be obsolete."
651 " In case of doubt, you should have a look at the English version.") << "</p>";
653 os << "</body>\n</html>"<<endl;
655 data(array);
656 finished();
659 void MANProtocol::stat( const KUrl& url)
661 kDebug(7107) << "ENTERING STAT " << url.url();
663 QString title, section;
665 if (!parseUrl(url.path(), title, section))
667 error(KIO::ERR_MALFORMED_URL, url.url());
668 return;
671 kDebug(7107) << "URL " << url.url() << " parsed to title='" << title << "' section=" << section;
673 UDSEntry entry;
674 entry.insert(KIO::UDSEntry::UDS_NAME, title);
675 entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
677 #if 0 // not useful, is it?
678 QString newUrl = "man:"+title;
679 if (!section.isEmpty())
680 newUrl += QString("(%1)").arg(section);
681 entry.insert(KIO::UDSEntry::UDS_URL, newUrl);
682 #endif
684 entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("text/html"));
686 statEntry(entry);
688 finished();
692 extern "C"
695 int KDE_EXPORT kdemain( int argc, char **argv ) {
697 KComponentData componentData("kio_man");
699 kDebug(7107) << "STARTING";
701 if (argc != 4)
703 fprintf(stderr, "Usage: kio_man protocol domain-socket1 domain-socket2\n");
704 exit(-1);
707 MANProtocol slave(argv[2], argv[3]);
708 slave.dispatchLoop();
710 kDebug(7107) << "Done";
712 return 0;
717 void MANProtocol::mimetype(const KUrl & /*url*/)
719 mimeType("text/html");
720 finished();
723 static QString sectionName(const QString& section)
725 if (section == "1")
726 return i18n("User Commands");
727 else if (section == "2")
728 return i18n("System Calls");
729 else if (section == "3")
730 return i18n("Subroutines");
731 else if (section == "3p")
732 return i18n("Perl Modules");
733 else if (section == "3n")
734 return i18n("Network Functions");
735 else if (section == "4")
736 return i18n("Devices");
737 else if (section == "5")
738 return i18n("File Formats");
739 else if (section == "6")
740 return i18n("Games");
741 else if (section == "7")
742 return i18n("Miscellaneous");
743 else if (section == "8")
744 return i18n("System Administration");
745 else if (section == "9")
746 return i18n("Kernel");
747 else if (section == "l")
748 return i18n("Local Documentation");
749 else if (section == "n")
750 return i18n("New");
752 return QString();
755 QStringList MANProtocol::buildSectionList(const QStringList& dirs) const
757 QStringList l;
759 for (QStringList::ConstIterator it = section_names.begin();
760 it != section_names.end(); ++it)
762 for (QStringList::ConstIterator dir = dirs.begin();
763 dir != dirs.end(); ++dir)
765 QDir d((*dir)+"/man"+(*it));
766 if (d.exists())
768 l << *it;
769 break;
773 return l;
776 void MANProtocol::showMainIndex()
778 QByteArray array;
779 QTextStream os(&array, QIODevice::WriteOnly);
780 os.setCodec( "UTF-8" );
782 // print header
783 os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl;
784 os << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" << endl;
785 os << "<title>" << i18n("UNIX Manual Index") << "</title>" << endl;
786 if (!m_manCSSFile.isEmpty())
787 os << "<link href=\"" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl;
788 os << "</head>" << endl;
789 os << "<body><h1>" << i18n("UNIX Manual Index") << "</h1>" << endl;
791 // ### TODO: why still the environment variable
792 const QString sectList = getenv("MANSECT");
793 QStringList sections;
794 if (sectList.isEmpty())
795 sections = buildSectionList(manDirectories());
796 else
797 sections = sectList.split( ':');
799 os << "<table>" << endl;
801 QStringList::ConstIterator it;
802 for (it = sections.constBegin(); it != sections.constEnd(); ++it)
803 os << "<tr><td><a href=\"man:(" << *it << ")\" accesskey=\"" <<
804 (((*it).length()==1)?(*it):(*it).right(1))<<"\">" << i18n("Section %1", *it)
805 << "</a></td><td>&nbsp;</td><td> " << sectionName(*it) << "</td></tr>" << endl;
807 os << "</table>" << endl;
809 // print footer
810 os << "</body></html>" << endl;
812 data(array);
813 finished();
816 void MANProtocol::constructPath(QStringList& constr_path, QStringList constr_catmanpath)
818 QMap<QString, QString> manpath_map;
819 QMap<QString, QString> mandb_map;
821 // Add paths from /etc/man.conf
823 // Explicit manpaths may be given by lines starting with "MANPATH" or
824 // "MANDATORY_MANPATH" (depending on system ?).
825 // Mappings from $PATH to manpath are given by lines starting with
826 // "MANPATH_MAP"
828 QRegExp manpath_regex( "^MANPATH\\s" );
829 QRegExp mandatory_regex( "^MANDATORY_MANPATH\\s" );
830 QRegExp manpath_map_regex( "^MANPATH_MAP\\s" );
831 QRegExp mandb_map_regex( "^MANDB_MAP\\s" );
832 //QRegExp section_regex( "^SECTION\\s" );
833 QRegExp space_regex( "\\s+" ); // for parsing manpath map
835 QFile mc("/etc/man.conf"); // Caldera
836 if (!mc.exists())
837 mc.setFileName("/etc/manpath.config"); // SuSE, Debian
838 if (!mc.exists())
839 mc.setFileName("/etc/man.config"); // Mandrake
841 if (mc.open(QIODevice::ReadOnly))
843 QTextStream is(&mc);
844 is.setCodec( QTextCodec::codecForLocale () );
846 while (!is.atEnd())
848 const QString line = is.readLine();
849 if ( manpath_regex.indexIn(line) == 0 )
851 const QString path = line.mid(8).trimmed();
852 constr_path += path;
854 else if ( mandatory_regex.indexIn(line) == 0 )
856 const QString path = line.mid(18).trimmed();
857 constr_path += path;
859 else if ( manpath_map_regex.indexIn(line) == 0 )
861 // The entry is "MANPATH_MAP <path> <manpath>"
862 const QStringList mapping =
863 line.split( space_regex);
865 if ( mapping.count() == 3 )
867 const QString dir = QDir::cleanPath( mapping[1] );
868 const QString mandir = QDir::cleanPath( mapping[2] );
870 manpath_map[ dir ] = mandir;
873 else if ( mandb_map_regex.indexIn(line) == 0 )
875 // The entry is "MANDB_MAP <manpath> <catmanpath>"
876 const QStringList mapping =
877 line.split( space_regex);
879 if ( mapping.count() == 3 )
881 const QString mandir = QDir::cleanPath( mapping[1] );
882 const QString catmandir = QDir::cleanPath( mapping[2] );
884 mandb_map[ mandir ] = catmandir;
887 /* sections are not used
888 else if ( section_regex.find(line, 0) == 0 )
890 if ( !conf_section.isEmpty() )
891 conf_section += ':';
892 conf_section += line.mid(8).trimmed();
896 mc.close();
899 // Default paths
900 static const char *manpaths[] = {
901 "/usr/X11/man",
902 "/usr/X11R6/man",
903 "/usr/man",
904 "/usr/local/man",
905 "/usr/exp/man",
906 "/usr/openwin/man",
907 "/usr/dt/man",
908 "/opt/freetool/man",
909 "/opt/local/man",
910 "/usr/tex/man",
911 "/usr/www/man",
912 "/usr/lang/man",
913 "/usr/gnu/man",
914 "/usr/share/man",
915 "/usr/motif/man",
916 "/usr/titools/man",
917 "/usr/sunpc/man",
918 "/usr/ncd/man",
919 "/usr/newsprint/man",
920 NULL };
923 int i = 0;
924 while (manpaths[i]) {
925 if ( constr_path.indexOf( QString( manpaths[i] ) ) == -1 )
926 constr_path += QString( manpaths[i] );
927 i++;
930 // Directories in $PATH
931 // - if a manpath mapping exists, use that mapping
932 // - if a directory "<path>/man" or "<path>/../man" exists, add it
933 // to the man path (the actual existence check is done further down)
935 if ( ::getenv("PATH") ) {
936 const QStringList path =
937 QString::fromLocal8Bit( ::getenv("PATH") ).split( ":", QString::SkipEmptyParts );
939 for ( QStringList::const_iterator it = path.constBegin();
940 it != path.constEnd();
941 ++it )
943 const QString dir = QDir::cleanPath( *it );
944 QString mandir = manpath_map[ dir ];
946 if ( !mandir.isEmpty() ) {
947 // a path mapping exists
948 if ( constr_path.indexOf( mandir ) == -1 )
949 constr_path += mandir;
951 else {
952 // no manpath mapping, use "<path>/man" and "<path>/../man"
954 mandir = dir + QString( "/man" );
955 if ( constr_path.indexOf( mandir ) == -1 )
956 constr_path += mandir;
958 int pos = dir.lastIndexOf( '/' );
959 if ( pos > 0 ) {
960 mandir = dir.left( pos ) + QString("/man");
961 if ( constr_path.indexOf( mandir ) == -1 )
962 constr_path += mandir;
965 QString catmandir = mandb_map[ mandir ];
966 if ( !mandir.isEmpty() )
968 if ( constr_catmanpath.indexOf( catmandir ) == -1 )
969 constr_catmanpath += catmandir;
971 else
973 // What is the default mapping?
974 catmandir = mandir;
975 catmandir.replace("/usr/share/","/var/cache/");
976 if ( constr_catmanpath.indexOf( catmandir ) == -1 )
977 constr_catmanpath += catmandir;
983 void MANProtocol::checkManPaths()
985 static bool inited = false;
987 if (inited)
988 return;
990 inited = true;
992 const QString manpath_env = QString::fromLocal8Bit( ::getenv("MANPATH") );
993 //QString mansect_env = QString::fromLocal8Bit( ::getenv("MANSECT") );
995 // Decide if $MANPATH is enough on its own or if it should be merged
996 // with the constructed path.
997 // A $MANPATH starting or ending with ":", or containing "::",
998 // should be merged with the constructed path.
1000 bool construct_path = false;
1002 if ( manpath_env.isEmpty()
1003 || manpath_env[0] == ':'
1004 || manpath_env[manpath_env.length()-1] == ':'
1005 || manpath_env.contains( "::" ) )
1007 construct_path = true; // need to read config file
1010 // Constucted man path -- consists of paths from
1011 // /etc/man.conf
1012 // default dirs
1013 // $PATH
1014 QStringList constr_path;
1015 QStringList constr_catmanpath; // catmanpath
1017 QString conf_section;
1019 if ( construct_path )
1021 constructPath(constr_path, constr_catmanpath);
1024 m_mandbpath=constr_catmanpath;
1026 // Merge $MANPATH with the constructed path to form the
1027 // actual manpath.
1029 // The merging syntax with ":" and "::" in $MANPATH will be
1030 // satisfied if any empty string in path_list_env (there
1031 // should be 1 or 0) is replaced by the constructed path.
1033 const QStringList path_list_env = manpath_env.split( ':', QString::KeepEmptyParts);
1035 for ( QStringList::const_iterator it = path_list_env.constBegin();
1036 it != path_list_env.constEnd();
1037 ++it )
1039 struct stat sbuf;
1041 QString dir = (*it);
1043 if ( !dir.isEmpty() ) {
1044 // Add dir to the man path if it exists
1045 if ( m_manpath.indexOf( dir ) == -1 ) {
1046 if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0
1047 && S_ISDIR( sbuf.st_mode ) )
1049 m_manpath += dir;
1053 else {
1054 // Insert constructed path ($MANPATH was empty, or
1055 // there was a ":" at an end or "::")
1057 for ( QStringList::const_iterator it2 = constr_path.constBegin();
1058 it2 != constr_path.constEnd();
1059 it2++ )
1061 dir = (*it2);
1063 if ( !dir.isEmpty() ) {
1064 if ( m_manpath.indexOf( dir ) == -1 ) {
1065 if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0
1066 && S_ISDIR( sbuf.st_mode ) )
1068 m_manpath += dir;
1076 /* sections are not used
1077 // Sections
1078 QStringList m_mansect = mansect_env.split( ':', QString::KeepEmptyParts);
1080 const char* default_sect[] =
1081 { "1", "2", "3", "4", "5", "6", "7", "8", "9", "n", 0L };
1083 for ( int i = 0; default_sect[i] != 0L; i++ )
1084 if ( m_mansect.indexOf( QString( default_sect[i] ) ) == -1 )
1085 m_mansect += QString( default_sect[i] );
1091 //#define _USE_OLD_CODE
1093 #ifdef _USE_OLD_CODE
1094 #ifdef __GNUC__
1095 #warning "using old code"
1096 #endif
1097 #else
1099 // Define this, if you want to compile with qsort from stdlib.h
1100 // else the Qt Heapsort will be used.
1101 // Note, qsort seems to be a bit faster (~10%) on a large man section
1102 // eg. man section 3
1103 #define _USE_QSORT
1105 // Setup my own structure, with char pointers.
1106 // from now on only pointers are copied, no strings
1108 // containing the whole path string,
1109 // the beginning of the man page name
1110 // and the length of the name
1111 struct man_index_t {
1112 char *manpath; // the full path including man file
1113 const char *manpage_begin; // pointer to the begin of the man file name in the path
1114 int manpage_len; // len of the man file name
1116 typedef man_index_t *man_index_ptr;
1118 #ifdef _USE_QSORT
1119 int compare_man_index(const void *s1, const void *s2)
1121 struct man_index_t *m1 = *(struct man_index_t **)s1;
1122 struct man_index_t *m2 = *(struct man_index_t **)s2;
1123 int i;
1124 // Compare the names of the pages
1125 // with the shorter length.
1126 // Man page names are not '\0' terminated, so
1127 // this is a bit tricky
1128 if ( m1->manpage_len > m2->manpage_len)
1130 i = qstrnicmp( m1->manpage_begin,
1131 m2->manpage_begin,
1132 m2->manpage_len);
1133 if (!i)
1134 return 1;
1135 return i;
1138 if ( m1->manpage_len < m2->manpage_len)
1140 i = qstrnicmp( m1->manpage_begin,
1141 m2->manpage_begin,
1142 m1->manpage_len);
1143 if (!i)
1144 return -1;
1145 return i;
1148 return qstrnicmp( m1->manpage_begin,
1149 m2->manpage_begin,
1150 m1->manpage_len);
1153 #else /* !_USE_QSORT */
1154 #ifdef __GNUC__
1155 #warning using heapsort
1156 #endif
1157 // Set up my own man page list,
1158 // with a special compare function to sort itself
1159 typedef QList<struct man_index_t*> QManIndexListBase;
1160 typedef QList<struct man_index_t*>::Iterator QManIndexListIterator;
1162 class QManIndexList : public QManIndexListBase
1164 public:
1165 private:
1166 int compareItems( Q3PtrCollection::Item s1, Q3PtrCollection::Item s2 )
1168 struct man_index_t *m1 = (struct man_index_t *)s1;
1169 struct man_index_t *m2 = (struct man_index_t *)s2;
1170 int i;
1171 // compare the names of the pages
1172 // with the shorter length
1173 if (m1->manpage_len > m2->manpage_len)
1175 i = qstrnicmp(m1->manpage_begin,
1176 m2->manpage_begin,
1177 m2->manpage_len);
1178 if (!i)
1179 return 1;
1180 return i;
1183 if (m1->manpage_len > m2->manpage_len)
1186 i = qstrnicmp(m1->manpage_begin,
1187 m2->manpage_begin,
1188 m1->manpage_len);
1189 if (!i)
1190 return -1;
1191 return i;
1194 return qstrnicmp(m1->manpage_begin,
1195 m2->manpage_begin,
1196 m1->manpage_len);
1200 #endif /* !_USE_QSORT */
1201 #endif /* !_USE_OLD_CODE */
1206 void MANProtocol::showIndex(const QString& section)
1208 QByteArray array;
1209 QTextStream os(&array, QIODevice::WriteOnly);
1210 os.setCodec( "UTF-8" );
1212 // print header
1213 os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl;
1214 os << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" << endl;
1215 os << "<title>" << i18n("UNIX Manual Index") << "</title>" << endl;
1216 if ( !m_manCSSFile.isEmpty() )
1217 os << "<link href=\"" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl;
1218 os << "</head>" << endl;
1219 os << "<body><div class=\"secidxmain\">" << endl;
1220 os << "<h1>" << i18n( "Index for Section %1: %2", section, sectionName(section)) << "</h1>" << endl;
1222 // compose list of search paths -------------------------------------------------------------
1224 checkManPaths();
1225 infoMessage(i18n("Generating Index"));
1227 // search for the man pages
1228 QStringList pages = findPages( section, QString() );
1230 QMap<QString, QString> indexmap = buildIndexMap(section);
1232 // print out the list
1233 os << "<table>" << endl;
1235 #ifdef _USE_OLD_CODE
1236 pages.sort();
1238 QMap<QString, QString> pagemap;
1240 QStringList::ConstIterator page;
1241 for (page = pages.constBegin(); page != pages.constEnd(); ++page)
1243 QString fileName = *page;
1245 stripExtension( &fileName );
1247 pos = fileName.lastIndexOf('/');
1248 if (pos > 0)
1249 fileName = fileName.mid(pos+1);
1251 if (!fileName.isEmpty())
1252 pagemap[fileName] = *page;
1256 for (QMap<QString,QString>::ConstIterator it = pagemap.constBegin();
1257 it != pagemap.constEnd(); ++it)
1259 os << "<tr><td><a href=\"man:" << it.data() << "\">\n"
1260 << it.key() << "</a></td><td>&nbsp;</td><td> "
1261 << (indexmap.contains(it.key()) ? indexmap[it.key()] : "" )
1262 << "</td></tr>" << endl;
1265 #else /* ! _USE_OLD_CODE */
1267 #ifdef _USE_QSORT
1269 int listlen = pages.count();
1270 man_index_ptr *indexlist = new man_index_ptr[listlen];
1271 listlen = 0;
1273 #else /* !_USE_QSORT */
1275 QManIndexList manpages;
1276 manpages.setAutoDelete(true);
1278 #endif /* _USE_QSORT */
1280 QStringList::const_iterator page;
1281 for (page = pages.constBegin(); page != pages.constEnd(); ++page)
1283 // I look for the beginning of the man page name
1284 // i.e. "bla/pagename.3.gz" by looking for the last "/"
1285 // Then look for the end of the name by searching backwards
1286 // for the last ".", not counting zip extensions.
1287 // If the len of the name is >0,
1288 // store it in the list structure, to be sorted later
1290 char *manpage_end;
1291 struct man_index_t *manindex = new man_index_t;
1292 manindex->manpath = strdup((*page).toUtf8());
1294 manindex->manpage_begin = strrchr(manindex->manpath, '/');
1295 if (manindex->manpage_begin)
1297 manindex->manpage_begin++;
1298 assert(manindex->manpage_begin >= manindex->manpath);
1300 else
1302 manindex->manpage_begin = manindex->manpath;
1303 assert(manindex->manpage_begin >= manindex->manpath);
1306 // Skip extension ".section[.gz]"
1308 char *begin = (char*)(manindex->manpage_begin);
1309 int len = strlen( begin );
1310 char *end = begin+(len-1);
1312 if ( len >= 3 && strcmp( end-2, ".gz" ) == 0 )
1313 end -= 3;
1314 else if ( len >= 2 && strcmp( end-1, ".Z" ) == 0 )
1315 end -= 2;
1316 else if ( len >= 2 && strcmp( end-1, ".z" ) == 0 )
1317 end -= 2;
1318 else if ( len >= 4 && strcmp( end-3, ".bz2" ) == 0 )
1319 end -= 4;
1321 while ( end >= begin && *end != '.' )
1322 end--;
1324 if ( end < begin )
1325 manpage_end = 0;
1326 else
1327 manpage_end = end;
1329 if (NULL == manpage_end)
1331 // no '.' ending ???
1332 // set the pointer past the end of the filename
1333 manindex->manpage_len = (*page).length();
1334 manindex->manpage_len -= (manindex->manpage_begin - manindex->manpath);
1335 assert(manindex->manpage_len >= 0);
1337 else
1339 manindex->manpage_len = (manpage_end - manindex->manpage_begin);
1340 assert(manindex->manpage_len >= 0);
1343 if (0 < manindex->manpage_len)
1346 #ifdef _USE_QSORT
1348 indexlist[listlen] = manindex;
1349 listlen++;
1351 #else /* !_USE_QSORT */
1353 manpages.append(manindex);
1355 #endif /* _USE_QSORT */
1358 else delete manindex;
1362 // Now do the sorting on the page names
1363 // and the printout afterwards
1364 // While printing avoid duplicate man page names
1367 struct man_index_t dummy_index = {0l,0l,0};
1368 struct man_index_t *last_index = &dummy_index;
1370 #ifdef _USE_QSORT
1372 // sort and print
1373 qsort(indexlist, listlen, sizeof(struct man_index_t *), compare_man_index);
1375 QChar firstchar, tmp;
1376 QString indexLine="<div class=\"secidxshort\">\n";
1377 if (indexlist[0]->manpage_len>0)
1379 firstchar=QChar((indexlist[0]->manpage_begin)[0]).toLower();
1381 const QString appendixstr = QString(
1382 " [<a href=\"#%1\" accesskey=\"%2\">%3</a>]\n"
1383 ).arg(firstchar).arg(firstchar).arg(firstchar);
1384 indexLine.append(appendixstr);
1386 os << "<tr><td class=\"secidxnextletter\"" << " colspan=\"3\">\n <a name=\""
1387 << firstchar << "\">" << firstchar <<"</a>\n</td></tr>" << endl;
1389 for (int i=0; i<listlen; i++)
1391 struct man_index_t *manindex = indexlist[i];
1393 // qstrncmp():
1394 // "last_man" has already a \0 string ending, but
1395 // "manindex->manpage_begin" has not,
1396 // so do compare at most "manindex->manpage_len" of the strings.
1397 if (last_index->manpage_len == manindex->manpage_len &&
1398 !qstrncmp(last_index->manpage_begin,
1399 manindex->manpage_begin,
1400 manindex->manpage_len)
1403 continue;
1406 tmp=QChar((manindex->manpage_begin)[0]).toLower();
1407 if (firstchar != tmp)
1409 firstchar = tmp;
1410 os << "<tr><td class=\"secidxnextletter\"" << " colspan=\"3\">\n <a name=\""
1411 << firstchar << "\">" << firstchar << "</a>\n</td></tr>" << endl;
1413 const QString appendixstr = QString(
1414 " [<a href=\"#%1\" accesskey=\"%2\">%3</a>]\n"
1415 ).arg(firstchar).arg(firstchar).arg(firstchar);
1416 indexLine.append(appendixstr);
1418 os << "<tr><td><a href=\"man:"
1419 << manindex->manpath << "\">\n";
1421 ((char *)manindex->manpage_begin)[manindex->manpage_len] = '\0';
1422 os << manindex->manpage_begin
1423 << "</a></td><td>&nbsp;</td><td> "
1424 << (indexmap.contains(manindex->manpage_begin) ? indexmap[manindex->manpage_begin] : "" )
1425 << "</td></tr>" << endl;
1426 last_index = manindex;
1428 indexLine.append("</div>");
1430 for (int i=0; i<listlen; i++) {
1431 ::free(indexlist[i]->manpath); // allocated by strdup
1432 delete indexlist[i];
1435 delete [] indexlist;
1437 #else /* !_USE_QSORT */
1439 manpages.sort(); // using
1441 for (QManIndexListIterator mit(manpages);
1442 mit.current();
1443 ++mit )
1445 struct man_index_t *manindex = mit.current();
1447 // qstrncmp():
1448 // "last_man" has already a \0 string ending, but
1449 // "manindex->manpage_begin" has not,
1450 // so do compare at most "manindex->manpage_len" of the strings.
1451 if (last_index->manpage_len == manindex->manpage_len &&
1452 !qstrncmp(last_index->manpage_begin,
1453 manindex->manpage_begin,
1454 manindex->manpage_len)
1457 continue;
1460 os << "<tr><td><a href=\"man:"
1461 << manindex->manpath << "\">\n";
1463 manindex->manpage_begin[manindex->manpage_len] = '\0';
1464 os << manindex->manpage_begin
1465 << "</a></td><td>&nbsp;</td><td> "
1466 << (indexmap.contains(manindex->manpage_begin) ? indexmap[manindex->manpage_begin] : "" )
1467 << "</td></tr>" << endl;
1468 last_index = manindex;
1470 #endif /* _USE_QSORT */
1471 #endif /* _USE_OLD_CODE */
1473 os << "</table></div>" << endl;
1475 os << indexLine << endl;
1477 // print footer
1478 os << "</body></html>" << endl;
1480 infoMessage(QString());
1481 mimeType("text/html");
1482 data(array);
1483 finished();
1486 void MANProtocol::listDir(const KUrl &url)
1488 kDebug( 7107 ) << url;
1490 QString title;
1491 QString section;
1493 if ( !parseUrl(url.path(), title, section) ) {
1494 error( KIO::ERR_MALFORMED_URL, url.url() );
1495 return;
1498 // stat() and listDir() declared that everything is an html file.
1499 // However we can list man: and man:(1) as a directory (e.g. in dolphin).
1500 // But we cannot list man:ls as a directory, this makes no sense (#154173)
1502 if (!title.isEmpty() && title != "/") {
1503 error(KIO::ERR_IS_FILE, url.url());
1504 return;
1507 UDSEntryList uds_entry_list;
1509 if (section.isEmpty()) {
1510 for (QStringList::ConstIterator it = section_names.constBegin(); it != section_names.constEnd(); ++it) {
1511 UDSEntry uds_entry;
1513 QString name = "man:/(" + *it + ")";
1514 uds_entry.insert( KIO::UDSEntry::UDS_NAME, sectionName( *it ) );
1515 uds_entry.insert( KIO::UDSEntry::UDS_URL, name );
1516 uds_entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR );
1518 uds_entry_list.append( uds_entry );
1522 QStringList list = findPages( section, QString(), false );
1524 QStringList::Iterator it = list.begin();
1525 QStringList::Iterator end = list.end();
1527 for ( ; it != end; ++it ) {
1528 stripExtension( &(*it) );
1530 UDSEntry uds_entry;
1531 uds_entry.insert( KIO::UDSEntry::UDS_NAME, *it );
1532 uds_entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
1533 uds_entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("text/html"));
1534 uds_entry_list.append( uds_entry );
1537 listEntries( uds_entry_list );
1538 finished();
1541 void MANProtocol::getProgramPath()
1543 if (!mySgml2RoffPath.isEmpty())
1544 return;
1546 mySgml2RoffPath = KGlobal::dirs()->findExe("sgml2roff");
1547 if (!mySgml2RoffPath.isEmpty())
1548 return;
1550 /* sgml2roff isn't found in PATH. Check some possible locations where it may be found. */
1551 mySgml2RoffPath = KGlobal::dirs()->findExe("sgml2roff", QString(SGML2ROFF_DIRS));
1552 if (!mySgml2RoffPath.isEmpty())
1553 return;
1555 /* Cannot find sgml2roff program: */
1556 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."));
1557 finished();
1558 exit();