File indexing completed on 2024-04-28 04:57:49
0001 /* This file is part of the KDE libraries 0002 SPDX-FileCopyrightText: 2000 Matthias Hoelzer-Kluepfel <mhk@caldera.de> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "kio_man.h" 0008 #include "kio_man_debug.h" 0009 0010 #include <QByteArray> 0011 #include <QCoreApplication> 0012 #include <QDir> 0013 #include <QFile> 0014 #include <QMap> 0015 #include <QProcess> 0016 #include <QRegularExpression> 0017 #include <QStandardPaths> 0018 #include <QTextCodec> 0019 #include <QTextStream> 0020 #include <QUrl> 0021 0022 #include <KLocalizedString> 0023 0024 #include "man2html.h" 0025 #include <KCompressionDevice> 0026 0027 using namespace KIO; 0028 0029 // Pseudo plugin class to embed meta data 0030 class KIOPluginForMetaData : public QObject 0031 { 0032 Q_OBJECT 0033 Q_PLUGIN_METADATA(IID "org.kde.kio.worker.man" FILE "man.json") 0034 }; 0035 0036 MANProtocol *MANProtocol::s_self = nullptr; 0037 0038 static const char *SGML2ROFF_DIRS = "/usr/lib/sgml"; 0039 static const char *SGML2ROFF_EXECUTABLE = "sgml2roff"; 0040 0041 /* 0042 * Drop trailing compression suffix from name 0043 */ 0044 static QString stripCompression(const QString &name) 0045 { 0046 int pos = name.length(); 0047 0048 if (name.endsWith(".gz")) 0049 pos -= 3; 0050 else if (name.endsWith(".z", Qt::CaseInsensitive)) 0051 pos -= 2; 0052 else if (name.endsWith(".bz2")) 0053 pos -= 4; 0054 else if (name.endsWith(".bz")) 0055 pos -= 3; 0056 else if (name.endsWith(".lzma")) 0057 pos -= 5; 0058 else if (name.endsWith(".xz")) 0059 pos -= 3; 0060 else if (name.endsWith(".zst")) 0061 pos -= 4; 0062 else if (name.endsWith(".br")) 0063 pos -= 3; 0064 0065 return (pos > 0 ? name.left(pos) : name); 0066 } 0067 0068 /* 0069 * Drop trailing compression suffix and section from name 0070 */ 0071 static QString stripExtension(const QString &name) 0072 { 0073 QString wc = stripCompression(name); 0074 const int pos = wc.lastIndexOf('.'); 0075 return (pos > 0 ? wc.left(pos) : wc); 0076 } 0077 0078 static bool parseUrl(const QString &_url, QString &title, QString §ion) 0079 { 0080 section.clear(); 0081 0082 QString url = _url.trimmed(); 0083 if (url.isEmpty() || url.startsWith('/')) { 0084 if (url.isEmpty() || QFile::exists(url)) { 0085 // man:/usr/share/man/man1/ls.1.gz is a valid file 0086 title = url; 0087 return true; 0088 } else { 0089 // If a full path is specified but does not exist, 0090 // then it is perhaps a normal man page. 0091 qCDebug(KIO_MAN_LOG) << url << " does not exist"; 0092 } 0093 } 0094 0095 while (url.startsWith('/')) 0096 url.remove(0, 1); 0097 title = url; 0098 0099 int pos = url.indexOf('('); 0100 if (pos < 0) 0101 return true; // man:ls -> title=ls 0102 0103 title = title.left(pos); 0104 section = url.mid(pos + 1); 0105 0106 pos = section.indexOf(')'); 0107 if (pos >= 0) { 0108 if (pos < section.length() - 2 && title.isEmpty()) { 0109 title = section.mid(pos + 2); 0110 } 0111 section = section.left(pos); 0112 } 0113 0114 // man:ls(2) -> title="ls", section="2" 0115 0116 return true; 0117 } 0118 0119 MANProtocol::MANProtocol(const QByteArray &pool_socket, const QByteArray &app_socket) 0120 : QObject() 0121 , WorkerBase("man", pool_socket, app_socket) 0122 { 0123 Q_ASSERT(s_self == nullptr); 0124 s_self = this; 0125 0126 m_sectionNames << "0" 0127 << "0p" 0128 << "1" 0129 << "1p" 0130 << "2" 0131 << "3" 0132 << "3n" 0133 << "3p" 0134 << "4" 0135 << "5" 0136 << "6" 0137 << "7" 0138 << "8" 0139 << "9" 0140 << "l" 0141 << "n"; 0142 0143 const QString cssPath(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kio_docfilter/kio_docfilter.css")); 0144 m_manCSSFile = QFile::encodeName(QUrl::fromLocalFile(cssPath).url()); 0145 } 0146 0147 MANProtocol *MANProtocol::self() 0148 { 0149 return s_self; 0150 } 0151 0152 MANProtocol::~MANProtocol() 0153 { 0154 s_self = nullptr; 0155 } 0156 0157 void MANProtocol::parseWhatIs(QMap<QString, QString> &i, QTextStream &t, const QString &mark) 0158 { 0159 const QRegularExpression re(mark); 0160 QString l; 0161 while (!t.atEnd()) { 0162 l = t.readLine(); 0163 QRegularExpressionMatch match = re.match(l); 0164 int pos = match.capturedStart(0); 0165 if (pos != -1) { 0166 QString names = l.left(pos); 0167 QString descr = l.mid(match.capturedEnd(0)); 0168 while ((pos = names.indexOf(",")) != -1) { 0169 i[names.left(pos++)] = descr; 0170 while (names[pos] == ' ') 0171 pos++; 0172 names = names.mid(pos); 0173 } 0174 i[names] = descr; 0175 } 0176 } 0177 } 0178 0179 bool MANProtocol::addWhatIs(QMap<QString, QString> &i, const QString &name, const QString &mark) 0180 { 0181 QFile f(name); 0182 if (!f.open(QIODevice::ReadOnly)) 0183 return false; 0184 0185 QTextStream t(&f); 0186 parseWhatIs(i, t, mark); 0187 return true; 0188 } 0189 0190 QMap<QString, QString> MANProtocol::buildIndexMap(const QString §ion) 0191 { 0192 qCDebug(KIO_MAN_LOG) << "for section" << section; 0193 0194 QMap<QString, QString> i; 0195 QStringList man_dirs = manDirectories(); 0196 // Supplementary places for whatis databases 0197 man_dirs += m_mandbpath; 0198 if (!man_dirs.contains("/var/cache/man")) 0199 man_dirs << "/var/cache/man"; 0200 if (!man_dirs.contains("/var/catman")) 0201 man_dirs << "/var/catman"; 0202 0203 QStringList names; 0204 names << "whatis.db" 0205 << "whatis"; 0206 const QString mark = "\\s+\\(" + section + "[a-z]*\\)\\s+-\\s+"; 0207 0208 int count0; 0209 for (const QString &it_dir : qAsConst(man_dirs)) { 0210 if (!QFile::exists(it_dir)) 0211 continue; 0212 0213 bool added = false; 0214 for (const QString &it_name : qAsConst(names)) { 0215 count0 = i.count(); 0216 if (addWhatIs(i, (it_dir + '/' + it_name), mark)) { 0217 qCDebug(KIO_MAN_LOG) << "added" << (i.count() - count0) << "from" << it_name << "in" << it_dir; 0218 added = true; 0219 break; 0220 } 0221 } 0222 0223 if (!added) { 0224 // Nothing was able to be added by scanning the directory, 0225 // so try parsing the output of the whatis(1) command. 0226 QProcess proc; 0227 proc.setProgram("whatis"); 0228 proc.setArguments(QStringList() << "-M" << it_dir << "-w" 0229 << "*"); 0230 proc.setProcessChannelMode(QProcess::ForwardedErrorChannel); 0231 proc.start(); 0232 proc.waitForFinished(); 0233 QTextStream t(proc.readAllStandardOutput(), QIODevice::ReadOnly); 0234 0235 count0 = i.count(); 0236 parseWhatIs(i, t, mark); 0237 qCDebug(KIO_MAN_LOG) << "added" << (i.count() - count0) << "from whatis in" << it_dir; 0238 } 0239 } 0240 0241 qCDebug(KIO_MAN_LOG) << "returning" << i.count() << "index entries"; 0242 return i; 0243 } 0244 0245 //--------------------------------------------------------------------- 0246 0247 QStringList MANProtocol::manDirectories() 0248 { 0249 checkManPaths(); 0250 // 0251 // Build a list of man directories including translations 0252 // 0253 QStringList man_dirs; 0254 const QList<QLocale> locales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry); 0255 0256 for (const QString &it_dir : qAsConst(m_manpath)) { 0257 // Translated pages in "<mandir>/<lang>" if the directory 0258 // exists 0259 for (const QLocale &it_loc : locales) { 0260 // TODO: languageToString() is wrong, that returns the readable name 0261 // of the language. We want the country code returned by name(). 0262 QString lang = QLocale::languageToString(it_loc.language()); 0263 if (!lang.isEmpty() && lang != QString("C")) { 0264 QString dir = it_dir + '/' + lang; 0265 QDir d(dir); 0266 if (d.exists()) { 0267 const QString p = d.canonicalPath(); 0268 if (!man_dirs.contains(p)) 0269 man_dirs += p; 0270 } 0271 } 0272 } 0273 0274 // Untranslated pages in "<mandir>" 0275 const QString p = QDir(it_dir).canonicalPath(); 0276 if (!man_dirs.contains(p)) 0277 man_dirs += p; 0278 } 0279 0280 qCDebug(KIO_MAN_LOG) << "returning" << man_dirs.count() << "man directories"; 0281 return man_dirs; 0282 } 0283 0284 QStringList MANProtocol::findPages(const QString &_section, const QString &title, bool full_path) 0285 { 0286 QStringList list; 0287 // qCDebug(KIO_MAN_LOG) << "findPages '" << section << "' '" << title << "'\n"; 0288 if (title.startsWith('/')) // absolute man page path 0289 { 0290 list.append(title); // just that and nothing else 0291 return list; 0292 } 0293 0294 const QString star("*"); // flag for finding sections 0295 const QString man("man"); // prefix for ROFF subdirectories 0296 const QString sman("sman"); // prefix for SGML subdirectories 0297 0298 // Generate a list of applicable sections 0299 QStringList sect_list; 0300 if (!_section.isEmpty()) // section requested as parameter 0301 { 0302 sect_list += _section; 0303 0304 // It's not clear what this code does. If a section with a letter 0305 // suffix is specified, for example "3p", both "3p" and "3" are 0306 // added to the section list. The result is that "man:(3p)" returns 0307 // the same entries as "man:(3)" 0308 QString section = _section; 0309 while ((!section.isEmpty()) && (section.at(section.length() - 1).isLetter())) { 0310 section.truncate(section.length() - 1); 0311 sect_list += section; 0312 } 0313 } else { 0314 sect_list += star; 0315 } 0316 0317 const QStringList man_dirs = manDirectories(); 0318 QStringList nameFilters; 0319 nameFilters += man + '*'; 0320 nameFilters += sman + '*'; 0321 0322 // Find man pages in the sections specified above, 0323 // or all sections. 0324 // 0325 // Do not convert this loop to an iterator, or 'it_s' below 0326 // to a reference. The 'sect_list' list can be modified 0327 // within the loop. 0328 for (int i = 0; i < sect_list.count(); ++i) { 0329 const QString it_s = sect_list.at(i); 0330 QString it_real = it_s.toLower(); 0331 0332 // Find applicable pages within all man directories. 0333 for (const QString &man_dir : man_dirs) { 0334 // Find all subdirectories named "man*" and "sman*" 0335 // to extend the list of sections and find the correct 0336 // case for the section suffix. 0337 QDir dp(man_dir); 0338 dp.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); 0339 dp.setNameFilters(nameFilters); 0340 const QStringList entries = dp.entryList(); 0341 for (const QString &file : entries) { 0342 QString sect; 0343 if (file.startsWith(man)) 0344 sect = file.mid(man.length()); 0345 else if (file.startsWith(sman)) 0346 sect = file.mid(sman.length()); 0347 // Should never happen, because of the name filter above. 0348 if (sect.isEmpty()) 0349 continue; 0350 0351 if (sect.toLower() == it_real) 0352 it_real = sect; 0353 0354 // Only add sect if not already contained, avoid duplicates 0355 if (!sect_list.contains(sect) && _section.isEmpty()) { 0356 // qCDebug(KIO_MAN_LOG) << "another section " << sect; 0357 sect_list += sect; 0358 } 0359 } 0360 0361 if (it_s != star) // finding pages, not just sections 0362 { 0363 const QString dir = man_dir + '/' + man + it_real + '/'; 0364 list.append(findManPagesInSection(dir, title, full_path)); 0365 0366 const QString sdir = man_dir + '/' + sman + it_real + '/'; 0367 list.append(findManPagesInSection(sdir, title, full_path)); 0368 } 0369 } 0370 } 0371 0372 // qCDebug(KIO_MAN_LOG) << "finished " << list << " " << sect_list; 0373 return list; 0374 } 0375 0376 QStringList MANProtocol::findManPagesInSection(const QString &dir, const QString &title, bool full_path) 0377 { 0378 QStringList list; 0379 0380 qCDebug(KIO_MAN_LOG) << "in" << dir << "title" << title; 0381 const bool title_given = !title.isEmpty(); 0382 0383 QDir dp(dir); 0384 dp.setFilter(QDir::Files); 0385 const QStringList names = dp.entryList(); 0386 for (const QString &name : names) { 0387 if (title_given) { 0388 // check title if we're looking for a specific page 0389 if (!name.startsWith(title)) 0390 continue; 0391 // beginning matches, do a more thorough check... 0392 const QString tmp_name = stripExtension(name); 0393 if (tmp_name != title) 0394 continue; 0395 } 0396 0397 list.append(full_path ? dir + name : name); 0398 } 0399 0400 qCDebug(KIO_MAN_LOG) << "returning" << list.count() << "pages"; 0401 return (list); 0402 } 0403 0404 //--------------------------------------------------------------------- 0405 0406 void MANProtocol::output(const char *insert) 0407 { 0408 if (insert) { 0409 m_outputBuffer.write(insert, strlen(insert)); 0410 } 0411 if (!insert || m_outputBuffer.pos() >= 2048) { 0412 m_outputBuffer.close(); 0413 data(m_outputBuffer.buffer()); 0414 m_outputBuffer.setData(QByteArray()); 0415 m_outputBuffer.open(QIODevice::WriteOnly); 0416 } 0417 } 0418 0419 #ifndef SIMPLE_MAN2HTML 0420 // called by man2html 0421 extern char *read_man_page(const char *filename) 0422 { 0423 return MANProtocol::self()->readManPage(filename); 0424 } 0425 0426 // called by man2html 0427 extern void output_real(const char *insert) 0428 { 0429 MANProtocol::self()->output(insert); 0430 } 0431 #endif 0432 0433 //--------------------------------------------------------------------- 0434 0435 KIO::WorkerResult MANProtocol::get(const QUrl &url) 0436 { 0437 qCDebug(KIO_MAN_LOG) << "GET " << url.url(); 0438 0439 // Indicate the mimetype - this will apply to both 0440 // formatted man pages and error pages. 0441 mimeType("text/html"); 0442 0443 QString title, section; 0444 if (!parseUrl(url.path(), title, section)) { 0445 showMainIndex(); 0446 return KIO::WorkerResult::pass(); 0447 } 0448 0449 // see if an index was requested 0450 if (url.query().isEmpty() && (title.isEmpty() || title == "/" || title == ".")) { 0451 if (section == "index" || section.isEmpty()) 0452 showMainIndex(); 0453 else 0454 showIndex(section); 0455 return KIO::WorkerResult::pass(); 0456 } 0457 0458 QStringList foundPages = findPages(section, title); 0459 if (foundPages.isEmpty()) { 0460 outputError(xi18nc("@info", 0461 "No man page matching <resource>%1</resource> could be found." 0462 "<nl/><nl/>" 0463 "Check that you have not mistyped the name of the page, " 0464 "and note that man page names are case sensitive." 0465 "<nl/><nl/>" 0466 "If the name is correct, then you may need to extend the search path " 0467 "for man pages, either using the <envar>MANPATH</envar> environment " 0468 "variable or a configuration file in the <filename>/etc</filename> " 0469 "directory.", 0470 title.toHtmlEscaped())); 0471 return KIO::WorkerResult::pass(); 0472 } 0473 0474 // Sort the list of pages now, for display if required and for 0475 // testing for equivalents below. 0476 std::sort(foundPages.begin(), foundPages.end()); 0477 const QString pageFound = foundPages.first(); 0478 0479 if (foundPages.count() > 1) { 0480 // See if the multiple pages found refer to the same man page, for example 0481 // if 'foo.1' and 'foo.1.gz' were both found. To make this generic with 0482 // regard to compression suffixes, assume that the first page name (after 0483 // the list has been sorted above) is the shortest. Then check that all of 0484 // the others are the same with a possible compression suffix added. 0485 for (int i = 1; i < foundPages.count(); ++i) { 0486 if (!foundPages[i].startsWith(pageFound + '.')) { 0487 // There is a page which is not the same as the reference, even 0488 // allowing for a compression suffix. Output the list of multiple 0489 // pages only. 0490 outputMatchingPages(foundPages); 0491 return KIO::WorkerResult::pass(); 0492 } 0493 } 0494 } 0495 0496 setCssFile(m_manCSSFile); 0497 m_outputBuffer.open(QIODevice::WriteOnly); 0498 const QByteArray filename = QFile::encodeName(pageFound); 0499 const char *buf = readManPage(filename); 0500 if (!buf) { 0501 // readManPage emits an error page instead, so still passing 0502 return KIO::WorkerResult::pass(); 0503 ; 0504 } 0505 0506 // will call output_real 0507 scan_man_page(buf); 0508 delete[] buf; 0509 0510 output(nullptr); // flush 0511 0512 m_outputBuffer.close(); 0513 data(m_outputBuffer.buffer()); 0514 m_outputBuffer.setData(QByteArray()); 0515 0516 // tell we are done 0517 data(QByteArray()); 0518 return KIO::WorkerResult::pass(); 0519 } 0520 0521 //--------------------------------------------------------------------- 0522 0523 // If this function returns nullptr to indicate a problem, 0524 // then it must call outputError() first. 0525 char *MANProtocol::readManPage(const char *_filename) 0526 { 0527 QByteArray filename = _filename; 0528 QByteArray array, dirName; 0529 0530 // Determine the type of man page file by checking its path 0531 // Determination by MIME type with KMimeType doesn't work reliably. 0532 // E.g., Solaris 7: /usr/man/sman7fs/pcfs.7fs -> text/x-csrc - WRONG 0533 // 0534 // If the path name contains a component "sman", assume that it's SGML 0535 // and convert it to roff format (used on Solaris). Check for a pathname 0536 // component of "sman" only - we don't want a man page called "gasman.1" 0537 // to match. 0538 // QString file_mimetype = KMimeType::findByPath(QString(filename), 0, false)->name(); 0539 0540 if (QString(filename).contains("/sman/", Qt::CaseInsensitive)) { 0541 QProcess proc; 0542 // Determine path to sgml2roff, if not already done. 0543 if (!getProgramPath()) 0544 return nullptr; 0545 proc.setProgram(mySgml2RoffPath); 0546 proc.setArguments(QStringList() << filename); 0547 proc.setProcessChannelMode(QProcess::ForwardedErrorChannel); 0548 proc.start(); 0549 proc.waitForFinished(); 0550 array = proc.readAllStandardOutput(); 0551 } else { 0552 if (QDir::isRelativePath(filename)) { 0553 qCDebug(KIO_MAN_LOG) << "relative" << filename; 0554 filename = QDir::cleanPath(lastdir + '/' + filename).toUtf8(); 0555 qCDebug(KIO_MAN_LOG) << "resolved to" << filename; 0556 } 0557 0558 lastdir = filename.left(filename.lastIndexOf('/')); 0559 0560 // get the last directory name (which might be a language name, to be able to guess the encoding) 0561 QDir dir(lastdir); 0562 dir.cdUp(); 0563 dirName = QFile::encodeName(dir.dirName()); 0564 0565 if (!QFile::exists(QFile::decodeName(filename))) // if given file does not exist, find with suffix 0566 { 0567 qCDebug(KIO_MAN_LOG) << "not existing " << filename; 0568 QDir mandir(lastdir); 0569 const QString nameFilter = filename.mid(filename.lastIndexOf('/') + 1) + ".*"; 0570 mandir.setNameFilters(QStringList(nameFilter)); 0571 0572 const QStringList entries = mandir.entryList(); 0573 if (entries.isEmpty()) { 0574 outputError(xi18nc("@info", 0575 "The specified man page references " 0576 "another page <filename>%1</filename>," 0577 "<nl/>" 0578 "but the referenced page <filename>%2</filename> " 0579 "could not be found.", 0580 QFile::decodeName(filename), 0581 QDir::cleanPath(lastdir + '/' + nameFilter))); 0582 return nullptr; 0583 } 0584 0585 filename = lastdir + '/' + QFile::encodeName(entries.first()); 0586 qCDebug(KIO_MAN_LOG) << "resolved to " << filename; 0587 } 0588 0589 KCompressionDevice fd(QFile::encodeName(filename)); 0590 0591 if (!fd.open(QIODevice::ReadOnly)) { 0592 outputError(xi18nc("@info", "The man page <filename>%1</filename> could not be read.", QFile::decodeName(filename))); 0593 return nullptr; 0594 } 0595 0596 array = fd.readAll(); 0597 qCDebug(KIO_MAN_LOG) << "read " << array.size(); 0598 } 0599 0600 if (array.isEmpty()) { 0601 outputError(xi18nc("@info", "The man page <filename>%1</filename> could not be converted.", QFile::decodeName(filename))); 0602 return nullptr; 0603 } 0604 0605 return manPageToUtf8(array, dirName); // never returns nullptr 0606 } 0607 0608 //--------------------------------------------------------------------- 0609 0610 // This opens one <div> to format the HTML body, so when the document 0611 // is finished it needs to be closed by outputFooter(). 0612 void MANProtocol::outputHeader(QTextStream &os, const QString &header, const QString &title) 0613 { 0614 const QString pageTitle = (!title.isEmpty() ? title : header); 0615 0616 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0617 os.setCodec("UTF-8"); 0618 #endif 0619 0620 // The same header and styling as generated by man2html 0621 os << "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"; 0622 os << "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n"; 0623 os << "<title>" << pageTitle << "</title>\n"; 0624 os << "<link rel=\"stylesheet\" href=\"help:/kdoctools5-common/kde-default.css\" type=\"text/css\">\n"; 0625 if (!m_manCSSFile.isEmpty()) { 0626 os << "<link rel=\"stylesheet\" href=\"" << m_manCSSFile << "\" type=\"text/css\">\n"; 0627 } 0628 os << "<style type=\"text/css\">\n"; 0629 os << "#header_top { background-image: url(\"help:/kdoctools5-common/top.jpg\"); }\n"; 0630 os << "#header_top div { background-image: url(\"help:/kdoctools5-common/top-left.jpg\"); }\n"; 0631 os << "#header_top div div { background-image: url(\"help:/kdoctools5-common/top-right.jpg\"); }\n"; 0632 os << "</style>\n"; 0633 os << "</head>\n"; 0634 0635 os << "<body>\n"; 0636 os << "<div id=\"header\"><div id=\"header_top\"><div><div>\n"; 0637 os << "<img src=\"help:/kdoctools5-common/top-kde.jpg\" alt=\"top-kde\">\n"; 0638 os << pageTitle << "\n"; 0639 os << "</div></div></div></div>\n"; 0640 0641 os << "<div style=\"margin-left: 5em; margin-right: 5em;\">\n"; 0642 os << "<h1>" << header << "</h1>\n"; 0643 0644 os.flush(); 0645 } 0646 0647 void MANProtocol::outputFooter(QTextStream &os) 0648 { 0649 os << "</div>\n"; // closes "<div style="margin-left: 5em; margin-right: 5em;" 0650 os << "</body>\n"; 0651 os << "</html>\n"; 0652 os.flush(); 0653 } 0654 0655 // Do not call any other WorkerBase functions afterwards, 0656 // only return a KIO::WorkerResult::pass(). 0657 // It is assumed that mimeType() has already been called at the start of get(). 0658 void MANProtocol::outputError(const QString &errmsg) 0659 { 0660 QByteArray array; 0661 QTextStream os(&array, QIODevice::WriteOnly); 0662 0663 outputHeader(os, i18n("Manual Page Viewer Error")); 0664 os << errmsg << "\n"; 0665 outputFooter(os); 0666 0667 data(array); 0668 data(QByteArray()); 0669 } 0670 0671 void MANProtocol::outputMatchingPages(const QStringList &matchingPages) 0672 { 0673 QByteArray array; 0674 QTextStream os(&array, QIODevice::WriteOnly); 0675 0676 outputHeader(os, i18n("There is more than one matching man page:"), i18n("Multiple Manual Pages")); 0677 os << "<ul>\n"; 0678 0679 int acckey = 1; 0680 for (const QString &page : matchingPages) { 0681 os << "<li><a href='man:" << page << "' accesskey='" << acckey << "'>" << page << "</a><br>\n<br>\n"; 0682 ++acckey; 0683 } 0684 0685 os << "</ul>\n"; 0686 os << "<hr>\n"; 0687 os << "<p>" 0688 << i18n( 0689 "Note: if you read a man page in your language," 0690 " be aware it can contain some mistakes or be obsolete." 0691 " In case of doubt, you should have a look at the English version.") 0692 << "</p>"; 0693 0694 outputFooter(os); 0695 data(array); 0696 // Do not call finished(), the caller will do that 0697 } 0698 0699 KIO::WorkerResult MANProtocol::stat(const QUrl &url) 0700 { 0701 qCDebug(KIO_MAN_LOG) << "STAT " << url.url(); 0702 0703 QString title, section; 0704 0705 if (!parseUrl(url.path(), title, section)) { 0706 return KIO::WorkerResult::fail(KIO::ERR_MALFORMED_URL, url.url()); 0707 } 0708 0709 qCDebug(KIO_MAN_LOG) << "URL" << url.url() << "parsed to title" << title << "section" << section; 0710 0711 UDSEntry entry; 0712 entry.reserve(3); 0713 entry.fastInsert(KIO::UDSEntry::UDS_NAME, title); 0714 entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); 0715 entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("text/html")); 0716 0717 statEntry(entry); 0718 0719 return KIO::WorkerResult::pass(); 0720 } 0721 0722 extern "C" { 0723 0724 int Q_DECL_EXPORT kdemain(int argc, char **argv) 0725 { 0726 QCoreApplication app(argc, argv); 0727 app.setApplicationName(QLatin1String("kio_man")); 0728 0729 qCDebug(KIO_MAN_LOG) << "STARTING"; 0730 0731 if (argc != 4) { 0732 fprintf(stderr, "Usage: kio_man protocol domain-socket1 domain-socket2\n"); 0733 exit(-1); 0734 } 0735 0736 MANProtocol worker(argv[2], argv[3]); 0737 worker.dispatchLoop(); 0738 0739 qCDebug(KIO_MAN_LOG) << "Done"; 0740 0741 return 0; 0742 } 0743 } 0744 0745 KIO::WorkerResult MANProtocol::mimetype(const QUrl & /*url*/) 0746 { 0747 mimeType("text/html"); 0748 0749 return KIO::WorkerResult::pass(); 0750 } 0751 0752 //--------------------------------------------------------------------- 0753 0754 static QString sectionName(const QString §ion) 0755 { 0756 if (section == "0") 0757 return i18n("Header Files"); 0758 else if (section == "0p") 0759 return i18n("Header Files (POSIX)"); 0760 else if (section == "1") 0761 return i18n("User Commands"); 0762 else if (section == "1p") 0763 return i18n("User Commands (POSIX)"); 0764 else if (section == "2") 0765 return i18n("System Calls"); 0766 else if (section == "3") 0767 return i18n("Subroutines"); 0768 // TODO: current usage of '3p' seems to be "Subroutines (POSIX)" 0769 else if (section == "3p") 0770 return i18n("Perl Modules"); 0771 else if (section == "3n") 0772 return i18n("Network Functions"); 0773 else if (section == "4") 0774 return i18n("Devices"); 0775 else if (section == "5") 0776 return i18n("File Formats"); 0777 else if (section == "6") 0778 return i18n("Games"); 0779 else if (section == "7") 0780 return i18n("Miscellaneous"); 0781 else if (section == "8") 0782 return i18n("System Administration"); 0783 else if (section == "9") 0784 return i18n("Kernel"); 0785 else if (section == "l") 0786 return i18n("Local Documentation"); 0787 // TODO: current usage of 'n' seems to be TCL commands 0788 else if (section == "n") 0789 return i18n("New"); 0790 0791 return QString(); 0792 } 0793 0794 QStringList MANProtocol::buildSectionList(const QStringList &dirs) const 0795 { 0796 QStringList l; 0797 for (const QString &it_sect : qAsConst(m_sectionNames)) { 0798 for (const QString &it_dir : dirs) { 0799 QDir d(it_dir + "/man" + it_sect); 0800 if (d.exists()) { 0801 l << it_sect; 0802 break; 0803 } 0804 } 0805 } 0806 return l; 0807 } 0808 0809 //--------------------------------------------------------------------- 0810 0811 void MANProtocol::showMainIndex() 0812 { 0813 QByteArray array; 0814 QTextStream os(&array, QIODevice::WriteOnly); 0815 0816 outputHeader(os, i18n("Main Manual Page Index")); 0817 0818 // ### TODO: why still the environment variable 0819 // if keeping it, also use it in listDir() 0820 const QString sectList = qgetenv("MANSECT"); 0821 QStringList sections; 0822 if (sectList.isEmpty()) 0823 sections = buildSectionList(manDirectories()); 0824 else 0825 sections = sectList.split(':'); 0826 0827 os << "<table>\n"; 0828 0829 QSet<QChar> accessKeys; 0830 char alternateAccessKey = 'a'; 0831 for (const QString &it_sect : qAsConst(sections)) { 0832 if (it_sect.isEmpty()) 0833 continue; // guard back() below 0834 0835 // create a unique access key 0836 QChar accessKey = it_sect.back(); // rightmost char 0837 while (accessKeys.contains(accessKey)) 0838 accessKey = alternateAccessKey++; 0839 accessKeys.insert(accessKey); 0840 0841 os << "<tr><td><a href=\"man:(" << it_sect << ")\" accesskey=\"" << accessKey << "\">" << i18n("Section %1", it_sect) << "</a></td><td> </td><td> " 0842 << sectionName(it_sect) << "</td></tr>\n"; 0843 } 0844 0845 os << "</table>\n"; 0846 outputFooter(os); 0847 data(array); 0848 data(QByteArray()); 0849 // Do not call finished(), the caller will do that 0850 } 0851 0852 //--------------------------------------------------------------------- 0853 0854 // TODO: constr_catmanpath modified here, should parameter be passed by reference? 0855 void MANProtocol::constructPath(QStringList &constr_path, QStringList constr_catmanpath) 0856 { 0857 QMap<QString, QString> manpath_map; 0858 QMap<QString, QString> mandb_map; 0859 0860 // Add paths from /etc/man.conf 0861 // 0862 // Explicit manpaths may be given by lines starting with "MANPATH" or 0863 // "MANDATORY_MANPATH" (depending on system ?). 0864 // Mappings from $PATH to manpath are given by lines starting with 0865 // "MANPATH_MAP" 0866 0867 // The entry is e.g. "MANDATORY_MANPATH <manpath>" 0868 const QRegularExpression manpath_regex("^(?:MANPATH|MANDATORY_MANPATH)\\s+(\\S+)"); 0869 0870 // The entry is "MANPATH_MAP <path> <manpath>" 0871 const QRegularExpression manpath_map_regex("^MANPATH_MAP\\s+(\\S+)\\s+(\\S+)"); 0872 0873 // The entry is "MANDB_MAP <manpath> <catmanpath>" 0874 const QRegularExpression mandb_map_regex("^MANDB_MAP\\s+(\\S+)\\s+(\\S+)"); 0875 0876 QFile mc("/etc/man.conf"); // Caldera 0877 if (!mc.exists()) 0878 mc.setFileName("/etc/manpath.config"); // SuSE, Debian 0879 if (!mc.exists()) 0880 mc.setFileName("/etc/man.config"); // Mandrake 0881 0882 if (mc.open(QIODevice::ReadOnly)) { 0883 QTextStream is(&mc); 0884 while (!is.atEnd()) { 0885 const QString line = is.readLine(); 0886 0887 QRegularExpressionMatch rmatch; 0888 if (line.contains(manpath_regex, &rmatch)) { 0889 constr_path += rmatch.captured(1); 0890 } else if (line.contains(manpath_map_regex, &rmatch)) { 0891 const QString dir = QDir::cleanPath(rmatch.captured(1)); 0892 const QString mandir = QDir::cleanPath(rmatch.captured(2)); 0893 manpath_map[dir] = mandir; 0894 } else if (line.contains(mandb_map_regex, &rmatch)) { 0895 const QString mandir = QDir::cleanPath(rmatch.captured(1)); 0896 const QString catmandir = QDir::cleanPath(rmatch.captured(2)); 0897 mandb_map[mandir] = catmandir; 0898 } 0899 /* sections are not used 0900 else if ( section_regex.find(line, 0) == 0 ) 0901 { 0902 if ( !conf_section.isEmpty() ) 0903 conf_section += ':'; 0904 conf_section += line.mid(8).trimmed(); 0905 } 0906 */ 0907 } 0908 mc.close(); 0909 } 0910 0911 // Default paths 0912 static const char *const manpaths[] = {"/usr/X11/man", "/usr/X11R6/man", "/usr/man", "/usr/local/man", "/usr/exp/man", 0913 "/usr/openwin/man", "/usr/dt/man", "/opt/freetool/man", "/opt/local/man", "/usr/tex/man", 0914 "/usr/www/man", "/usr/lang/man", "/usr/gnu/man", "/usr/share/man", "/usr/motif/man", 0915 "/usr/titools/man", "/usr/sunpc/man", "/usr/ncd/man", "/usr/newsprint/man", nullptr}; 0916 0917 int i = 0; 0918 while (manpaths[i]) { 0919 if (constr_path.indexOf(QString(manpaths[i])) == -1) 0920 constr_path += QString(manpaths[i]); 0921 i++; 0922 } 0923 0924 // Directories in $PATH 0925 // - if a manpath mapping exists, use that mapping 0926 // - if a directory "<path>/man" or "<path>/../man" exists, add it 0927 // to the man path (the actual existence check is done further down) 0928 0929 if (::getenv("PATH")) { 0930 const QStringList path = QString::fromLocal8Bit(::getenv("PATH")).split(':', Qt::SkipEmptyParts); 0931 0932 for (const QString &it : qAsConst(path)) { 0933 const QString dir = QDir::cleanPath(it); 0934 QString mandir = manpath_map[dir]; 0935 0936 if (!mandir.isEmpty()) { 0937 // a path mapping exists 0938 if (constr_path.indexOf(mandir) == -1) 0939 constr_path += mandir; 0940 } else { 0941 // no manpath mapping, use "<path>/man" and "<path>/../man" 0942 0943 mandir = dir + QString("/man"); 0944 if (constr_path.indexOf(mandir) == -1) 0945 constr_path += mandir; 0946 0947 int pos = dir.lastIndexOf('/'); 0948 if (pos > 0) { 0949 mandir = dir.left(pos) + QString("/man"); 0950 if (constr_path.indexOf(mandir) == -1) 0951 constr_path += mandir; 0952 } 0953 } 0954 QString catmandir = mandb_map[mandir]; 0955 if (!mandir.isEmpty()) { 0956 if (constr_catmanpath.indexOf(catmandir) == -1) 0957 constr_catmanpath += catmandir; 0958 } else { 0959 // What is the default mapping? 0960 catmandir = mandir; 0961 catmandir.replace("/usr/share/", "/var/cache/"); 0962 if (constr_catmanpath.indexOf(catmandir) == -1) 0963 constr_catmanpath += catmandir; 0964 } 0965 } 0966 } 0967 } 0968 0969 void MANProtocol::checkManPaths() 0970 { 0971 static bool inited = false; 0972 if (inited) 0973 return; 0974 inited = true; 0975 0976 const QString manpath_env = qgetenv("MANPATH"); 0977 0978 // Decide if $MANPATH is enough on its own or if it should be merged 0979 // with the constructed path. 0980 // A $MANPATH starting or ending with ":", or containing "::", 0981 // should be merged with the constructed path. 0982 0983 const bool construct_path = (manpath_env.isEmpty() || manpath_env.startsWith(':') || manpath_env.endsWith(':') || manpath_env.contains("::")); 0984 0985 // Constructed man path -- consists of paths from 0986 // /etc/man.conf 0987 // default dirs 0988 // $PATH 0989 QStringList constr_path; 0990 QStringList constr_catmanpath; // catmanpath 0991 0992 if (construct_path) // need to read config file 0993 { 0994 constructPath(constr_path, constr_catmanpath); 0995 } 0996 0997 m_mandbpath = constr_catmanpath; 0998 0999 // Merge $MANPATH with the constructed path to form the 1000 // actual manpath. 1001 // 1002 // The merging syntax with ":" and "::" in $MANPATH will be 1003 // satisfied if any empty string in path_list_env (there 1004 // should be 1 or 0) is replaced by the constructed path. 1005 1006 const QStringList path_list_env = manpath_env.split(':', Qt::KeepEmptyParts); 1007 for (const QString &dir : path_list_env) { 1008 if (!dir.isEmpty()) // non empty part - add if exists 1009 { 1010 if (m_manpath.contains(dir)) 1011 continue; // ignore if already present 1012 if (QDir(dir).exists()) 1013 m_manpath += dir; // add to list if exists 1014 } else // empty part - merge constructed path 1015 { 1016 // Insert constructed path ($MANPATH was empty, or 1017 // there was a ":" at either end or "::" within) 1018 for (const QString &dir2 : qAsConst(constr_path)) { 1019 if (dir2.isEmpty()) 1020 continue; // don't add a null entry 1021 if (m_manpath.contains(dir2)) 1022 continue; // ignore if already present 1023 // add to list if exists 1024 if (QDir(dir2).exists()) 1025 m_manpath += dir2; 1026 } 1027 } 1028 } 1029 1030 /* sections are not used 1031 // Sections 1032 QStringList m_mansect = mansect_env.split( ':', Qt::KeepEmptyParts); 1033 1034 const char* const default_sect[] = 1035 { "1", "2", "3", "4", "5", "6", "7", "8", "9", "n", 0L }; 1036 1037 for ( int i = 0; default_sect[i] != 0L; i++ ) 1038 if ( m_mansect.indexOf( QString( default_sect[i] ) ) == -1 ) 1039 m_mansect += QString( default_sect[i] ); 1040 */ 1041 1042 qCDebug(KIO_MAN_LOG) << "manpath" << m_manpath; 1043 } 1044 1045 // Setup my own structure, with char pointers. 1046 // from now on only pointers are copied, no strings 1047 // 1048 // containing the whole path string, 1049 // the beginning of the man page name 1050 // and the length of the name 1051 struct man_index_t { 1052 char *manpath; // the full path including man file 1053 const char *manpage_begin; // pointer to the begin of the man file name in the path 1054 int manpage_len; // len of the man file name 1055 }; 1056 typedef man_index_t *man_index_ptr; 1057 1058 int compare_man_index(const void *s1, const void *s2) 1059 { 1060 struct man_index_t *m1 = *(struct man_index_t **)s1; 1061 struct man_index_t *m2 = *(struct man_index_t **)s2; 1062 int i; 1063 // Compare the names of the pages 1064 // with the shorter length. 1065 // Man page names are not '\0' terminated, so 1066 // this is a bit tricky 1067 if (m1->manpage_len > m2->manpage_len) { 1068 i = qstrnicmp(m1->manpage_begin, m2->manpage_begin, m2->manpage_len); 1069 if (!i) 1070 return 1; 1071 return i; 1072 } 1073 1074 if (m1->manpage_len < m2->manpage_len) { 1075 i = qstrnicmp(m1->manpage_begin, m2->manpage_begin, m1->manpage_len); 1076 if (!i) 1077 return -1; 1078 return i; 1079 } 1080 1081 return qstrnicmp(m1->manpage_begin, m2->manpage_begin, m1->manpage_len); 1082 } 1083 1084 void MANProtocol::showIndex(const QString §ion) 1085 { 1086 QByteArray array_h; 1087 QTextStream os_h(&array_h, QIODevice::WriteOnly); 1088 1089 // print header 1090 outputHeader(os_h, i18n("Index for section %1: %2", section, sectionName(section)), i18n("Manual Page Index")); 1091 1092 QByteArray array_d; 1093 QTextStream os(&array_d, QIODevice::WriteOnly); 1094 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1095 os.setCodec("UTF-8"); 1096 #endif 1097 os << "<div class=\"secidxmain\">\n"; 1098 1099 // compose list of search paths ------------------------------------------------------------- 1100 1101 checkManPaths(); 1102 infoMessage(i18n("Generating Index")); 1103 1104 // search for the man pages 1105 QStringList pages = findPages(section, QString()); 1106 1107 if (pages.isEmpty()) // not a single page found 1108 { 1109 // print footer 1110 outputFooter(os); 1111 os_h.flush(); 1112 1113 infoMessage(QString()); 1114 data(array_h + array_d); 1115 return; 1116 } 1117 1118 QMap<QString, QString> indexmap = buildIndexMap(section); 1119 1120 // print out the list 1121 os << "<br/><br/>\n"; 1122 os << "<table>\n"; 1123 1124 int listlen = pages.count(); 1125 man_index_ptr *indexlist = new man_index_ptr[listlen]; 1126 listlen = 0; 1127 1128 QStringList::const_iterator page; 1129 for (const QString &page : qAsConst(pages)) { 1130 // I look for the beginning of the man page name 1131 // i.e. "bla/pagename.3.gz" by looking for the last "/" 1132 // Then look for the end of the name by searching backwards 1133 // for the last ".", not counting zip extensions. 1134 // If the len of the name is >0, 1135 // store it in the list structure, to be sorted later 1136 1137 struct man_index_t *manindex = new man_index_t; 1138 manindex->manpath = strdup(page.toUtf8()); 1139 1140 manindex->manpage_begin = strrchr(manindex->manpath, '/'); 1141 if (manindex->manpage_begin) { 1142 manindex->manpage_begin++; 1143 Q_ASSERT(manindex->manpage_begin >= manindex->manpath); 1144 } else { 1145 manindex->manpage_begin = manindex->manpath; 1146 Q_ASSERT(manindex->manpage_begin >= manindex->manpath); 1147 } 1148 1149 // Skip extension ".section[.gz]" 1150 1151 const char *begin = manindex->manpage_begin; 1152 const int len = strlen(begin); 1153 const char *end = begin + (len - 1); 1154 1155 if (len >= 3 && strcmp(end - 2, ".gz") == 0) 1156 end -= 3; 1157 else if (len >= 2 && strcmp(end - 1, ".Z") == 0) 1158 end -= 2; 1159 else if (len >= 2 && strcmp(end - 1, ".z") == 0) 1160 end -= 2; 1161 else if (len >= 4 && strcmp(end - 3, ".bz2") == 0) 1162 end -= 4; 1163 else if (len >= 5 && strcmp(end - 4, ".lzma") == 0) 1164 end -= 5; 1165 else if (len >= 3 && strcmp(end - 2, ".xz") == 0) 1166 end -= 3; 1167 1168 while (end >= begin && *end != '.') 1169 end--; 1170 1171 if (end < begin) { 1172 // no '.' ending ??? 1173 // set the pointer past the end of the filename 1174 manindex->manpage_len = page.length(); 1175 manindex->manpage_len -= (manindex->manpage_begin - manindex->manpath); 1176 Q_ASSERT(manindex->manpage_len >= 0); 1177 } else { 1178 const char *manpage_end = end; 1179 manindex->manpage_len = (manpage_end - manindex->manpage_begin); 1180 Q_ASSERT(manindex->manpage_len >= 0); 1181 } 1182 1183 if (manindex->manpage_len > 0) { 1184 indexlist[listlen] = manindex; 1185 listlen++; 1186 } else 1187 delete manindex; 1188 } 1189 1190 // 1191 // Now do the sorting on the page names 1192 // and the printout afterwards 1193 // While printing avoid duplicate man page names 1194 // 1195 1196 struct man_index_t dummy_index = {nullptr, nullptr, 0}; 1197 struct man_index_t *last_index = &dummy_index; 1198 1199 // sort and print 1200 qsort(indexlist, listlen, sizeof(struct man_index_t *), compare_man_index); 1201 1202 QChar firstchar, tmp; 1203 QString indexLine = "<div class=\"secidxshort\">\n"; 1204 if (indexlist[0]->manpage_len > 0) { 1205 firstchar = QChar((indexlist[0]->manpage_begin)[0]).toLower(); 1206 1207 const QString appendixstr = QString(" [<a href=\"#%1\" accesskey=\"%2\">%3</a>]\n").arg(firstchar).arg(firstchar).arg(firstchar); 1208 indexLine.append(appendixstr); 1209 } 1210 os << "<tr><td class=\"secidxnextletter\"" 1211 << " colspan=\"3\">\n"; 1212 os << "<a name=\"" << firstchar << "\">" << firstchar << "</a>\n"; 1213 os << "</td></tr>\n"; 1214 1215 for (int i = 0; i < listlen; i++) { 1216 struct man_index_t *manindex = indexlist[i]; 1217 1218 // qstrncmp(): 1219 // "last_man" has already a \0 string ending, but 1220 // "manindex->manpage_begin" has not, 1221 // so do compare at most "manindex->manpage_len" of the strings. 1222 if (last_index->manpage_len == manindex->manpage_len && !qstrncmp(last_index->manpage_begin, manindex->manpage_begin, manindex->manpage_len)) { 1223 continue; 1224 } 1225 1226 tmp = QChar((manindex->manpage_begin)[0]).toLower(); 1227 if (firstchar != tmp) { 1228 firstchar = tmp; 1229 os << "<tr><td class=\"secidxnextletter\"" 1230 << " colspan=\"3\">\n <a name=\"" << firstchar << "\">" << firstchar << "</a>\n</td></tr>\n"; 1231 1232 const QString appendixstr = QString(" [<a href=\"#%1\" accesskey=\"%2\">%3</a>]\n").arg(firstchar).arg(firstchar).arg(firstchar); 1233 indexLine.append(appendixstr); 1234 } 1235 os << "<tr><td><a href=\"man:" << manindex->manpath << "\">\n"; 1236 1237 ((char *)manindex->manpage_begin)[manindex->manpage_len] = '\0'; 1238 os << manindex->manpage_begin << "</a></td><td> </td><td> " 1239 << (indexmap.contains(manindex->manpage_begin) ? indexmap[manindex->manpage_begin] : "") << "</td></tr>\n"; 1240 last_index = manindex; 1241 } 1242 indexLine.append("</div>"); 1243 1244 for (int i = 0; i < listlen; i++) { 1245 ::free(indexlist[i]->manpath); // allocated by strdup 1246 delete indexlist[i]; 1247 } 1248 1249 delete[] indexlist; 1250 1251 os << "</table></div>\n"; 1252 os << "<br/><br/>\n"; 1253 1254 os << indexLine << '\n'; 1255 1256 // print footer 1257 outputFooter(os); 1258 1259 // set the links "toolbar" also at the top 1260 os_h << indexLine << '\n'; 1261 os_h.flush(); 1262 1263 infoMessage(QString()); 1264 data(array_h + array_d); 1265 // Do not call finished(), the caller will do that 1266 } 1267 1268 KIO::WorkerResult MANProtocol::listDir(const QUrl &url) 1269 { 1270 qCDebug(KIO_MAN_LOG) << url; 1271 1272 QString title; 1273 QString section; 1274 1275 if (!parseUrl(url.path(), title, section)) { 1276 return KIO::WorkerResult::fail(KIO::ERR_MALFORMED_URL, url.url()); 1277 } 1278 1279 // stat() and listDir() declared that everything is a HTML file. 1280 // However we can list man: and man:(1) as a directory (e.g. in dolphin). 1281 // But we cannot list man:ls as a directory, this makes no sense (#154173). 1282 1283 if (!title.isEmpty() && title != "/") { 1284 return KIO::WorkerResult::fail(KIO::ERR_IS_FILE, url.url()); 1285 } 1286 1287 // There is no need to accumulate a list of UDSEntry's and deliver 1288 // them all in one block with WorkerBase::listEntries(), because 1289 // WorkerBase::listEntry() batches the entries and delivers them 1290 // timed to maximise application performance. 1291 1292 UDSEntry uds_entry; 1293 uds_entry.reserve(4); 1294 1295 uds_entry.fastInsert(KIO::UDSEntry::UDS_NAME, "."); 1296 uds_entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); 1297 listEntry(uds_entry); 1298 1299 if (section.isEmpty()) // list the top level directory 1300 { 1301 for (const QString § : m_sectionNames) { 1302 uds_entry.clear(); // sectionName() is already I18N'ed 1303 uds_entry.fastInsert(KIO::UDSEntry::UDS_NAME, (sect + " - " + sectionName(sect))); 1304 uds_entry.fastInsert(KIO::UDSEntry::UDS_URL, ("man:/(" + sect + ')')); 1305 uds_entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); 1306 listEntry(uds_entry); 1307 } 1308 } else // list all pages in a section 1309 { 1310 const QStringList list = findPages(section, QString()); 1311 for (const QString &page : list) { 1312 // Remove any compression suffix present 1313 QString name = stripCompression(page); 1314 QString displayName; 1315 // Remove any preceding pathname components, just leave the base name 1316 int pos = name.lastIndexOf('/'); 1317 if (pos > 0) 1318 name = name.mid(pos + 1); 1319 // Remove the section suffix 1320 pos = name.lastIndexOf('.'); 1321 if (pos > 0) { 1322 displayName = name.left(pos) + " (" + name.mid(pos + 1) + ')'; 1323 name.truncate(pos); 1324 } 1325 1326 uds_entry.clear(); 1327 uds_entry.fastInsert(KIO::UDSEntry::UDS_NAME, name); 1328 if (!displayName.isEmpty()) 1329 uds_entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, displayName); 1330 uds_entry.fastInsert(KIO::UDSEntry::UDS_URL, ("man:" + page)); 1331 uds_entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); 1332 uds_entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("text/html")); 1333 listEntry(uds_entry); 1334 } 1335 } 1336 1337 return KIO::WorkerResult::pass(); 1338 } 1339 1340 bool MANProtocol::getProgramPath() 1341 { 1342 if (!mySgml2RoffPath.isEmpty()) 1343 return true; 1344 1345 mySgml2RoffPath = QStandardPaths::findExecutable(SGML2ROFF_EXECUTABLE); 1346 if (!mySgml2RoffPath.isEmpty()) 1347 return true; 1348 1349 /* sgml2roff isn't found in PATH. Check some possible locations where it may be found. */ 1350 mySgml2RoffPath = QStandardPaths::findExecutable(SGML2ROFF_EXECUTABLE, QStringList(QLatin1String(SGML2ROFF_DIRS))); 1351 if (!mySgml2RoffPath.isEmpty()) 1352 return true; 1353 1354 /* Cannot find sgml2roff program: */ 1355 outputError(xi18nc("@info", 1356 "Could not find the <command>%1</command> program on your system. " 1357 "Please install it if necessary, and ensure that it can be found using " 1358 "the environment variable <envar>PATH</envar>.", 1359 SGML2ROFF_EXECUTABLE)); 1360 return false; 1361 } 1362 1363 #include "kio_man.moc" 1364 #include "moc_kio_man.cpp"