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.
31 #include <QTextStream>
32 #include <QDataStream>
38 #include <kcomponentdata.h>
40 #include <kstandarddirs.h>
43 #include <kmimetype.h>
45 #include "kio_man.moc"
48 #include <kfilterbase.h>
49 #include <kfilterdev.h>
53 MANProtocol
*MANProtocol::_self
= 0;
55 #define SGML2ROFF_DIRS "/usr/lib/sgml"
58 * Drop trailing ".section[.gz]" from name
61 void stripExtension( QString
*name
)
63 int pos
= name
->length();
65 if ( name
->indexOf(".gz", -3) != -1 )
67 else if ( name
->indexOf(".z", -2, Qt::CaseInsensitive
) != -1 )
69 else if ( name
->indexOf(".bz2", -4) != -1 )
71 else if ( name
->indexOf(".bz", -3) != -1 )
75 pos
= name
->lastIndexOf('.', pos
-1);
78 name
->truncate( pos
);
82 bool parseUrl(const QString
& _url
, QString
&title
, QString
§ion
)
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
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) == '/')
104 int pos
= url
.indexOf('(');
106 return true; // man:ls -> title=ls
108 title
= title
.left(pos
);
110 section
= url
.mid(pos
+1);
112 pos
= section
.indexOf(')');
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"
126 MANProtocol::MANProtocol(const QByteArray
&pool_socket
, const QByteArray
&app_socket
)
127 : QObject(), SlaveBase("man", pool_socket
, app_socket
)
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()
149 void MANProtocol::parseWhatIs( QMap
<QString
, QString
> &i
, QTextStream
&t
, const QString
&mark
)
156 int pos
= re
.indexIn( l
);
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
] == ' ')
166 names
= names
.mid(pos
);
173 bool MANProtocol::addWhatIs(QMap
<QString
, QString
> &i
, const QString
&name
, const QString
&mark
)
176 if (!f
.open(QIODevice::ReadOnly
))
179 parseWhatIs( i
, t
, mark
);
183 QMap
<QString
, QString
> MANProtocol::buildIndexMap(const QString
§ion
)
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";
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();
202 if ( QFile::exists( *it_dir
) ) {
203 QStringList::ConstIterator it_name
;
204 for ( it_name
= names
.constBegin();
205 it_name
!= names
.constEnd();
208 if (addWhatIs(i
, (*it_dir
) + '/' + (*it_name
), mark
))
211 if ( it_name
== names
.constEnd() ) {
213 proc
<< "whatis" << "-M" << (*it_dir
) << "-w" << "*";
214 proc
.setOutputChannelMode( KProcess::OnlyStdoutChannel
);
216 QTextStream
t( proc
.readAllStandardOutput(), QIODevice::ReadOnly
);
217 parseWhatIs( i
, t
, mark
);
224 QStringList
MANProtocol::manDirectories()
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();
236 // Translated pages in "<mandir>/<lang>" if the directory
238 QStringList languages
= KGlobal::locale()->languageList();
240 for (QStringList::ConstIterator it_lang
= languages
.constBegin();
241 it_lang
!= languages
.constEnd();
244 if ( !(*it_lang
).isEmpty() && (*it_lang
) != QString("C") ) {
245 QString dir
= (*it_dir
) + '/' + (*it_lang
);
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
;
265 QStringList
MANProtocol::findPages(const QString
&_section
,
266 const QString
&title
,
269 QString section
= _section
;
273 // kDebug() << "findPages '" << section << "' '" << title << "'\n";
274 if ( (!title
.isEmpty()) && (title
.at(0) == '/') ) {
279 const QString
star( "*" );
282 // Find man sections in this directory
284 QStringList sect_list
;
285 if ( section
.isEmpty() )
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
;
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();
314 //kDebug(7107)<<"Before inner loop";
315 for ( QStringList::const_iterator it_dir
= man_dirs
.constBegin();
316 it_dir
!= man_dirs
.constEnd();
319 QString man_dir
= (*it_dir
);
321 // Sections = all sub directories "man*" and "sman*"
323 DIR *dp
= ::opendir( QFile::encodeName( man_dir
) );
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
) )
340 else if (file
.startsWith(sman
))
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;
352 //kDebug(7107) <<" after while loop";
355 kDebug(7107)<<"================-";
356 kDebug(7107)<<"star=="<<star
;
357 kDebug(7107)<<"it_s=="<<it_s
;
358 kDebug(7107)<<"================+";
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;
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
) );
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
397 if ( !name
.startsWith( title
) ) {
401 // beginning matches, do a more thorough check...
402 QString tmp_name
= name
;
403 stripExtension( &tmp_name
);
404 if ( tmp_name
!= title
)
418 void MANProtocol::output(const char *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
);
447 static QString
text2html(const QString
& txt
)
451 reply
= reply
.replace('&', "&");
452 reply
= reply
.replace('<', "<");
453 reply
= reply
.replace('>', ">");
454 reply
= reply
.replace('"', "&dquot;");
455 reply
= reply
.replace('\'', """);
460 void MANProtocol::get(const KUrl
& url
)
462 kDebug(7107) << "GET " << url
.url();
464 QString title
, section
;
466 if (!parseUrl(url
.path(), title
, section
))
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())
485 const QStringList foundPages
=findPages(section
, title
);
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
)));
498 else if (foundPages
.count()>1)
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"))))
508 outputMatchingPages(foundPages
);
510 //yes, we found exactly one man page
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
);
522 outputError(i18n("Open of %1 failed.", title
));
526 // will call output_real
532 m_outputBuffer
.close();
533 data(m_outputBuffer
.buffer());
534 m_outputBuffer
.setData(QByteArray());
541 char *MANProtocol::readManPage(const char *_filename
)
543 QByteArray filename
= _filename
;
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" || )
556 // Determine path to sgml2roff, if not already done.
558 proc
<< mySgml2RoffPath
<< filename
;
559 proc
.setOutputChannelMode( KProcess::OnlyStdoutChannel
);
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
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
))
590 QByteArray
array(fd
->readAll());
591 kDebug(7107) << "read " << array
.size();
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
608 void MANProtocol::outputError(const QString
& errmsg
)
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
;
626 void MANProtocol::outputMatchingPages(const QStringList
&matchingPages
)
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";
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";
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
;
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());
671 kDebug(7107) << "URL " << url
.url() << " parsed to title='" << title
<< "' section=" << section
;
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
);
684 entry
.insert(KIO::UDSEntry::UDS_MIME_TYPE
, QString::fromLatin1("text/html"));
695 int KDE_EXPORT
kdemain( int argc
, char **argv
) {
697 KComponentData
componentData("kio_man");
699 kDebug(7107) << "STARTING";
703 fprintf(stderr
, "Usage: kio_man protocol domain-socket1 domain-socket2\n");
707 MANProtocol
slave(argv
[2], argv
[3]);
708 slave
.dispatchLoop();
710 kDebug(7107) << "Done";
717 void MANProtocol::mimetype(const KUrl
& /*url*/)
719 mimeType("text/html");
723 static QString
sectionName(const QString
& section
)
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")
755 QStringList
MANProtocol::buildSectionList(const QStringList
& dirs
) const
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
));
776 void MANProtocol::showMainIndex()
779 QTextStream
os(&array
, QIODevice::WriteOnly
);
780 os
.setCodec( "UTF-8" );
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());
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> </td><td> " << sectionName(*it
) << "</td></tr>" << endl
;
807 os
<< "</table>" << endl
;
810 os
<< "</body></html>" << endl
;
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
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
837 mc
.setFileName("/etc/manpath.config"); // SuSE, Debian
839 mc
.setFileName("/etc/man.config"); // Mandrake
841 if (mc
.open(QIODevice::ReadOnly
))
844 is
.setCodec( QTextCodec::codecForLocale () );
848 const QString line
= is
.readLine();
849 if ( manpath_regex
.indexIn(line
) == 0 )
851 const QString path
= line
.mid(8).trimmed();
854 else if ( mandatory_regex
.indexIn(line
) == 0 )
856 const QString path
= line
.mid(18).trimmed();
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() )
892 conf_section += line.mid(8).trimmed();
900 static const char *manpaths
[] = {
919 "/usr/newsprint/man",
924 while (manpaths
[i
]) {
925 if ( constr_path
.indexOf( QString( manpaths
[i
] ) ) == -1 )
926 constr_path
+= QString( manpaths
[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();
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
;
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( '/' );
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
;
973 // What is the default mapping?
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;
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
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
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();
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
) )
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();
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
) )
1076 /* sections are not used
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
1095 #warning "using old code"
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
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
;
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
;
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
,
1138 if ( m1
->manpage_len
< m2
->manpage_len
)
1140 i
= qstrnicmp( m1
->manpage_begin
,
1148 return qstrnicmp( m1
->manpage_begin
,
1153 #else /* !_USE_QSORT */
1155 #warning using heapsort
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
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
;
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
,
1183 if (m1
->manpage_len
> m2
->manpage_len
)
1186 i
= qstrnicmp(m1
->manpage_begin
,
1194 return qstrnicmp(m1
->manpage_begin
,
1200 #endif /* !_USE_QSORT */
1201 #endif /* !_USE_OLD_CODE */
1206 void MANProtocol::showIndex(const QString
& section
)
1209 QTextStream
os(&array
, QIODevice::WriteOnly
);
1210 os
.setCodec( "UTF-8" );
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 -------------------------------------------------------------
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
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('/');
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> </td><td> "
1261 << (indexmap
.contains(it
.key()) ? indexmap
[it
.key()] : "" )
1262 << "</td></tr>" << endl
;
1265 #else /* ! _USE_OLD_CODE */
1269 int listlen
= pages
.count();
1270 man_index_ptr
*indexlist
= new man_index_ptr
[listlen
];
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
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
);
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 )
1314 else if ( len
>= 2 && strcmp( end
-1, ".Z" ) == 0 )
1316 else if ( len
>= 2 && strcmp( end
-1, ".z" ) == 0 )
1318 else if ( len
>= 4 && strcmp( end
-3, ".bz2" ) == 0 )
1321 while ( end
>= begin
&& *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);
1339 manindex
->manpage_len
= (manpage_end
- manindex
->manpage_begin
);
1340 assert(manindex
->manpage_len
>= 0);
1343 if (0 < manindex
->manpage_len
)
1348 indexlist
[listlen
] = manindex
;
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
;
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
];
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
)
1406 tmp
=QChar((manindex
->manpage_begin
)[0]).toLower();
1407 if (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> </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
);
1445 struct man_index_t
*manindex
= mit
.current();
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
)
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> </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
;
1478 os
<< "</body></html>" << endl
;
1480 infoMessage(QString());
1481 mimeType("text/html");
1486 void MANProtocol::listDir(const KUrl
&url
)
1488 kDebug( 7107 ) << url
;
1493 if ( !parseUrl(url
.path(), title
, section
) ) {
1494 error( KIO::ERR_MALFORMED_URL
, url
.url() );
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());
1507 UDSEntryList uds_entry_list
;
1509 if (section
.isEmpty()) {
1510 for (QStringList::ConstIterator it
= section_names
.constBegin(); it
!= section_names
.constEnd(); ++it
) {
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
) );
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
);
1541 void MANProtocol::getProgramPath()
1543 if (!mySgml2RoffPath
.isEmpty())
1546 mySgml2RoffPath
= KGlobal::dirs()->findExe("sgml2roff");
1547 if (!mySgml2RoffPath
.isEmpty())
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())
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."));